am 46fc50e8: Merge "Support wrapping app processes to inject debug instrumentation. Bug: 4437846"

* commit '46fc50e810eaab93f2787b355257ab1e837d8bf7':
  Support wrapping app processes to inject debug instrumentation. Bug: 4437846
diff --git a/Android.mk b/Android.mk
index 4384c3e..69e46ec 100644
--- a/Android.mk
+++ b/Android.mk
@@ -54,5 +54,18 @@
 
 ifeq ($(WITH_HOST_DALVIK),true)
     .PHONY: dalvik-host
-    dalvik-host: dalvik $(HOST_OUT)/bin/dalvikvm $(HOST_OUT)/bin/dexopt $(HOST_OUT)/lib/libsqlite.so cacerts-host $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.dat $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.idx $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.version core-hostdex bouncycastle-hostdex apache-xml-hostdex $(call intermediates-dir-for,JAVA_LIBRARIES,core-tests,,COMMON)/classes.jar
+    dalvik-host: \
+        dalvik \
+        $(HOST_OUT)/bin/dalvikvm \
+        $(HOST_OUT)/bin/dexopt \
+        $(HOST_OUT)/lib/libsqlite.so \
+        cacerts-host \
+        $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.dat \
+        $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.idx \
+        $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.version \
+        core-hostdex \
+        bouncycastle-hostdex \
+        apache-xml-hostdex \
+        apache-harmony-tests-hostdex \
+        $(call intermediates-dir-for,JAVA_LIBRARIES,core-tests,,COMMON)/classes.jar
 endif
diff --git a/dalvik/src/main/java/dalvik/system/BlockGuard.java b/dalvik/src/main/java/dalvik/system/BlockGuard.java
index a0d9828..cf9789e 100644
--- a/dalvik/src/main/java/dalvik/system/BlockGuard.java
+++ b/dalvik/src/main/java/dalvik/system/BlockGuard.java
@@ -25,12 +25,10 @@
 import java.net.InetAddress;
 import java.net.SocketException;
 import java.net.SocketImpl;
-
 import libcore.io.ErrnoException;
 import libcore.io.Libcore;
 import libcore.io.StructLinger;
-import org.apache.harmony.luni.platform.INetworkSystem;
-
+import libcore.util.EmptyArray;
 import static libcore.io.OsConstants.*;
 
 /**
@@ -219,8 +217,8 @@
             throws IOException {
         if (!TAG_SOCKETS) return;
 
-        final byte[] tagBytes = tag != null ? tag.getBytes() : new byte[0];
-        final byte[] uidBytes = uid != -1 ? Integer.toString(uid).getBytes() : new byte[0];
+        final byte[] tagBytes = tag != null ? tag.getBytes() : EmptyArray.BYTE;
+        final byte[] uidBytes = uid != -1 ? Integer.toString(uid).getBytes() : EmptyArray.BYTE;
 
         final ByteArrayOutputStream buffer = new ByteArrayOutputStream(
                 4 + tagBytes.length + uidBytes.length);
@@ -244,125 +242,4 @@
     }
 
     private BlockGuard() {}
-
-    /**
-     * A network wrapper that calls the policy check functions.
-     */
-    public static class WrappedNetworkSystem implements INetworkSystem {
-        private final INetworkSystem mNetwork;
-
-        public WrappedNetworkSystem(INetworkSystem network) {
-            mNetwork = network;
-        }
-
-        public void accept(FileDescriptor serverFd, SocketImpl newSocket,
-                FileDescriptor clientFd) throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            mNetwork.accept(serverFd, newSocket, clientFd);
-            tagSocketFd(clientFd);
-        }
-
-        public void bind(FileDescriptor aFD, InetAddress inetAddress, int port)
-                throws SocketException {
-            mNetwork.bind(aFD, inetAddress, port);
-        }
-
-        public int read(FileDescriptor aFD, byte[] data, int offset, int count) throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.read(aFD, data, offset, count);
-        }
-
-        public int readDirect(FileDescriptor aFD, int address, int count) throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.readDirect(aFD, address, count);
-        }
-
-        public int write(FileDescriptor fd, byte[] data, int offset, int count)
-                throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.write(fd, data, offset, count);
-        }
-
-        public int writeDirect(FileDescriptor fd, int address, int offset, int count)
-                throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.writeDirect(fd, address, offset, count);
-        }
-
-        public boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.connect(fd, inetAddress, port);
-        }
-
-        public boolean isConnected(FileDescriptor fd, int timeout) throws IOException {
-            if (timeout != 0) {
-                // Greater than 0 is a timeout, but zero means "poll and return immediately".
-                BlockGuard.getThreadPolicy().onNetwork();
-            }
-            return mNetwork.isConnected(fd, timeout);
-        }
-
-        public int send(FileDescriptor fd, byte[] data, int offset, int length,
-                int port, InetAddress inetAddress) throws IOException {
-            // Note: no BlockGuard violation.  We permit datagrams
-            // without hostname lookups.  (short, bounded amount of time)
-            return mNetwork.send(fd, data, offset, length, port, inetAddress);
-        }
-
-        public int sendDirect(FileDescriptor fd, int address, int offset, int length,
-                int port, InetAddress inetAddress) throws IOException {
-            // Note: no BlockGuard violation.  We permit datagrams
-            // without hostname lookups.  (short, bounded amount of time)
-            return mNetwork.sendDirect(fd, address, offset, length, port, inetAddress);
-        }
-
-        public int recv(FileDescriptor fd, DatagramPacket packet, byte[] data, int offset,
-                int length, boolean peek, boolean connected) throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.recv(fd, packet, data, offset, length, peek, connected);
-        }
-
-        public int recvDirect(FileDescriptor fd, DatagramPacket packet, int address, int offset,
-                int length, boolean peek, boolean connected) throws IOException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.recvDirect(fd, packet, address, offset, length, peek, connected);
-        }
-
-        public void disconnectDatagram(FileDescriptor aFD) throws SocketException {
-            mNetwork.disconnectDatagram(aFD);
-        }
-
-        public void sendUrgentData(FileDescriptor fd, byte value) {
-            mNetwork.sendUrgentData(fd, value);
-        }
-
-        public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
-                int numReadable, int numWritable, long timeout, int[] flags)
-                throws SocketException {
-            BlockGuard.getThreadPolicy().onNetwork();
-            return mNetwork.select(readFDs, writeFDs, numReadable, numWritable, timeout, flags);
-        }
-
-        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
-            // extra hoops.
-            if (isLingerSocket(aFD)) {
-                BlockGuard.getThreadPolicy().onNetwork();
-            }
-            mNetwork.close(aFD);
-        }
-
-        private boolean isLingerSocket(FileDescriptor fd) {
-            try {
-                StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
-                return linger.isOn() && linger.l_linger > 0;
-            } catch (Exception ignored) {
-                // We're called via Socket.close (which doesn't ask for us to be called), so we
-                // must not throw here, because Socket.close must not throw if asked to close an
-                // already-closed socket.
-                return false;
-            }
-        }
-    }
 }
diff --git a/expectations/icebox.txt b/expectations/icebox.txt
index 4d664a6..d85efc1 100644
--- a/expectations/icebox.txt
+++ b/expectations/icebox.txt
@@ -42,13 +42,13 @@
   description: "Dalvik doesn't support XML Schemas, DTDs or validation",
   bug: 3268630,
   name: "libcore.xml.DeclarationTest#testGetXmlEncoding",
-  substring: "This implementation doesn't parse the encoding from the XML declaration expected:<ISO-8859-1> but was:<null>"
+  substring: "This implementation doesn't parse the encoding from the XML declaration"
 },
 {
   description: "Dalvik doesn't support XML Schemas, DTDs or validation",
   bug: 3268630,
   name: "libcore.xml.DeclarationTest#testGetXmlStandalone",
-  substring: "This implementation doesn't parse standalone from the XML declaration expected:<true> but was:<false>"
+  substring: "This implementation doesn't parse standalone from the XML declaration"
 },
 {
   description: "Dalvik doesn't support XML Schemas, DTDs or validation",
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index 3d2eb5f..f784394 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -11,6 +11,11 @@
   bug: 3483365
 },
 {
+  description: "Expat uses an unbounded number of global references",
+  name: "libcore.xml.ExpatSaxParserTest#testGlobalReferenceTableOverflow",
+  bug: 2772628
+},
+{
   description: "Test fails, Intermediate certificate lacks BasicConstraints",
   name: "com.android.org.bouncycastle.jce.provider.PKIXCertPathValidatorSpiTest#testTrustAndRemoteCertificatesWithDifferentEncodings",
   bug: 3474648
diff --git a/junit/src/main/java/junit/framework/ComparisonFailure.java b/junit/src/main/java/junit/framework/ComparisonFailure.java
index 0cb2cee..ccd476b 100644
--- a/junit/src/main/java/junit/framework/ComparisonFailure.java
+++ b/junit/src/main/java/junit/framework/ComparisonFailure.java
@@ -34,7 +34,7 @@
         int end= Math.min(fExpected.length(), fActual.length());
 
         int i= 0;
-        for(; i < end; i++) {
+        for (; i < end; i++) {
             if (fExpected.charAt(i) != fActual.charAt(i))
                 break;
         }
diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java
index 5b26792..6c2da1e 100644
--- a/luni/src/main/java/java/io/FileInputStream.java
+++ b/luni/src/main/java/java/io/FileInputStream.java
@@ -23,6 +23,7 @@
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
 import libcore.io.ErrnoException;
+import libcore.io.IoBridge;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
 import libcore.io.Streams;
@@ -76,7 +77,7 @@
         if (file == null) {
             throw new NullPointerException("file == null");
         }
-        this.fd = IoUtils.open(file.getAbsolutePath(), O_RDONLY);
+        this.fd = IoBridge.open(file.getAbsolutePath(), O_RDONLY);
         this.ownedFd = fd;
         guard.open("close");
     }
@@ -108,7 +109,7 @@
 
     @Override
     public int available() throws IOException {
-        return IoUtils.available(fd);
+        return IoBridge.available(fd);
     }
 
     @Override
@@ -171,7 +172,7 @@
     }
 
     @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
-        return IoUtils.read(fd, buffer, byteOffset, byteCount);
+        return IoBridge.read(fd, buffer, byteOffset, byteCount);
     }
 
     @Override
diff --git a/luni/src/main/java/java/io/FileOutputStream.java b/luni/src/main/java/java/io/FileOutputStream.java
index 8c61281..cc57571 100644
--- a/luni/src/main/java/java/io/FileOutputStream.java
+++ b/luni/src/main/java/java/io/FileOutputStream.java
@@ -21,6 +21,7 @@
 import java.nio.NioUtils;
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
+import libcore.io.IoBridge;
 import libcore.io.IoUtils;
 import static libcore.io.OsConstants.*;
 
@@ -88,7 +89,7 @@
             throw new NullPointerException("file == null");
         }
         this.mode = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC);
-        this.fd = IoUtils.open(file.getAbsolutePath(), mode);
+        this.fd = IoBridge.open(file.getAbsolutePath(), mode);
         this.ownedFd = fd;
         this.guard.open("close");
     }
@@ -175,7 +176,7 @@
 
     @Override
     public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
-        IoUtils.write(fd, buffer, byteOffset, byteCount);
+        IoBridge.write(fd, buffer, byteOffset, byteCount);
     }
 
     @Override
diff --git a/luni/src/main/java/java/io/InterruptedIOException.java b/luni/src/main/java/java/io/InterruptedIOException.java
index 8d79ba5..af307bd 100644
--- a/luni/src/main/java/java/io/InterruptedIOException.java
+++ b/luni/src/main/java/java/io/InterruptedIOException.java
@@ -32,20 +32,23 @@
     public int bytesTransferred;
 
     /**
-     * Constructs a new {@code InterruptedIOException} with its stack trace
-     * filled in.
+     * Constructs a new instance.
      */
     public InterruptedIOException() {
     }
 
     /**
-     * Constructs a new {@code InterruptedIOException} with its stack trace and
-     * detail message filled in.
-     *
-     * @param detailMessage
-     *            the detail message for this exception.
+     * Constructs a new instance with the given detail message.
      */
     public InterruptedIOException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public InterruptedIOException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/io/ObjectStreamClass.java b/luni/src/main/java/java/io/ObjectStreamClass.java
index 386cfb0..e87fcd4 100644
--- a/luni/src/main/java/java/io/ObjectStreamClass.java
+++ b/luni/src/main/java/java/io/ObjectStreamClass.java
@@ -810,7 +810,7 @@
         return loadFields == null ? fields().clone() : loadFields.clone();
     }
 
-    private volatile List<ObjectStreamClass> cachedHierarchy;
+    private transient volatile List<ObjectStreamClass> cachedHierarchy;
 
     List<ObjectStreamClass> getHierarchy() {
         List<ObjectStreamClass> result = cachedHierarchy;
diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java
index 8517604..dde779e 100644
--- a/luni/src/main/java/java/io/RandomAccessFile.java
+++ b/luni/src/main/java/java/io/RandomAccessFile.java
@@ -24,6 +24,7 @@
 import java.nio.charset.ModifiedUtf8;
 import java.util.Arrays;
 import libcore.io.ErrnoException;
+import libcore.io.IoBridge;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
 import libcore.io.Memory;
@@ -114,7 +115,7 @@
             throw new IllegalArgumentException("Invalid mode: " + mode);
         }
         this.mode = flags;
-        this.fd = IoUtils.open(file.getAbsolutePath(), flags);
+        this.fd = IoBridge.open(file.getAbsolutePath(), flags);
 
         // if we are in "rws" mode, attempt to sync file+metadata
         if (syncMetadata) {
@@ -286,7 +287,7 @@
      *             if this file is closed or another I/O error occurs.
      */
     public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
-        return IoUtils.read(fd, buffer, byteOffset, byteCount);
+        return IoBridge.read(fd, buffer, byteOffset, byteCount);
     }
 
     /**
@@ -688,7 +689,7 @@
      *             if an I/O error occurs while writing to this file.
      */
     public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
-        IoUtils.write(fd, buffer, byteOffset, byteCount);
+        IoBridge.write(fd, buffer, byteOffset, byteCount);
         // if we are in "rws" mode, attempt to sync file+metadata
         if (syncMetadata) {
             fd.sync();
diff --git a/luni/src/main/java/java/lang/Boolean.java b/luni/src/main/java/java/lang/Boolean.java
index f95a38b..2c8ffc8 100644
--- a/luni/src/main/java/java/lang/Boolean.java
+++ b/luni/src/main/java/java/lang/Boolean.java
@@ -101,9 +101,9 @@
      *         {@code Boolean}; {@code false} otherwise.
      */
     @Override
+    @FindBugsSuppressWarnings("RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN")
     public boolean equals(Object o) {
-        return (o == this)
-                || ((o instanceof Boolean) && (value == ((Boolean) o).value));
+        return (o == this) || ((o instanceof Boolean) && (((Boolean) o).value == value));
     }
 
     /**
diff --git a/luni/src/main/java/java/lang/Byte.java b/luni/src/main/java/java/lang/Byte.java
index 7e0b293..bd1a1e4 100644
--- a/luni/src/main/java/java/lang/Byte.java
+++ b/luni/src/main/java/java/lang/Byte.java
@@ -22,6 +22,7 @@
  *
  * @since 1.1
  */
+@FindBugsSuppressWarnings("DM_NUMBER_CTOR")
 public final class Byte extends Number implements Comparable<Byte> {
 
     private static final long serialVersionUID = -7183698231559129828L;
@@ -155,9 +156,9 @@
      *         {@code Byte}; {@code false} otherwise.
      */
     @Override
+    @FindBugsSuppressWarnings("RC_REF_COMPARISON")
     public boolean equals(Object object) {
-        return (object == this) || (object instanceof Byte)
-                && (value == ((Byte) object).value);
+        return (object == this) || ((object instanceof Byte) && (((Byte) object).value == value));
     }
 
     @Override
diff --git a/luni/src/main/java/java/lang/Character.java b/luni/src/main/java/java/lang/Character.java
index 1e39428..67c5c71 100644
--- a/luni/src/main/java/java/lang/Character.java
+++ b/luni/src/main/java/java/lang/Character.java
@@ -96,6 +96,7 @@
  *
  * @since 1.0
  */
+@FindBugsSuppressWarnings("DM_NUMBER_CTOR")
 public final class Character implements Serializable, Comparable<Character> {
     private static final long serialVersionUID = 3786198910865385080L;
 
@@ -1641,7 +1642,7 @@
     private static final Character[] SMALL_VALUES = new Character[128];
 
     static {
-        for(int i = 0; i < 128; i++) {
+        for (int i = 0; i < 128; i++) {
             SMALL_VALUES[i] = new Character((char) i);
         }
     }
@@ -2405,7 +2406,7 @@
      */
     @Override
     public boolean equals(Object object) {
-        return (object instanceof Character) && (value == ((Character) object).value);
+        return (object instanceof Character) && (((Character) object).value == value);
     }
 
     /**
diff --git a/luni/src/main/java/java/lang/ClassLoader.java b/luni/src/main/java/java/lang/ClassLoader.java
index 2ec066f..0cdc448 100644
--- a/luni/src/main/java/java/lang/ClassLoader.java
+++ b/luni/src/main/java/java/lang/ClassLoader.java
@@ -747,6 +747,7 @@
 
     private static BootClassLoader instance;
 
+    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
     public static synchronized BootClassLoader getInstance() {
         if (instance == null) {
             instance = new BootClassLoader();
diff --git a/luni/src/main/java/java/lang/Double.java b/luni/src/main/java/java/lang/Double.java
index 1748128..456529b 100644
--- a/luni/src/main/java/java/lang/Double.java
+++ b/luni/src/main/java/java/lang/Double.java
@@ -166,31 +166,18 @@
     }
 
     /**
-     * Converts the specified double value to a binary representation conforming
-     * to the IEEE 754 floating-point double precision bit layout. All
-     * <em>Not-a-Number (NaN)</em> values are converted to a single NaN
-     * representation ({@code 0x7ff8000000000000L}).
-     *
-     * @param value
-     *            the double value to convert.
-     * @return the IEEE 754 floating-point double precision representation of
-     *         {@code value}.
-     * @see #doubleToRawLongBits(double)
-     * @see #longBitsToDouble(long)
+     * Returns an integer corresponding to the bits of the given
+     * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> double precision
+     * {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN
+     * representation ({@code 0x7ff8000000000000L}) (compare to {@link #doubleToRawLongBits}).
      */
     public static native long doubleToLongBits(double value);
 
     /**
-     * Converts the specified double value to a binary representation conforming
-     * to the IEEE 754 floating-point double precision bit layout.
-     * <em>Not-a-Number (NaN)</em> values are preserved.
-     *
-     * @param value
-     *            the double value to convert.
-     * @return the IEEE 754 floating-point double precision representation of
-     *         {@code value}.
-     * @see #doubleToLongBits(double)
-     * @see #longBitsToDouble(long)
+     * Returns an integer corresponding to the bits of the given
+     * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> double precision
+     * {@code value}. <em>Not-a-Number (NaN)</em> values are preserved (compare
+     * to {@link #doubleToLongBits}).
      */
     public static native long doubleToRawLongBits(double value);
 
@@ -219,9 +206,8 @@
      */
     @Override
     public boolean equals(Object object) {
-        return (object == this)
-                || (object instanceof Double)
-                && (doubleToLongBits(this.value) == doubleToLongBits(((Double) object).value));
+        return (object instanceof Double) &&
+                (doubleToLongBits(this.value) == doubleToLongBits(((Double) object).value));
     }
 
     @Override
@@ -286,15 +272,8 @@
     }
 
     /**
-     * Converts the specified IEEE 754 floating-point double precision bit
-     * pattern to a Java double value.
-     *
-     * @param bits
-     *            the IEEE 754 floating-point double precision representation of
-     *            a double value.
-     * @return the double value converted from {@code bits}.
-     * @see #doubleToLongBits(double)
-     * @see #doubleToRawLongBits(double)
+     * Returns the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a>
+     * double precision float corresponding to the given {@code bits}.
      */
     public static native double longBitsToDouble(long bits);
 
@@ -313,7 +292,7 @@
      *             if {@code string} cannot be parsed as a double value.
      */
     public static double parseDouble(String string) throws NumberFormatException {
-        return org.apache.harmony.luni.util.FloatingPointParser.parseDouble(string);
+        return StringToReal.parseDouble(string);
     }
 
     @Override
@@ -421,7 +400,7 @@
      */
     public static String toHexString(double d) {
         /*
-         * Reference: http://en.wikipedia.org/wiki/IEEE_754
+         * Reference: http://en.wikipedia.org/wiki/IEEE_754-1985
          */
         if (d != d) {
             return "NaN";
diff --git a/luni/src/main/java/java/lang/FinalizerThread.java b/luni/src/main/java/java/lang/FinalizerThread.java
index 5f478df..978332c 100644
--- a/luni/src/main/java/java/lang/FinalizerThread.java
+++ b/luni/src/main/java/java/lang/FinalizerThread.java
@@ -67,6 +67,7 @@
         }
     }
 
+    @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
     private void doFinalize(FinalizerReference<Object> reference) {
         FinalizerReference.remove(reference);
         Object obj = reference.get();
diff --git a/luni/src/main/java/java/lang/FindBugsSuppressWarnings.java b/luni/src/main/java/java/lang/FindBugsSuppressWarnings.java
new file mode 100644
index 0000000..ac753bd
--- /dev/null
+++ b/luni/src/main/java/java/lang/FindBugsSuppressWarnings.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+import java.lang.annotation.Target;
+
+/**
+ * Suppress FindBugs warnings on the annotated element. FindBugs will recognize
+ * any annotation that has class retention and whose name ends with
+ * "SuppressWarnings".
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(CLASS)
+public @interface FindBugsSuppressWarnings {
+
+    /**
+     * The <a href="http://findbugs.sourceforge.net/bugDescriptions.html">FindBugs
+     * Patterns</a> to suppress, such as {@code SE_TRANSIENT_FIELD_NOT_RESTORED}
+     * or {@code Se}. Full, upper case names are preferred.
+     */
+    String[] value();
+}
diff --git a/luni/src/main/java/java/lang/Float.java b/luni/src/main/java/java/lang/Float.java
index bbbb7f7..900b2a0 100644
--- a/luni/src/main/java/java/lang/Float.java
+++ b/luni/src/main/java/java/lang/Float.java
@@ -189,37 +189,23 @@
      */
     @Override
     public boolean equals(Object object) {
-        return (object == this)
-                || (object instanceof Float)
-                && (floatToIntBits(this.value) == floatToIntBits(((Float) object).value));
+        return (object instanceof Float) &&
+                (floatToIntBits(this.value) == floatToIntBits(((Float) object).value));
     }
 
     /**
-     * Converts the specified float value to a binary representation conforming
-     * to the IEEE 754 floating-point single precision bit layout. All
-     * <em>Not-a-Number (NaN)</em> values are converted to a single NaN
-     * representation ({@code 0x7fc00000}).
-     *
-     * @param value
-     *            the float value to convert.
-     * @return the IEEE 754 floating-point single precision representation of
-     *         {@code value}.
-     * @see #floatToRawIntBits(float)
-     * @see #intBitsToFloat(int)
+     * Returns an integer corresponding to the bits of the given
+     * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> single precision
+     * float {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN
+     * representation ({@code 0x7fc00000}) (compare to {@link #floatToRawIntBits}).
      */
     public static native int floatToIntBits(float value);
 
     /**
-     * Converts the specified float value to a binary representation conforming
-     * to the IEEE 754 floating-point single precision bit layout.
-     * <em>Not-a-Number (NaN)</em> values are preserved.
-     *
-     * @param value
-     *            the float value to convert.
-     * @return the IEEE 754 floating-point single precision representation of
-     *         {@code value}.
-     * @see #floatToIntBits(float)
-     * @see #intBitsToFloat(int)
+     * Returns an integer corresponding to the bits of the given
+     * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> single precision
+     * float {@code value}. <em>Not-a-Number (NaN)</em> values are preserved (compare
+     * to {@link #floatToIntBits}).
      */
     public static native int floatToRawIntBits(float value);
 
@@ -239,15 +225,8 @@
     }
 
     /**
-     * Converts the specified IEEE 754 floating-point single precision bit
-     * pattern to a Java float value.
-     *
-     * @param bits
-     *            the IEEE 754 floating-point single precision representation of
-     *            a float value.
-     * @return the float value converted from {@code bits}.
-     * @see #floatToIntBits(float)
-     * @see #floatToRawIntBits(float)
+     * Returns the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a>
+     * single precision float corresponding to the given {@code bits}.
      */
     public static native float intBitsToFloat(int bits);
 
@@ -318,8 +297,7 @@
      * @since 1.2
      */
     public static float parseFloat(String string) throws NumberFormatException {
-        return org.apache.harmony.luni.util.FloatingPointParser
-                .parseFloat(string);
+        return StringToReal.parseFloat(string);
     }
 
     @Override
@@ -429,7 +407,7 @@
      */
     public static String toHexString(float f) {
         /*
-         * Reference: http://en.wikipedia.org/wiki/IEEE_754
+         * Reference: http://en.wikipedia.org/wiki/IEEE_754-1985
          */
         if (f != f) {
             return "NaN";
diff --git a/luni/src/main/java/org/apache/harmony/luni/util/HexStringParser.java b/luni/src/main/java/java/lang/HexStringParser.java
similarity index 99%
rename from luni/src/main/java/org/apache/harmony/luni/util/HexStringParser.java
rename to luni/src/main/java/java/lang/HexStringParser.java
index 9e20a16..8593b99 100644
--- a/luni/src/main/java/org/apache/harmony/luni/util/HexStringParser.java
+++ b/luni/src/main/java/java/lang/HexStringParser.java
@@ -15,13 +15,17 @@
  *  limitations under the License.
  */
 
-package org.apache.harmony.luni.util;
+package java.lang;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /*
  * Parses hex string to a single or double precision floating point number.
+ *
+ * TODO: rewrite this!
+ *
+ * @hide
  */
 final class HexStringParser {
 
diff --git a/luni/src/main/java/java/lang/Integer.java b/luni/src/main/java/java/lang/Integer.java
index 06d7f90..15511a8 100644
--- a/luni/src/main/java/java/lang/Integer.java
+++ b/luni/src/main/java/java/lang/Integer.java
@@ -29,6 +29,7 @@
  * @see java.lang.Long
  * @since 1.0
  */
+@FindBugsSuppressWarnings("DM_NUMBER_CTOR")
 public final class Integer extends Number implements Comparable<Integer> {
 
     private static final long serialVersionUID = 1360826667806852920L;
@@ -205,7 +206,7 @@
      */
     @Override
     public boolean equals(Object o) {
-        return o instanceof Integer && ((Integer) o).value == value;
+        return (o instanceof Integer) && (((Integer) o).value == value);
     }
 
     @Override
@@ -712,7 +713,7 @@
     private static final Integer[] SMALL_VALUES = new Integer[256];
 
     static {
-        for(int i = -128; i < 128; i++) {
+        for (int i = -128; i < 128; i++) {
             SMALL_VALUES[i + 128] = new Integer(i);
         }
     }
diff --git a/luni/src/main/java/java/lang/IntegralToString.java b/luni/src/main/java/java/lang/IntegralToString.java
index c858a37..03a5359 100644
--- a/luni/src/main/java/java/lang/IntegralToString.java
+++ b/luni/src/main/java/java/lang/IntegralToString.java
@@ -462,6 +462,17 @@
         return new String(0, 2, buf);
     }
 
+    public static String bytesToHexString(byte[] bytes, boolean upperCase) {
+        char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+        char[] buf = new char[bytes.length * 2];
+        int c = 0;
+        for (byte b : bytes) {
+            buf[c++] = digits[(b >> 4) & 0xf];
+            buf[c++] = digits[b & 0xf];
+        }
+        return new String(buf);
+    }
+
     public static String intToHexString(int i, boolean upperCase, int minWidth) {
         int bufLen = 8;  // Max number of hex digits in an int
         char[] buf = new char[bufLen];
diff --git a/luni/src/main/java/java/lang/Long.java b/luni/src/main/java/java/lang/Long.java
index d6605e6..8b592e2 100644
--- a/luni/src/main/java/java/lang/Long.java
+++ b/luni/src/main/java/java/lang/Long.java
@@ -29,6 +29,7 @@
  * @see java.lang.Integer
  * @since 1.0
  */
+@FindBugsSuppressWarnings("DM_NUMBER_CTOR")
 public final class Long extends Number implements Comparable<Long> {
 
     private static final long serialVersionUID = 4290774380558885855L;
@@ -193,7 +194,7 @@
      */
     @Override
     public boolean equals(Object o) {
-        return o instanceof Long && ((Long) o).value == value;
+        return (o instanceof Long) && (((Long) o).value == value);
     }
 
     @Override
@@ -724,8 +725,7 @@
      * @since 1.5
      */
     public static Long valueOf(long v) {
-        return  v >= 128 || v < -128 ? new Long(v)
-                                     : SMALL_VALUES[((int) v) + 128];
+        return  v >= 128 || v < -128 ? new Long(v) : SMALL_VALUES[((int) v) + 128];
     }
 
     /**
@@ -734,7 +734,7 @@
     private static final Long[] SMALL_VALUES = new Long[256];
 
     static {
-        for(int i = -128; i < 128; i++) {
+        for (int i = -128; i < 128; i++) {
             SMALL_VALUES[i + 128] = new Long(i);
         }
     }
diff --git a/luni/src/main/java/java/lang/Object.java b/luni/src/main/java/java/lang/Object.java
index f6c01fe..7f4b490 100644
--- a/luni/src/main/java/java/lang/Object.java
+++ b/luni/src/main/java/java/lang/Object.java
@@ -219,6 +219,7 @@
      *
      * See <i>Effective Java</i> Item 7, "Avoid finalizers" for more.
      */
+    @FindBugsSuppressWarnings("FI_EMPTY")
     protected void finalize() throws Throwable {
     }
 
diff --git a/luni/src/main/java/java/lang/ProcessManager.java b/luni/src/main/java/java/lang/ProcessManager.java
index cdcbb8a..f5afedb 100644
--- a/luni/src/main/java/java/lang/ProcessManager.java
+++ b/luni/src/main/java/java/lang/ProcessManager.java
@@ -31,31 +31,13 @@
 import libcore.io.ErrnoException;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
+import libcore.util.MutableInt;
 import static libcore.io.OsConstants.*;
 
 /**
  * Manages child processes.
  */
 final class ProcessManager {
-
-    /**
-     * constant communicated from native code indicating that a
-     * child died, but it was unable to determine the status
-     */
-    private static final int WAIT_STATUS_UNKNOWN = -1;
-
-    /**
-     * constant communicated from native code indicating that there
-     * are currently no children to wait for
-     */
-    private static final int WAIT_STATUS_NO_CHILDREN = -2;
-
-    /**
-     * constant communicated from native code indicating that a wait()
-     * call returned -1 and set an undocumented (and hence unexpected) errno
-     */
-    private static final int WAIT_STATUS_STRANGE_ERRNO = -3;
-
     /**
      * 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
@@ -71,13 +53,13 @@
 
     private ProcessManager() {
         // Spawn a thread to listen for signals from child processes.
-        Thread processThread = new Thread(ProcessManager.class.getName()) {
+        Thread reaperThread = new Thread(ProcessManager.class.getName()) {
             @Override public void run() {
-                watchChildren(ProcessManager.this);
+                watchChildren();
             }
         };
-        processThread.setDaemon(true);
-        processThread.start();
+        reaperThread.setDaemon(true);
+        reaperThread.start();
     }
 
     /**
@@ -94,10 +76,40 @@
     }
 
     /**
-     * Listens for signals from processes and calls back to
-     * {@link #onExit(int,int)}.
+     * Loops indefinitely and calls ProcessManager.onExit() when children exit.
      */
-    private static native void watchChildren(ProcessManager manager);
+    private void watchChildren() {
+        MutableInt status = new MutableInt(-1);
+        while (true) {
+            try {
+                // Wait for children in our process group.
+                int pid = Libcore.os.waitpid(0, status, 0);
+
+                // Work out what onExit wants to hear.
+                int exitValue;
+                if (WIFEXITED(status.value)) {
+                    exitValue = WEXITSTATUS(status.value);
+                } else if (WIFSIGNALED(status.value)) {
+                    exitValue = WTERMSIG(status.value);
+                } else if (WIFSTOPPED(status.value)) {
+                    exitValue = WSTOPSIG(status.value);
+                } else {
+                    throw new AssertionError("unexpected status from waitpid: " + status.value);
+                }
+
+                onExit(pid, exitValue);
+            } catch (ErrnoException errnoException) {
+                if (errnoException.errno == ECHILD) {
+                    // Expected errno: there are no children to wait for.
+                    // onExit will sleep until it is informed of another child coming to life.
+                    waitForMoreChildren();
+                    continue;
+                } else {
+                    throw errnoException;
+                }
+            }
+        }
+    }
 
     /**
      * Called by {@link #watchChildren()} when a child process exits.
@@ -107,38 +119,10 @@
      */
     private void onExit(int pid, int exitValue) {
         ProcessReference processReference = null;
-
         synchronized (processReferences) {
             cleanUp();
-            if (pid >= 0) {
-                processReference = processReferences.remove(pid);
-            } else if (exitValue == WAIT_STATUS_NO_CHILDREN) {
-                if (processReferences.isEmpty()) {
-                    /*
-                     * There are no eligible children; wait for one to be
-                     * added. The wait() will return due to the
-                     * notifyAll() call below.
-                     */
-                    try {
-                        processReferences.wait();
-                    } catch (InterruptedException ex) {
-                        // This should never happen.
-                        throw new AssertionError("unexpected interrupt");
-                    }
-                } else {
-                    /*
-                     * A new child was spawned just before we entered
-                     * the synchronized block. We can just fall through
-                     * without doing anything special and land back in
-                     * the native wait().
-                     */
-                }
-            } else {
-                // Something weird is happening; abort!
-                throw new AssertionError("unexpected wait() behavior");
-            }
+            processReference = processReferences.remove(pid);
         }
-
         if (processReference != null) {
             ProcessImpl process = processReference.get();
             if (process != null) {
@@ -147,6 +131,28 @@
         }
     }
 
+    private void waitForMoreChildren() {
+        synchronized (processReferences) {
+            if (processReferences.isEmpty()) {
+                // There are no eligible children; wait for one to be added.
+                // This wait will return because of the notifyAll call in exec.
+                try {
+                    processReferences.wait();
+                } catch (InterruptedException ex) {
+                    // This should never happen.
+                    throw new AssertionError("unexpected interrupt");
+                }
+            } else {
+                /*
+                 * A new child was spawned just before we entered
+                 * the synchronized block. We can just fall through
+                 * without doing anything special and land back in
+                 * the native waitpid().
+                 */
+            }
+        }
+    }
+
     /**
      * Executes a native process. Fills in in, out, and err and returns the
      * new process ID upon success.
diff --git a/luni/src/main/java/java/lang/Short.java b/luni/src/main/java/java/lang/Short.java
index 02f7e65..1101805 100644
--- a/luni/src/main/java/java/lang/Short.java
+++ b/luni/src/main/java/java/lang/Short.java
@@ -23,6 +23,7 @@
  * @see java.lang.Number
  * @since 1.1
  */
+@FindBugsSuppressWarnings("DM_NUMBER_CTOR")
 public final class Short extends Number implements Comparable<Short> {
 
     private static final long serialVersionUID = 7515723908773894738L;
@@ -156,8 +157,7 @@
      */
     @Override
     public boolean equals(Object object) {
-        return (object instanceof Short)
-                && (value == ((Short) object).value);
+        return (object instanceof Short) && (((Short) object).value == value);
     }
 
     @Override
@@ -314,7 +314,7 @@
     private static final Short[] SMALL_VALUES = new Short[256];
 
     static {
-        for(int i = -128; i < 128; i++) {
+        for (int i = -128; i < 128; i++) {
             SMALL_VALUES[i + 128] = new Short((short) i);
         }
     }
diff --git a/luni/src/main/java/java/lang/String.java b/luni/src/main/java/java/lang/String.java
index 56b99f4..5991f7a 100644
--- a/luni/src/main/java/java/lang/String.java
+++ b/luni/src/main/java/java/lang/String.java
@@ -136,6 +136,7 @@
      * Converts the byte array to a string using the system's
      * {@link java.nio.charset.Charset#defaultCharset default charset}.
      */
+    @FindBugsSuppressWarnings("DM_DEFAULT_ENCODING")
     public String(byte[] data) {
         this(data, 0, data.length);
     }
@@ -762,6 +763,7 @@
      * @return {@code true} if the specified string is equal to this string,
      *         {@code false} otherwise.
      */
+    @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
     public boolean equalsIgnoreCase(String string) {
         if (string == this) {
             return true;
@@ -803,6 +805,7 @@
      */
     @Deprecated
     public void getBytes(int start, int end, byte[] data, int index) {
+        // Note: last character not copied!
         if (start >= 0 && start <= end && end <= count) {
             end += offset;
             try {
@@ -812,8 +815,9 @@
             } catch (ArrayIndexOutOfBoundsException ignored) {
                 throw failedBoundsCheck(data.length, index, end - start);
             }
+        } else {
+            throw startEndAndLength(start, end);
         }
-        throw startEndAndLength(start, end);
     }
 
     /**
@@ -890,7 +894,7 @@
      *             index}
      */
     public void getChars(int start, int end, char[] buffer, int index) {
-        // NOTE last character not copied!
+        // Note: last character not copied!
         if (start >= 0 && start <= end && end <= count) {
             System.arraycopy(value, start + offset, buffer, index, end - start);
         } else {
@@ -2020,6 +2024,7 @@
      * where the needle is a constant string, may compute the values cache, md2
      * and lastChar, and change the call to the following method.
      */
+    @FindBugsSuppressWarnings("UPM_UNCALLED_PRIVATE_METHOD")
     @SuppressWarnings("unused")
     private static int indexOf(String haystackString, String needleString,
             int cache, int md2, char lastChar) {
diff --git a/luni/src/main/java/org/apache/harmony/luni/util/FloatingPointParser.java b/luni/src/main/java/java/lang/StringToReal.java
similarity index 98%
rename from luni/src/main/java/org/apache/harmony/luni/util/FloatingPointParser.java
rename to luni/src/main/java/java/lang/StringToReal.java
index e08e560..97f6d6b 100644
--- a/luni/src/main/java/org/apache/harmony/luni/util/FloatingPointParser.java
+++ b/luni/src/main/java/java/lang/StringToReal.java
@@ -15,14 +15,14 @@
  *  limitations under the License.
  */
 
-package org.apache.harmony.luni.util;
-
+package java.lang;
 
 /**
  * Used to parse a string and return either a single or double precision
  * floating point number.
+ * @hide
  */
-public final class FloatingPointParser {
+final class StringToReal {
 
     private static final class StringExponentPair {
         String s;
diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java
index 203e5e3..c65ebbb 100644
--- a/luni/src/main/java/java/lang/System.java
+++ b/luni/src/main/java/java/lang/System.java
@@ -272,9 +272,13 @@
         p.put("java.boot.class.path", runtime.bootClassPath());
         p.put("java.class.path", runtime.classPath());
 
-        p.put("java.class.version", "46.0");
+        // None of these four are meaningful on Android, but these keys are guaranteed
+        // to be present for System.getProperty. For java.class.version, we use the maximum
+        // class file version that dx currently supports.
+        p.put("java.class.version", "50.0");
         p.put("java.compiler", "");
         p.put("java.ext.dirs", "");
+        p.put("java.version", "0");
 
         p.put("java.home", getenv("JAVA_HOME", "/system"));
 
@@ -287,7 +291,6 @@
 
         p.put("java.vendor", projectName);
         p.put("java.vendor.url", projectUrl);
-        p.put("java.version", "0");
         p.put("java.vm.name", "Dalvik");
         p.put("java.vm.specification.name", "Dalvik Virtual Machine Specification");
         p.put("java.vm.specification.vendor", projectName);
@@ -358,7 +361,7 @@
      * <tr><td>file.separator</td>     <td>{@link java.io.File#separator}</td>    <td>{@code /}</td></tr>
      *
      * <tr><td>java.class.path</td>    <td>System class path</td>                 <td>{@code .}</td></tr>
-     * <tr><td>java.class.version</td> <td>Maximum supported .class file version</td> <td>{@code 46.0}</td></tr>
+     * <tr><td>java.class.version</td> <td>(Not useful on Android)</td>           <td>{@code 50.0}</td></tr>
      * <tr><td>java.compiler</td>      <td>(Not useful on Android)</td>           <td>Empty</td></tr>
      * <tr><td>java.ext.dirs</td>      <td>(Not useful on Android)</td>           <td>Empty</td></tr>
      * <tr><td>java.home</td>          <td>Location of the VM on the file system</td> <td>{@code /system}</td></tr>
diff --git a/luni/src/main/java/java/lang/ThreadGroup.java b/luni/src/main/java/java/lang/ThreadGroup.java
index d0e593f..e99e99f 100644
--- a/luni/src/main/java/java/lang/ThreadGroup.java
+++ b/luni/src/main/java/java/lang/ThreadGroup.java
@@ -75,7 +75,6 @@
      * @param name the name
      * @see Thread#currentThread
      */
-
     public ThreadGroup(String name) {
         this(Thread.currentThread().getThreadGroup(), name);
     }
diff --git a/luni/src/main/java/java/lang/UnsafeByteSequence.java b/luni/src/main/java/java/lang/UnsafeByteSequence.java
index 66f904b..228bb01 100644
--- a/luni/src/main/java/java/lang/UnsafeByteSequence.java
+++ b/luni/src/main/java/java/lang/UnsafeByteSequence.java
@@ -67,6 +67,7 @@
         bytes[count++] = (byte) b;
     }
 
+    @FindBugsSuppressWarnings("EI_EXPOSE_REP")
     public byte[] toByteArray() {
         if (count == bytes.length) {
             return bytes;
diff --git a/luni/src/main/java/java/lang/reflect/Constructor.java b/luni/src/main/java/java/lang/reflect/Constructor.java
index ceb54c4..b03e28b 100644
--- a/luni/src/main/java/java/lang/reflect/Constructor.java
+++ b/luni/src/main/java/java/lang/reflect/Constructor.java
@@ -344,7 +344,7 @@
         StringBuilder result = new StringBuilder();
 
         result.append('(');
-        for(int i = 0; i < parameterTypes.length; i++) {
+        for (int i = 0; i < parameterTypes.length; i++) {
             result.append(getSignature(parameterTypes[i]));
         }
         result.append(")V");
diff --git a/luni/src/main/java/java/lang/reflect/Method.java b/luni/src/main/java/java/lang/reflect/Method.java
index de1dcbd..8a9c0f1 100644
--- a/luni/src/main/java/java/lang/reflect/Method.java
+++ b/luni/src/main/java/java/lang/reflect/Method.java
@@ -571,7 +571,7 @@
         StringBuilder result = new StringBuilder();
 
         result.append('(');
-        for(int i = 0; i < parameterTypes.length; i++) {
+        for (int i = 0; i < parameterTypes.length; i++) {
             result.append(getSignature(parameterTypes[i]));
         }
         result.append(')');
diff --git a/luni/src/main/java/java/math/BitLevel.java b/luni/src/main/java/java/math/BitLevel.java
index 607358f..91f7a9b 100644
--- a/luni/src/main/java/java/math/BitLevel.java
+++ b/luni/src/main/java/java/math/BitLevel.java
@@ -113,7 +113,7 @@
 
     static void shiftLeftOneBit(int[] result, int[] source, int srcLen) {
         int carry = 0;
-        for(int i = 0; i < srcLen; i++) {
+        for (int i = 0; i < srcLen; i++) {
             int val = source[i];
             result[i] = (val << 1) | carry;
             carry = val >>> 31;
diff --git a/luni/src/main/java/java/math/Conversion.java b/luni/src/main/java/java/math/Conversion.java
index 731f972..585fff4 100644
--- a/luni/src/main/java/java/math/Conversion.java
+++ b/luni/src/main/java/java/math/Conversion.java
@@ -350,7 +350,7 @@
             if (exponent >= 0) {
                 // special case 1
                 int insertPoint = currentChar + (int) exponent ;
-                for(int j=resLengthInChars-1; j>=insertPoint; j--) {
+                for (int j=resLengthInChars-1; j>=insertPoint; j--) {
                     result[j+1] = result[j];
                 }
                 result[++insertPoint]='.';
diff --git a/luni/src/main/java/java/math/Logical.java b/luni/src/main/java/java/math/Logical.java
index 229a5e8..9de0924 100644
--- a/luni/src/main/java/java/math/Logical.java
+++ b/luni/src/main/java/java/math/Logical.java
@@ -207,7 +207,7 @@
             resDigits[i] = longer.digits[i] | shorter.digits[i];
         }
         // shorter has only the remaining virtual sign bits
-        for( ; i < longer.numberLength; i++){
+        for ( ; i < longer.numberLength; i++){
             resDigits[i] = longer.digits[i];
         }
 
@@ -533,7 +533,7 @@
             i = iPos;
             resDigits[i] = -positive.digits[i];
             limit = Math.min(positive.numberLength, iNeg);
-            for(i++; i < limit; i++ ) {
+            for (i++; i < limit; i++ ) {
                 resDigits[i] = ~positive.digits[i];
             }
             if (i != positive.numberLength) {
@@ -558,7 +558,7 @@
             // resDigits[i] = ~(~negative.digits[i] | positive.digits[i] );
             resDigits[i] = negative.digits[i] & ~positive.digits[i];
         }
-        for( ; i < negative.numberLength; i++) {
+        for ( ; i < negative.numberLength; i++) {
             resDigits[i] = negative.digits[i];
         }
 
@@ -611,7 +611,7 @@
         for ( ; i < shorter.numberLength; i++) {
             resDigits[i] = longer.digits[i] ^ shorter.digits[i];
         }
-        for( ; i < longer.numberLength; i++ ){
+        for ( ; i < longer.numberLength; i++ ){
             resDigits[i] = longer.digits[i];
         }
 
diff --git a/luni/src/main/java/java/math/MathContext.java b/luni/src/main/java/java/math/MathContext.java
index 24ccb29..6f3f1ed 100644
--- a/luni/src/main/java/java/math/MathContext.java
+++ b/luni/src/main/java/java/math/MathContext.java
@@ -30,21 +30,21 @@
     private static final long serialVersionUID = 5579720004786848255L;
 
     /**
-     * A {@code MathContext} which corresponds to the IEEE 754r quadruple
+     * A {@code MathContext} which corresponds to the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> quadruple
      * decimal precision format: 34 digit precision and
      * {@link RoundingMode#HALF_EVEN} rounding.
      */
     public static final MathContext DECIMAL128 = new MathContext(34, RoundingMode.HALF_EVEN);
 
     /**
-     * A {@code MathContext} which corresponds to the IEEE 754r single decimal
+     * A {@code MathContext} which corresponds to the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> single decimal
      * precision format: 7 digit precision and {@link RoundingMode#HALF_EVEN}
      * rounding.
      */
     public static final MathContext DECIMAL32 = new MathContext(7, RoundingMode.HALF_EVEN);
 
     /**
-     * A {@code MathContext} which corresponds to the IEEE 754r double decimal
+     * A {@code MathContext} which corresponds to the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> double decimal
      * precision format: 16 digit precision and {@link RoundingMode#HALF_EVEN}
      * rounding.
      */
diff --git a/luni/src/main/java/java/net/BindException.java b/luni/src/main/java/java/net/BindException.java
index 498ead6..4f035cc 100644
--- a/luni/src/main/java/java/net/BindException.java
+++ b/luni/src/main/java/java/net/BindException.java
@@ -26,15 +26,23 @@
     private static final long serialVersionUID = -5945005768251722951L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public BindException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public BindException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with the given detail message and cause.
+     * @hide
+     */
+    public BindException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/ConnectException.java b/luni/src/main/java/java/net/ConnectException.java
index 554316f..45f60be 100644
--- a/luni/src/main/java/java/net/ConnectException.java
+++ b/luni/src/main/java/java/net/ConnectException.java
@@ -26,18 +26,23 @@
     private static final long serialVersionUID = 3831404271622369215L;
 
     /**
-     * This implementation does nothing.
+     * Constructs a new instance.
      */
     public ConnectException() {
     }
 
     /**
-     * This implementation does nothing.
-     *
-     * @param detailMessage
-     *            detail message of the exception.
+     * Constructs a new instance with the given detail message.
      */
     public ConnectException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with the given detail message and cause.
+     * @hide
+     */
+    public ConnectException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java
index 47ac0ff..70e3db9 100644
--- a/luni/src/main/java/java/net/DatagramSocket.java
+++ b/luni/src/main/java/java/net/DatagramSocket.java
@@ -161,8 +161,7 @@
         isConnected = false;
     }
 
-    synchronized void createSocket(int aPort, InetAddress addr)
-            throws SocketException {
+    synchronized void createSocket(int aPort, InetAddress addr) throws SocketException {
         impl = factory != null ? factory.createDatagramSocketImpl()
                 : new PlainDatagramSocketImpl();
         impl.create();
diff --git a/luni/src/main/java/java/net/DatagramSocketImpl.java b/luni/src/main/java/java/net/DatagramSocketImpl.java
index 4485b18..6e706c3 100644
--- a/luni/src/main/java/java/net/DatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/DatagramSocketImpl.java
@@ -19,7 +19,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import libcore.io.IoUtils;
+import libcore.io.IoBridge;
 
 /**
  * The abstract superclass for datagram and multicast socket implementations.
@@ -85,7 +85,7 @@
      * Returns the local address to which the socket is bound.
      */
     InetAddress getLocalAddress() {
-        return IoUtils.getSocketLocalAddress(fd);
+        return IoBridge.getSocketLocalAddress(fd);
     }
 
     /**
diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java
index 8376220..a54e11a 100644
--- a/luni/src/main/java/java/net/HttpURLConnection.java
+++ b/luni/src/main/java/java/net/HttpURLConnection.java
@@ -251,7 +251,7 @@
 
     /**
      * The subset of HTTP methods that the user may select via {@link
-     * libcore.net.http.HttpURLConnectionImpl#setRequestMethod(String)}.
+     * #setRequestMethod(String)}.
      */
     private static final String[] PERMITTED_USER_METHODS = {
             HttpEngine.OPTIONS,
@@ -472,7 +472,11 @@
     public static final int HTTP_SERVER_ERROR = 500;
 
     /**
-     * Numeric status code, 305: Use proxy
+     * Numeric status code, 305: Use proxy.
+     *
+     * <p>Like Firefox and Chrome, this class doesn't honor this response code.
+     * Other implementations respond to this status code by retrying the request
+     * using the HTTP proxy named by the response's Location header field.
      */
     public static final int HTTP_USE_PROXY = 305;
 
diff --git a/luni/src/main/java/java/net/Inet4Address.java b/luni/src/main/java/java/net/Inet4Address.java
index a12eacd..8f8f4f8 100644
--- a/luni/src/main/java/java/net/Inet4Address.java
+++ b/luni/src/main/java/java/net/Inet4Address.java
@@ -20,6 +20,7 @@
 import java.io.ObjectStreamException;
 import java.nio.ByteOrder;
 import libcore.io.Memory;
+import static libcore.io.OsConstants.*;
 
 /**
  * An IPv4 address. See {@link InetAddress}.
@@ -28,8 +29,6 @@
 
     private static final long serialVersionUID = 3286316764910316507L;
 
-    private static final int AF_INET = 2;
-
     /**
      * @hide
      */
diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java
index c253a50..71c8e4a 100644
--- a/luni/src/main/java/java/net/Inet6Address.java
+++ b/luni/src/main/java/java/net/Inet6Address.java
@@ -23,6 +23,7 @@
 import java.io.ObjectStreamField;
 import java.util.Arrays;
 import java.util.Enumeration;
+import static libcore.io.OsConstants.*;
 
 /**
  * An IPv6 address. See {@link InetAddress}.
@@ -31,8 +32,6 @@
 
     private static final long serialVersionUID = 6880410070516793377L;
 
-    private static final int AF_INET6 = 10;
-
     /**
      * @hide
      */
@@ -46,11 +45,11 @@
             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;
+    private boolean scope_id_set;
+    private int scope_id;
 
-    boolean scope_ifname_set;
-    String ifname;
+    private boolean scope_ifname_set;
+    private String ifname;
 
     /**
      * Constructs an {@code InetAddress} representing the {@code address} and
@@ -269,7 +268,7 @@
      */
     public NetworkInterface getScopedInterface() {
         try {
-            return scope_ifname_set ? NetworkInterface.getByName(ifname) : null;
+            return (scope_ifname_set && ifname != null) ? NetworkInterface.getByName(ifname) : null;
         } catch (SocketException ex) {
             return null;
         }
diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java
index 463bff4..a507f82 100644
--- a/luni/src/main/java/java/net/InetAddress.java
+++ b/luni/src/main/java/java/net/InetAddress.java
@@ -31,12 +31,12 @@
 import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.List;
+import libcore.io.ErrnoException;
 import libcore.io.GaiException;
 import libcore.io.Libcore;
-import libcore.io.IoUtils;
+import libcore.io.IoBridge;
 import libcore.io.Memory;
 import libcore.io.StructAddrinfo;
-import org.apache.harmony.luni.platform.Platform;
 import static libcore.io.OsConstants.*;
 
 /**
@@ -143,8 +143,6 @@
     /** Our Java-side DNS cache. */
     private static final AddressCache addressCache = new AddressCache();
 
-    private static final String ERRMSG_CONNECTION_REFUSED = "Connection refused";
-
     private static final long serialVersionUID = 3286316764910316507L;
 
     private transient Object waitReachable = new Object();
@@ -160,16 +158,15 @@
     String hostName;
 
     /**
+     * Used by the DatagramSocket.disconnect implementation.
+     * @hide internal use only
+     */
+    public static final InetAddress UNSPECIFIED = new InetAddress(AF_UNSPEC, null, null);
+
+    /**
      * Constructs an {@code InetAddress}.
      *
-     * Note: this constructor should not be used. Creating an InetAddress
-     * without specifying whether it's an IPv4 or IPv6 address does not make
-     * sense, because subsequent code cannot know which of of the subclasses'
-     * methods need to be called to implement a given InetAddress method. The
-     * proper way to create an InetAddress is to call new Inet4Address or
-     * Inet6Address or to use one of the static methods that return
-     * InetAddresses (e.g., getByAddress). That is why the API does not have
-     * public constructors for any of these classes.
+     * Note: this constructor is for subclasses only.
      */
     InetAddress(int family, byte[] ipaddress, String hostName) {
         this.family = family;
@@ -810,53 +807,39 @@
     }
 
     private boolean isReachableByTCP(InetAddress destination, InetAddress source, int timeout) throws IOException {
-        FileDescriptor fd = IoUtils.socket(true);
+        FileDescriptor fd = IoBridge.socket(true);
         boolean reached = false;
         try {
             if (source != null) {
-                Platform.NETWORK.bind(fd, source, 0);
+                IoBridge.bind(fd, source, 0);
             }
-            IoUtils.connect(fd, destination, 7, timeout);
+            IoBridge.connect(fd, destination, 7, timeout);
             reached = true;
         } catch (IOException e) {
-            if (ERRMSG_CONNECTION_REFUSED.equals(e.getMessage())) {
-                // Connection refused means the IP is reachable
-                reached = true;
+            if (e.getCause() instanceof ErrnoException) {
+                // "Connection refused" means the IP address was reachable.
+                reached = (((ErrnoException) e.getCause()).errno == ECONNREFUSED);
             }
         }
 
-        Platform.NETWORK.close(fd);
+        IoBridge.closeSocket(fd);
 
         return reached;
     }
 
     /**
-     * Equivalent to {@code getByAddress(null, ipAddress, 0)}. Handy for IPv4 addresses with
+     * Equivalent to {@code getByAddress(null, ipAddress)}. Handy for 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.)
      */
     public static InetAddress getByAddress(byte[] ipAddress) throws UnknownHostException {
-        return getByAddressInternal(null, ipAddress, 0);
-    }
-
-    /**
-     * Equivalent to {@code getByAddress(hostName, ipAddress, 0)}. Handy for IPv4 addresses
-     * with an 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.)
-     */
-    public static InetAddress getByAddress(String hostName, byte[] ipAddress) throws UnknownHostException {
-        return getByAddressInternal(hostName, ipAddress, 0);
+        return getByAddress(null, ipAddress, 0);
     }
 
     /**
      * Returns an {@code InetAddress} corresponding to the given network-order
      * bytes {@code ipAddress} and {@code scopeId}.
      *
-     * <p>For an IPv4 address, the byte array must be of length 4, and the scopeId is ignored.
+     * <p>For an IPv4 address, the byte array must be of length 4.
      * For IPv6, the byte array must be of length 16. Any other length will cause an {@code
      * UnknownHostException}.
      *
@@ -868,8 +851,11 @@
      *
      * @throws UnknownHostException if {@code ipAddress} is null or the wrong length.
      */
-    private static InetAddress getByAddressInternal(String hostName, byte[] ipAddress, int scopeId)
-            throws UnknownHostException {
+    public static InetAddress getByAddress(String hostName, byte[] ipAddress) throws UnknownHostException {
+        return getByAddress(hostName, ipAddress, 0);
+    }
+
+    private static InetAddress getByAddress(String hostName, byte[] ipAddress, int scopeId) throws UnknownHostException {
         if (ipAddress == null) {
             throw new UnknownHostException("ipAddress == null");
         }
@@ -913,7 +899,7 @@
 
     private static byte[] ipv4MappedToIPv4(byte[] mappedAddress) {
         byte[] ipv4Address = new byte[4];
-        for(int i = 0; i < 4; i++) {
+        for (int i = 0; i < 4; i++) {
             ipv4Address[i] = mappedAddress[12 + i];
         }
         return ipv4Address;
diff --git a/luni/src/main/java/java/net/InetSocketAddress.java b/luni/src/main/java/java/net/InetSocketAddress.java
index 7b33a44..01b5301 100644
--- a/luni/src/main/java/java/net/InetSocketAddress.java
+++ b/luni/src/main/java/java/net/InetSocketAddress.java
@@ -34,6 +34,16 @@
     private final int port;
 
     /**
+     * @hide internal use only
+     */
+    public InetSocketAddress() {
+        // These will be filled in the native implementation of recvfrom.
+        this.addr = null;
+        this.hostname = null;
+        this.port = -1;
+    }
+
+    /**
      * Creates a socket endpoint with the given port number {@code port} and
      * no specified address. The range for valid port numbers is between 0 and
      * 65535 inclusive.
diff --git a/luni/src/main/java/java/net/MalformedURLException.java b/luni/src/main/java/java/net/MalformedURLException.java
index 77965e8..c6b82c4 100644
--- a/luni/src/main/java/java/net/MalformedURLException.java
+++ b/luni/src/main/java/java/net/MalformedURLException.java
@@ -30,15 +30,23 @@
     private static final long serialVersionUID = -182787522200415866L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public MalformedURLException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public MalformedURLException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public MalformedURLException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java
index 9aa20a0..c6d9af4 100644
--- a/luni/src/main/java/java/net/NetworkInterface.java
+++ b/luni/src/main/java/java/net/NetworkInterface.java
@@ -184,6 +184,7 @@
         }
     }
 
+    @FindBugsSuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
     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()) {
@@ -261,6 +262,7 @@
         return Collections.enumeration(getNetworkInterfacesList());
     }
 
+    @FindBugsSuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
     private static List<NetworkInterface> getNetworkInterfacesList() throws SocketException {
         String[] interfaceNames = new File("/sys/class/net").list();
         NetworkInterface[] interfaces = new NetworkInterface[interfaceNames.length];
diff --git a/luni/src/main/java/java/net/NoRouteToHostException.java b/luni/src/main/java/java/net/NoRouteToHostException.java
index 3d46821..8d6d941 100644
--- a/luni/src/main/java/java/net/NoRouteToHostException.java
+++ b/luni/src/main/java/java/net/NoRouteToHostException.java
@@ -27,15 +27,23 @@
     private static final long serialVersionUID = -1897550894873493790L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public NoRouteToHostException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public NoRouteToHostException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public NoRouteToHostException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
index 0eb413f..f27db949 100644
--- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -28,10 +28,11 @@
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.UnknownHostException;
-import libcore.io.IoUtils;
+import libcore.io.IoBridge;
+import libcore.io.Libcore;
 import libcore.io.StructGroupReq;
 import libcore.util.EmptyArray;
-import org.apache.harmony.luni.platform.Platform;
+import static libcore.io.OsConstants.*;
 
 /**
  * @hide used in java.nio.
@@ -62,15 +63,13 @@
         fd = new FileDescriptor();
     }
 
-    @Override
-    public void bind(int port, InetAddress addr) throws SocketException {
-        Platform.NETWORK.bind(fd, addr, port);
+    @Override public void bind(int port, InetAddress address) throws SocketException {
+        IoBridge.bind(fd, address, port);
         if (port != 0) {
             localPort = port;
         } else {
-            localPort = IoUtils.getSocketLocalPort(fd);
+            localPort = IoBridge.getSocketLocalPort(fd);
         }
-
         try {
             setOption(SocketOptions.SO_BROADCAST, Boolean.TRUE);
         } catch (IOException ignored) {
@@ -81,14 +80,14 @@
     public synchronized void close() {
         guard.close();
         try {
-            Platform.NETWORK.close(fd);
+            IoBridge.closeSocket(fd);
         } catch (IOException ignored) {
         }
     }
 
     @Override
     public void create() throws SocketException {
-        this.fd = IoUtils.socket(false);
+        this.fd = IoBridge.socket(false);
     }
 
     @Override protected void finalize() throws Throwable {
@@ -103,12 +102,12 @@
     }
 
     @Override public Object getOption(int option) throws SocketException {
-        return IoUtils.getSocketOption(fd, option);
+        return IoBridge.getSocketOption(fd, option);
     }
 
     @Override
     public int getTimeToLive() throws IOException {
-        return (Integer) getOption(IoUtils.JAVA_IP_MULTICAST_TTL);
+        return (Integer) getOption(IoBridge.JAVA_IP_MULTICAST_TTL);
     }
 
     @Override
@@ -123,27 +122,27 @@
 
     @Override
     public void join(InetAddress addr) throws IOException {
-        setOption(IoUtils.JAVA_MCAST_JOIN_GROUP, makeGroupReq(addr, null));
+        setOption(IoBridge.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.JAVA_MCAST_JOIN_GROUP, makeGroupReq(groupAddr, netInterface));
+            setOption(IoBridge.JAVA_MCAST_JOIN_GROUP, makeGroupReq(groupAddr, netInterface));
         }
     }
 
     @Override
     public void leave(InetAddress addr) throws IOException {
-        setOption(IoUtils.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(addr, null));
+        setOption(IoBridge.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.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(groupAddr, netInterface));
+            setOption(IoBridge.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(groupAddr, netInterface));
         }
     }
 
@@ -152,14 +151,13 @@
         // We don't actually want the data: we just want the DatagramPacket's filled-in address.
         DatagramPacket packet = new DatagramPacket(EmptyArray.BYTE, 0);
         int result = peekData(packet);
-        // TODO: maybe recv should do this?
+        // Note: evil side-effect on InetAddress! This method should have returned InetSocketAddress!
         sender.ipaddress = packet.getAddress().getAddress();
         return result;
     }
 
-    private void doRecv(DatagramPacket pack, boolean peek) throws IOException {
-        Platform.NETWORK.recv(fd, pack, pack.getData(), pack.getOffset(), pack.getLength(), peek,
-                isNativeConnected);
+    private void doRecv(DatagramPacket pack, int flags) throws IOException {
+        IoBridge.recvfrom(false, fd, pack.getData(), pack.getOffset(), pack.getLength(), flags, pack, isNativeConnected);
         if (isNativeConnected) {
             updatePacketRecvAddress(pack);
         }
@@ -167,12 +165,12 @@
 
     @Override
     public void receive(DatagramPacket pack) throws IOException {
-        doRecv(pack, false);
+        doRecv(pack, 0);
     }
 
     @Override
     public int peekData(DatagramPacket pack) throws IOException {
-        doRecv(pack, true);
+        doRecv(pack, MSG_PEEK);
         return pack.getPort();
     }
 
@@ -180,17 +178,16 @@
     public void send(DatagramPacket packet) throws IOException {
         int port = isNativeConnected ? 0 : packet.getPort();
         InetAddress address = isNativeConnected ? null : packet.getAddress();
-        Platform.NETWORK.send(fd, packet.getData(), packet.getOffset(), packet.getLength(),
-                              port, address);
+        IoBridge.sendto(fd, packet.getData(), packet.getOffset(), packet.getLength(), 0, address, port);
     }
 
     public void setOption(int option, Object value) throws SocketException {
-        IoUtils.setSocketOption(fd, option, value);
+        IoBridge.setSocketOption(fd, option, value);
     }
 
     @Override
     public void setTimeToLive(int ttl) throws IOException {
-        setOption(IoUtils.JAVA_IP_MULTICAST_TTL, Integer.valueOf(ttl));
+        setOption(IoBridge.JAVA_IP_MULTICAST_TTL, Integer.valueOf(ttl));
     }
 
     @Override
@@ -200,7 +197,7 @@
 
     @Override
     public void connect(InetAddress inetAddr, int port) throws SocketException {
-        IoUtils.connect(fd, inetAddr, port); // Throws on failure.
+        IoBridge.connect(fd, inetAddr, port); // Throws on failure.
         try {
             connectedAddress = InetAddress.getByAddress(inetAddr.getAddress());
         } catch (UnknownHostException e) {
@@ -214,10 +211,7 @@
 
     @Override
     public void disconnect() {
-        try {
-            Platform.NETWORK.disconnectDatagram(fd);
-        } catch (Exception ignored) {
-        }
+        Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0);
         connectedPort = -1;
         connectedAddress = null;
         isNativeConnected = false;
diff --git a/luni/src/main/java/java/net/PlainServerSocketImpl.java b/luni/src/main/java/java/net/PlainServerSocketImpl.java
index cb45010..4fc1a9d 100644
--- a/luni/src/main/java/java/net/PlainServerSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainServerSocketImpl.java
@@ -26,8 +26,8 @@
  * @hide used in java.nio.
  */
 public class PlainServerSocketImpl extends PlainSocketImpl {
-
-    public PlainServerSocketImpl() {}
+    public PlainServerSocketImpl() {
+    }
 
     public PlainServerSocketImpl(FileDescriptor fd) {
         super(fd);
diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java
index ccdbb5b..20dfcc2 100644
--- a/luni/src/main/java/java/net/PlainSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainSocketImpl.java
@@ -34,11 +34,10 @@
 import java.nio.ByteOrder;
 import java.util.Arrays;
 import libcore.io.ErrnoException;
-import libcore.io.IoUtils;
+import libcore.io.IoBridge;
 import libcore.io.Libcore;
 import libcore.io.Memory;
 import libcore.io.Streams;
-import org.apache.harmony.luni.platform.Platform;
 import static libcore.io.OsConstants.*;
 
 /**
@@ -93,7 +92,28 @@
             ((PlainSocketImpl) newImpl).socksAccept();
             return;
         }
-        Platform.NETWORK.accept(fd, newImpl, newImpl.getFileDescriptor());
+
+        try {
+            InetSocketAddress peerAddress = new InetSocketAddress();
+            FileDescriptor clientFd = Libcore.os.accept(fd, peerAddress);
+
+            // TODO: we can't just set newImpl.fd to clientFd because a nio SocketChannel may
+            // be sharing the FileDescriptor. http://b//4452981.
+            newImpl.fd.setInt$(clientFd.getInt$());
+
+            newImpl.address = peerAddress.getAddress();
+            newImpl.port = peerAddress.getPort();
+        } catch (ErrnoException errnoException) {
+            if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+                throw new SocketTimeoutException(errnoException);
+            }
+            throw errnoException.rethrowAsSocketException();
+        }
+
+        // Reset the client's inherited read timeout to the Java-specified default of 0.
+        newImpl.setOption(SocketOptions.SO_TIMEOUT, Integer.valueOf(0));
+
+        newImpl.localport = IoBridge.getSocketLocalPort(newImpl.fd);
     }
 
     private boolean usingSocks() {
@@ -123,24 +143,23 @@
         if (shutdownInput) {
             return 0;
         }
-        return IoUtils.available(fd);
+        return IoBridge.available(fd);
     }
 
-    @Override
-    protected void bind(InetAddress address, int port) throws IOException {
-        Platform.NETWORK.bind(fd, address, port);
+    @Override protected void bind(InetAddress address, int port) throws IOException {
+        IoBridge.bind(fd, address, port);
         this.address = address;
         if (port != 0) {
             this.localport = port;
         } else {
-            this.localport = IoUtils.getSocketLocalPort(fd);
+            this.localport = IoBridge.getSocketLocalPort(fd);
         }
     }
 
     @Override
     protected synchronized void close() throws IOException {
         guard.close();
-        Platform.NETWORK.close(fd);
+        IoBridge.closeSocket(fd);
     }
 
     @Override
@@ -167,14 +186,10 @@
      */
     private void connect(InetAddress anAddr, int aPort, int timeout) throws IOException {
         InetAddress normalAddr = anAddr.isAnyLocalAddress() ? InetAddress.getLocalHost() : anAddr;
-        try {
-            if (streaming && usingSocks()) {
-                socksConnect(anAddr, aPort, 0);
-            } else {
-                IoUtils.connect(fd, normalAddr, aPort, timeout);
-            }
-        } catch (ConnectException e) {
-            throw new ConnectException(anAddr + " (port " + aPort + "): " + e.getMessage());
+        if (streaming && usingSocks()) {
+            socksConnect(anAddr, aPort, 0);
+        } else {
+            IoBridge.connect(fd, normalAddr, aPort, timeout);
         }
         super.address = normalAddr;
         super.port = aPort;
@@ -183,7 +198,7 @@
     @Override
     protected void create(boolean streaming) throws IOException {
         this.streaming = streaming;
-        this.fd = IoUtils.socket(streaming);
+        this.fd = IoBridge.socket(streaming);
     }
 
     @Override protected void finalize() throws Throwable {
@@ -227,7 +242,7 @@
     }
 
     @Override public Object getOption(int option) throws SocketException {
-        return IoUtils.getSocketOption(fd, option);
+        return IoBridge.getSocketOption(fd, option);
     }
 
     @Override protected synchronized OutputStream getOutputStream() throws IOException {
@@ -271,7 +286,7 @@
 
     @Override
     public void setOption(int option, Object value) throws SocketException {
-        IoUtils.setSocketOption(fd, option, value);
+        IoBridge.setSocketOption(fd, option, value);
     }
 
     /**
@@ -306,7 +321,7 @@
      */
     private void socksConnect(InetAddress applicationServerAddress, int applicationServerPort, int timeout) throws IOException {
         try {
-            IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
+            IoBridge.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
         } catch (Exception e) {
             throw new SocketException("SOCKS connection failed", e);
         }
@@ -371,7 +386,7 @@
      */
     private void socksBind() throws IOException {
         try {
-            IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort());
+            IoBridge.connect(fd, socksGetServerAddress(), socksGetServerPort());
         } catch (Exception e) {
             throw new IOException("Unable to connect to SOCKS server", e);
         }
@@ -451,7 +466,12 @@
 
     @Override
     protected void sendUrgentData(int value) throws IOException {
-        Platform.NETWORK.sendUrgentData(fd, (byte) value);
+        try {
+            byte[] buffer = new byte[] { (byte) value };
+            Libcore.os.sendto(fd, buffer, 0, 1, MSG_OOB, null, 0);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
     }
 
     /**
@@ -465,16 +485,16 @@
         if (shutdownInput) {
             return -1;
         }
-        int read = Platform.NETWORK.read(fd, buffer, offset, byteCount);
+        int readCount = IoBridge.recvfrom(true, fd, buffer, offset, byteCount, 0, null, false);
         // Return of zero bytes for a blocking socket means a timeout occurred
-        if (read == 0) {
+        if (readCount == 0) {
             throw new SocketTimeoutException();
         }
         // Return of -1 indicates the peer was closed
-        if (read == -1) {
+        if (readCount == -1) {
             shutdownInput = true;
         }
-        return read;
+        return readCount;
     }
 
     /**
@@ -484,7 +504,7 @@
         Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);
         if (streaming) {
             while (byteCount > 0) {
-                int bytesWritten = Platform.NETWORK.write(fd, buffer, offset, byteCount);
+                int bytesWritten = IoBridge.sendto(fd, buffer, offset, byteCount, 0, null, 0);
                 byteCount -= bytesWritten;
                 offset += bytesWritten;
             }
@@ -492,7 +512,7 @@
             // Unlike writes to a streaming socket, writes to a datagram
             // socket are all-or-nothing, so we don't need a loop here.
             // http://code.google.com/p/android/issues/detail?id=15304
-            Platform.NETWORK.send(fd, buffer, offset, byteCount, port, address);
+            IoBridge.sendto(fd, buffer, offset, byteCount, 0, address, port);
         }
     }
 }
diff --git a/luni/src/main/java/java/net/PortUnreachableException.java b/luni/src/main/java/java/net/PortUnreachableException.java
index b8e8fa9..0cb4114 100644
--- a/luni/src/main/java/java/net/PortUnreachableException.java
+++ b/luni/src/main/java/java/net/PortUnreachableException.java
@@ -26,15 +26,23 @@
     private static final long serialVersionUID = 8462541992376507323L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public PortUnreachableException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public PortUnreachableException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public PortUnreachableException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/ProtocolException.java b/luni/src/main/java/java/net/ProtocolException.java
index 979f7d9..e0bec50 100644
--- a/luni/src/main/java/java/net/ProtocolException.java
+++ b/luni/src/main/java/java/net/ProtocolException.java
@@ -27,15 +27,23 @@
     private static final long serialVersionUID = -6098449442062388080L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public ProtocolException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public ProtocolException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public ProtocolException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/ServerSocket.java b/luni/src/main/java/java/net/ServerSocket.java
index 663cddf..ba8d6269 100644
--- a/luni/src/main/java/java/net/ServerSocket.java
+++ b/luni/src/main/java/java/net/ServerSocket.java
@@ -27,99 +27,79 @@
  * implemented by an internal {@code SocketImpl} instance.
  */
 public class ServerSocket {
+    /**
+     * The RI specifies that where the caller doesn't give an explicit backlog,
+     * the default is 50. The OS disagrees, so we need to explicitly call listen(2).
+     */
+    private static final int DEFAULT_BACKLOG = 50;
 
-    SocketImpl impl;
+    private final SocketImpl impl;
+
+    /**
+     * @hide internal use only
+     */
+    public SocketImpl getImpl$() {
+        return impl;
+    }
 
     static SocketImplFactory factory;
 
-    private volatile boolean isCreated;
-
     private boolean isBound;
 
     private boolean isClosed;
 
     /**
-     * Constructs a new {@code ServerSocket} instance which is not bound to any
-     * port. The default number of pending connections may be backlogged.
+     * Constructs a new unbound {@code ServerSocket}.
      *
-     * @throws IOException
-     *             if an error occurs while creating the server socket.
+     * @throws IOException if an error occurs while creating the socket.
      */
     public ServerSocket() throws IOException {
-        impl = factory != null ? factory.createSocketImpl()
+        this.impl = factory != null ? factory.createSocketImpl()
                 : new PlainServerSocketImpl();
+        impl.create(true);
     }
 
     /**
-     * Unspecified constructor needed by ServerSocketChannelImpl.ServerSocketAdapter.
+     * Constructs a new {@code ServerSocket} instance bound to the given {@code port}.
+     * The backlog is set to 50. If {@code port == 0}, a port will be assigned by the OS.
      *
-     * @hide
+     * @throws IOException if an error occurs while creating the socket.
      */
-    protected ServerSocket(SocketImpl impl) {
-        this.impl = impl;
+    public ServerSocket(int port) throws IOException {
+        this(port, DEFAULT_BACKLOG, Inet4Address.ANY);
     }
 
     /**
-     * Constructs a new {@code ServerSocket} instance bound to the nominated
-     * port on the localhost. The default number of pending connections may be
-     * backlogged. If {@code aport} is 0 a free port is assigned to the socket.
+     * Constructs a new {@code ServerSocket} instance bound to the given {@code port}.
+     * The backlog is set to {@code backlog}.
+     * If {@code port == 0}, a port will be assigned by the OS.
      *
-     * @param aport
-     *            the port number to listen for connection requests on.
-     * @throws IOException
-     *             if an error occurs while creating the server socket.
+     * @throws IOException if an error occurs while creating the socket.
      */
-    public ServerSocket(int aport) throws IOException {
-        this(aport, defaultBacklog(), Inet4Address.ANY);
+    public ServerSocket(int port, int backlog) throws IOException {
+        this(port, backlog, Inet4Address.ANY);
     }
 
     /**
-     * Constructs a new {@code ServerSocket} instance bound to the nominated
-     * port on the localhost. The number of pending connections that may be
-     * backlogged is specified by {@code backlog}. If {@code aport} is 0 a free
-     * port is assigned to the socket.
+     * Constructs a new {@code ServerSocket} instance bound to the given {@code localAddress}
+     * and {@code port}. The backlog is set to {@code backlog}.
+     * If {@code localAddress == null}, the ANY address is used.
+     * If {@code port == 0}, a port will be assigned by the OS.
      *
-     * @param aport
-     *            the port number to listen for connection requests on.
-     * @param backlog
-     *            the number of pending connection requests, before requests
-     *            will be rejected.
-     * @throws IOException
-     *             if an error occurs while creating the server socket.
+     * @throws IOException if an error occurs while creating the socket.
      */
-    public ServerSocket(int aport, int backlog) throws IOException {
-        this(aport, backlog, Inet4Address.ANY);
-    }
-
-    /**
-     * Constructs a new {@code ServerSocket} instance bound to the nominated
-     * local host address and port. The number of pending connections that may
-     * be backlogged is specified by {@code backlog}. If {@code aport} is 0 a
-     * free port is assigned to the socket.
-     *
-     * @param aport
-     *            the port number to listen for connection requests on.
-     * @param localAddr
-     *            the local machine address to bind on.
-     * @param backlog
-     *            the number of pending connection requests, before requests
-     *            will be rejected.
-     * @throws IOException
-     *             if an error occurs while creating the server socket.
-     */
-    public ServerSocket(int aport, int backlog, InetAddress localAddr) throws IOException {
-        checkListen(aport);
-        impl = factory != null ? factory.createSocketImpl()
+    public ServerSocket(int port, int backlog, InetAddress localAddress) throws IOException {
+        checkListen(port);
+        this.impl = factory != null ? factory.createSocketImpl()
                 : new PlainServerSocketImpl();
-        InetAddress addr = localAddr == null ? Inet4Address.ANY : localAddr;
+        InetAddress addr = (localAddress == null) ? Inet4Address.ANY : localAddress;
 
         synchronized (this) {
             impl.create(true);
-            isCreated = true;
             try {
-                impl.bind(addr, aport);
+                impl.bind(addr, port);
                 isBound = true;
-                impl.listen(backlog > 0 ? backlog : defaultBacklog());
+                impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG);
             } catch (IOException e) {
                 close();
                 throw e;
@@ -137,7 +117,7 @@
      *             if an error occurs while accepting a new connection.
      */
     public Socket accept() throws IOException {
-        checkClosedAndCreate(false);
+        checkOpen();
         if (!isBound()) {
             throw new SocketException("Socket is not bound");
         }
@@ -171,17 +151,6 @@
     }
 
     /**
-     * Returns the default number of pending connections on a server socket. If
-     * the backlog value maximum is reached, any subsequent incoming request is
-     * rejected.
-     *
-     * @return int the default number of pending connection requests
-     */
-    static int defaultBacklog() {
-        return 50;
-    }
-
-    /**
      * Gets the local IP address of this server socket or {@code null} if the
      * socket is unbound. This is useful for multihomed hosts.
      *
@@ -214,20 +183,7 @@
      *             if the option cannot be retrieved.
      */
     public synchronized int getSoTimeout() throws IOException {
-        if (!isCreated) {
-            synchronized (this) {
-                if (!isCreated) {
-                    try {
-                        impl.create(true);
-                    } catch (SocketException e) {
-                        throw e;
-                    } catch (IOException e) {
-                        throw new SocketException(e.toString());
-                    }
-                    isCreated = true;
-                }
-            }
-        }
+        checkOpen();
         return ((Integer) impl.getOption(SocketOptions.SO_TIMEOUT)).intValue();
     }
 
@@ -259,8 +215,7 @@
      * @throws IOException
      *             if the factory could not be set or is already set.
      */
-    public static synchronized void setSocketFactory(SocketImplFactory aFactory)
-            throws IOException {
+    public static synchronized void setSocketFactory(SocketImplFactory aFactory) throws IOException {
         if (factory != null) {
             throw new SocketException("Factory already set");
         }
@@ -279,7 +234,7 @@
      *             if an error occurs while setting the option.
      */
     public synchronized void setSoTimeout(int timeout) throws SocketException {
-        checkClosedAndCreate(true);
+        checkOpen();
         if (timeout < 0) {
             throw new IllegalArgumentException("timeout < 0");
         }
@@ -323,7 +278,7 @@
      *             binding.
      */
     public void bind(SocketAddress localAddr) throws IOException {
-        bind(localAddr, defaultBacklog());
+        bind(localAddr, DEFAULT_BACKLOG);
     }
 
     /**
@@ -340,7 +295,7 @@
      *     during binding.
      */
     public void bind(SocketAddress localAddr, int backlog) throws IOException {
-        checkClosedAndCreate(true);
+        checkOpen();
         if (isBound()) {
             throw new BindException("Socket is already bound");
         }
@@ -362,7 +317,7 @@
             try {
                 impl.bind(addr, port);
                 isBound = true;
-                impl.listen(backlog > 0 ? backlog : defaultBacklog());
+                impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG);
             } catch (IOException e) {
                 close();
                 throw e;
@@ -402,31 +357,10 @@
         return isClosed;
     }
 
-    /**
-     * Checks whether the socket is closed, and throws an exception.
-     */
-    private void checkClosedAndCreate(boolean create) throws SocketException {
+    private void checkOpen() throws SocketException {
         if (isClosed()) {
             throw new SocketException("Socket is closed");
         }
-
-        if (!create || isCreated) {
-            return;
-        }
-
-        synchronized (this) {
-            if (isCreated) {
-                return;
-            }
-            try {
-                impl.create(true);
-            } catch (SocketException e) {
-                throw e;
-            } catch (IOException e) {
-                throw new SocketException(e.toString());
-            }
-            isCreated = true;
-        }
     }
 
     /**
@@ -438,7 +372,7 @@
      *             if an error occurs while setting the option value.
      */
     public void setReuseAddress(boolean reuse) throws SocketException {
-        checkClosedAndCreate(true);
+        checkOpen();
         impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(reuse));
     }
 
@@ -450,16 +384,15 @@
      *             if an error occurs while reading the option value.
      */
     public boolean getReuseAddress() throws SocketException {
-        checkClosedAndCreate(true);
-        return ((Boolean) impl.getOption(SocketOptions.SO_REUSEADDR))
-                .booleanValue();
+        checkOpen();
+        return ((Boolean) impl.getOption(SocketOptions.SO_REUSEADDR)).booleanValue();
     }
 
     /**
      * Sets this socket's {@link SocketOptions#SO_SNDBUF receive buffer size}.
      */
     public void setReceiveBufferSize(int size) throws SocketException {
-        checkClosedAndCreate(true);
+        checkOpen();
         if (size < 1) {
             throw new IllegalArgumentException("size < 1");
         }
@@ -470,7 +403,7 @@
      * Returns this socket's {@link SocketOptions#SO_RCVBUF receive buffer size}.
      */
     public int getReceiveBufferSize() throws SocketException {
-        checkClosedAndCreate(true);
+        checkOpen();
         return ((Integer) impl.getOption(SocketOptions.SO_RCVBUF)).intValue();
     }
 
@@ -498,8 +431,7 @@
      * @param bandwidth
      *            the value representing the importance of high bandwidth.
      */
-    public void setPerformancePreferences(int connectionTime, int latency,
-            int bandwidth) {
+    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
         // Our socket implementation only provide one protocol: TCP/IP, so
         // we do nothing for this method
     }
diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java
index 06e1ab3..f7788b5 100644
--- a/luni/src/main/java/java/net/Socket.java
+++ b/luni/src/main/java/java/net/Socket.java
@@ -22,7 +22,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.channels.SocketChannel;
-import libcore.io.IoUtils;
+import libcore.io.IoBridge;
 
 /**
  * Provides a client-side TCP socket.
@@ -947,7 +947,7 @@
     }
 
     private void cacheLocalAddress() {
-        this.localAddress = IoUtils.getSocketLocalAddress(impl.fd);
+        this.localAddress = IoBridge.getSocketLocalAddress(impl.fd);
     }
 
     /**
diff --git a/luni/src/main/java/java/net/SocketException.java b/luni/src/main/java/java/net/SocketException.java
index b7366b3..23e5c01 100644
--- a/luni/src/main/java/java/net/SocketException.java
+++ b/luni/src/main/java/java/net/SocketException.java
@@ -28,19 +28,19 @@
     private static final long serialVersionUID = -5935874303556886934L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public SocketException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public SocketException(String detailMessage) {
         super(detailMessage);
     }
     /**
-     * Constructs a new instance with the current stack trace and given cause.
+     * Constructs a new instance with the given cause.
      * @hide internal use only
      */
     public SocketException(Throwable cause) {
@@ -48,7 +48,7 @@
     }
 
     /**
-     * Constructs a new instance with the current stack trace, given detail message and given cause.
+     * Constructs a new instance with given detail message and cause.
      * @hide internal use only
      */
     public SocketException(String detailMessage, Throwable cause) {
diff --git a/luni/src/main/java/java/net/SocketImpl.java b/luni/src/main/java/java/net/SocketImpl.java
index 3d0507c..92de9cf 100644
--- a/luni/src/main/java/java/net/SocketImpl.java
+++ b/luni/src/main/java/java/net/SocketImpl.java
@@ -142,6 +142,13 @@
     }
 
     /**
+     * @hide used by java.nio
+     */
+    public FileDescriptor getFD$() {
+        return fd;
+    }
+
+    /**
      * Gets the remote address this socket is connected to.
      *
      * @return the remote address of this socket.
diff --git a/luni/src/main/java/java/net/SocketTimeoutException.java b/luni/src/main/java/java/net/SocketTimeoutException.java
index 62e8731..8f8ef82 100644
--- a/luni/src/main/java/java/net/SocketTimeoutException.java
+++ b/luni/src/main/java/java/net/SocketTimeoutException.java
@@ -28,15 +28,31 @@
     private static final long serialVersionUID = -8846654841826352300L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public SocketTimeoutException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public SocketTimeoutException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given cause.
+     * @hide internal use only
+     */
+    public SocketTimeoutException(Throwable cause) {
+        super(null, cause);
+    }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public SocketTimeoutException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java
index 8779cca..4e68b39 100644
--- a/luni/src/main/java/java/net/URL.java
+++ b/luni/src/main/java/java/net/URL.java
@@ -19,9 +19,12 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.util.Hashtable;
 import java.util.Locale;
+import java.util.jar.JarFile;
 import libcore.net.http.HttpHandler;
 import libcore.net.http.HttpsHandler;
 import libcore.net.url.FileHandler;
@@ -29,175 +32,126 @@
 import libcore.net.url.JarHandler;
 
 /**
- * A URL instance specifies the location of a resource on the internet as
- * 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.
+ * A Uniform Resource Locator that identifies the location of an Internet
+ * resource as specified by <a href="http://www.ietf.org/rfc/rfc1738.txt">RFC
+ * 1738</a>.
+ *
+ * <h3>Parts of a URL</h3>
+ * A URL is composed of many parts. This class can both parse URL strings into
+ * parts and compose URL strings from parts. For example, consider the parts of
+ * this URL:
+ * {@code http://username:password@host:8080/directory/file?query#ref}:
+ * <table>
+ * <tr><th>Component</th><th>Example value</th><th>Also known as</th></tr>
+ * <tr><td>{@link #getProtocol() Protocol}</td><td>{@code http}</td><td>scheme</td></tr>
+ * <tr><td>{@link #getAuthority() Authority}</td><td>{@code username:password@host:8080}</td><td></td></tr>
+ * <tr><td>{@link #getUserInfo() User Info}</td><td>{@code username:password}</td><td></td></tr>
+ * <tr><td>{@link #getHost() Host}</td><td>{@code host}</td><td></td></tr>
+ * <tr><td>{@link #getPort() Port}</td><td>{@code 8080}</td><td></td></tr>
+ * <tr><td>{@link #getFile() File}</td><td>{@code /directory/file?query}</td><td></td></tr>
+ * <tr><td>{@link #getPath() Path}</td><td>{@code /directory/file}</td><td></td></tr>
+ * <tr><td>{@link #getQuery() Query}</td><td>{@code query}</td><td></td></tr>
+ * <tr><td>{@link #getRef() Ref}</td><td>{@code ref}</td><td>fragment</td></tr>
+ * </table>
+ *
+ * <h3>Supported Protocols</h3>
+ * This class may be used to construct URLs with the following protocols:
+ * <ul>
+ * <li><strong>file</strong>: read files from the local filesystem.
+ * <li><strong>ftp</strong>: <a href="http://www.ietf.org/rfc/rfc959.txt">File
+ *     Transfer Protocol</a>
+ * <li><strong>http</strong>: <a href="http://www.ietf.org/rfc/rfc2616.txt">Hypertext
+ *     Transfer Protocol</a>
+ * <li><strong>https</strong>: <a href="http://www.ietf.org/rfc/rfc2818.txt">HTTP
+ *     over TLS</a>
+ * <li><strong>jar</strong>: read {@link JarFile Jar files} from the
+ *     filesystem</li>
+ * </ul>
+ * In general, attempts to create URLs with any other protocol will fail with a
+ * {@link MalformedURLException}. Applications may install handlers for other
+ * schemes using {@link #setURLStreamHandlerFactory} or with the {@code
+ * java.protocol.handler.pkgs} system property.
+ *
+ * <p>The {@link URI} class can be used to manipulate URLs of any protocol.
  */
-public final class URL implements java.io.Serializable {
+public final class URL implements Serializable {
     private static final long serialVersionUID = -7627629688361524110L;
 
-    private static final NetPermission specifyStreamHandlerPermission = new NetPermission(
-            "specifyStreamHandler");
+    private static URLStreamHandlerFactory streamHandlerFactory;
 
-    private int hashCode;
-
-    /**
-     * The receiver's filename.
-     *
-     * @serial the file of this URL
-     *
-     */
-    private String file;
-
-    /**
-     * The receiver's protocol identifier.
-     *
-     * @serial the protocol of this URL (http, file)
-     *
-     */
-    private String protocol = null;
-
-    /**
-     * The receiver's host name.
-     *
-     * @serial the host of this URL
-     *
-     */
-    private String host;
-
-    /**
-     * The receiver's port number.
-     *
-     * @serial the port of this URL
-     *
-     */
-    private int port = -1;
-
-    /**
-     * The receiver's authority.
-     *
-     * @serial the authority of this URL
-     *
-     */
-    private String authority = null;
-
-    /**
-     * The receiver's userInfo.
-     */
-    private transient String userInfo = null;
-
-    /**
-     * The receiver's path.
-     */
-    private transient String path = null;
-
-    /**
-     * The receiver's query.
-     */
-    private transient String query = null;
-
-    /**
-     * The receiver's reference.
-     *
-     * @serial the reference of this URL
-     *
-     */
-    private String ref = null;
-
-    /**
-     * Cache for storing protocol handler
-     */
+    /** Cache of protocols to their handlers */
     private static final Hashtable<String, URLStreamHandler> streamHandlers
             = new Hashtable<String, URLStreamHandler>();
 
-    /**
-     * The URL Stream (protocol) Handler
-     */
-    transient URLStreamHandler strmHandler;
+    private String protocol;
+    private String authority;
+    private String host;
+    private int port = -1;
+    private String file;
+    private String ref;
+
+    private transient String userInfo;
+    private transient String path;
+    private transient String query;
+
+    transient URLStreamHandler streamHandler;
 
     /**
-     * The factory responsible for producing URL Stream (protocol) Handler
+     * The cached hash code, or 0 if it hasn't been computed yet. Unlike the RI,
+     * this implementation's hashCode is transient because the hash code is
+     * unspecified and may vary between VMs or versions.
      */
-    private static URLStreamHandlerFactory streamHandlerFactory;
+    private transient int hashCode;
 
     /**
-     * Sets the {@code URLStreamHandlerFactory} which creates protocol specific
-     * stream handlers. This method can be invoked only once during an
-     * application's lifetime. If the {@code URLStreamHandlerFactory} is already
-     * set an {@link Error} will be thrown.
-     * <p>
-     * A security check is performed to verify whether the current policy allows
-     * to set the stream handler factory.
+     * Sets the stream handler factory for this VM.
      *
-     * @param streamFactory
-     *            the factory to be used for creating stream protocol handlers.
+     * @throws Error if a URLStreamHandlerFactory has already been installed
+     *     for the current VM.
      */
-    public static synchronized void setURLStreamHandlerFactory(
-            URLStreamHandlerFactory streamFactory) {
+    public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory factory) {
         if (streamHandlerFactory != null) {
             throw new Error("Factory already set");
         }
         streamHandlers.clear();
-        streamHandlerFactory = streamFactory;
+        streamHandlerFactory = factory;
     }
 
     /**
-     * Creates a new URL instance by parsing the string {@code spec}.
+     * Creates a new URL instance by parsing {@code spec}.
      *
-     * @param spec
-     *            the URL string representation which has to be parsed.
-     * @throws MalformedURLException
-     *             if the given string {@code spec} could not be parsed as a
-     *             URL.
+     * @throws MalformedURLException if {@code spec} could not be parsed as a
+     *     URL.
      */
     public URL(String spec) throws MalformedURLException {
-        this((URL) null, spec, (URLStreamHandler) null);
+        this((URL) null, spec, null);
     }
 
     /**
-     * Creates a new URL to the specified resource {@code spec}. This URL is
-     * relative to the given {@code context}. If the protocol of the parsed URL
-     * does not match with the protocol of the context URL, then the newly
-     * created URL is absolute and bases only on the given URL represented by
-     * {@code spec}. Otherwise the protocol is defined by the context URL.
+     * Creates a new URL by resolving {@code spec} relative to {@code context}.
      *
-     * @param context
-     *            the URL which is used as the context.
-     * @param spec
-     *            the URL string representation which has to be parsed.
-     * @throws MalformedURLException
-     *             if the given string {@code spec} could not be parsed as a URL
-     *             or an invalid protocol has been found.
+     * @param context the URL to which {@code spec} is relative, or null for
+     *     no context in which case {@code spec} must be an absolute URL.
+     * @throws MalformedURLException if {@code spec} could not be parsed as a
+     *     URL or has an unsupported protocol.
      */
     public URL(URL context, String spec) throws MalformedURLException {
-        this(context, spec, (URLStreamHandler) null);
+        this(context, spec, null);
     }
 
     /**
-     * Creates a new URL to the specified resource {@code spec}. This URL is
-     * relative to the given {@code context}. The {@code handler} will be used
-     * to parse the URL string representation. If this argument is {@code null}
-     * the default {@code URLStreamHandler} will be used. If the protocol of the
-     * parsed URL does not match with the protocol of the context URL, then the
-     * newly created URL is absolute and bases only on the given URL represented
-     * by {@code spec}. Otherwise the protocol is defined by the context URL.
+     * Creates a new URL by resolving {@code spec} relative to {@code context}.
      *
-     * @param context
-     *            the URL which is used as the context.
-     * @param spec
-     *            the URL string representation which has to be parsed.
-     * @param handler
-     *            the specific stream handler to be used by this URL.
-     * @throws MalformedURLException
-     *             if the given string {@code spec} could not be parsed as a URL
-     *             or an invalid protocol has been found.
+     * @param context the URL to which {@code spec} is relative, or null for
+     *     no context in which case {@code spec} must be an absolute URL.
+     * @param handler the stream handler for this URL, or null for the
+     *     protocol's default stream handler.
+     * @throws MalformedURLException if the given string {@code spec} could not
+     *     be parsed as a URL or an invalid protocol has been found.
      */
     public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
         if (handler != null) {
-            strmHandler = handler;
+            streamHandler = handler;
         }
 
         if (spec == null) {
@@ -208,15 +162,10 @@
         // The spec includes a protocol if it includes a colon character
         // before the first occurrence of a slash character. Note that,
         // "protocol" is the field which holds this URLs protocol.
-        int index;
-        try {
-            index = spec.indexOf(':');
-        } catch (NullPointerException e) {
-            throw new MalformedURLException(e.toString());
-        }
-        int startIPv6Addr = spec.indexOf('[');
+        int index = spec.indexOf(':');
+        int startIpv6Address = spec.indexOf('[');
         if (index >= 0) {
-            if ((startIPv6Addr == -1) || (index < startIPv6Addr)) {
+            if ((startIpv6Address == -1) || (index < startIpv6Address)) {
                 protocol = spec.substring(0, index);
                 // According to RFC 2396 scheme part should match
                 // the following expression:
@@ -254,8 +203,8 @@
                             .getAuthority(), context.getUserInfo(), cPath,
                             context.getQuery(), null);
                 }
-                if (strmHandler == null) {
-                    strmHandler = context.strmHandler;
+                if (streamHandler == null) {
+                    streamHandler = context.streamHandler;
                 }
             }
         } else {
@@ -269,16 +218,16 @@
             set(context.getProtocol(), context.getHost(), context.getPort(),
                     context.getAuthority(), context.getUserInfo(), context
                             .getPath(), context.getQuery(), null);
-            if (strmHandler == null) {
-                strmHandler = context.strmHandler;
+            if (streamHandler == null) {
+                streamHandler = context.streamHandler;
             }
         }
 
         // If the stream handler has not been determined, set it
         // to the default for the specified protocol.
-        if (strmHandler == null) {
+        if (streamHandler == null) {
             setupStreamHandler();
-            if (strmHandler == null) {
+            if (streamHandler == null) {
                 throw new MalformedURLException("Unknown protocol: " + protocol);
             }
         }
@@ -292,7 +241,7 @@
         // increment it to point at either character 0 or the character
         // after the colon.
         try {
-            strmHandler.parseURL(this, spec, ++index, spec.length());
+            streamHandler.parseURL(this, spec, ++index, spec.length());
         } catch (Exception e) {
             throw new MalformedURLException(e.toString());
         }
@@ -303,64 +252,41 @@
     }
 
     /**
-     * Creates a new URL instance using the given arguments. The URL uses the
-     * default port for the specified protocol.
+     * Creates a new URL of the given component parts. The URL uses the
+     * protocol's default port.
      *
-     * @param protocol
-     *            the protocol of the new URL.
-     * @param host
-     *            the host name or IP address of the new URL.
-     * @param file
-     *            the name of the resource.
-     * @throws MalformedURLException
-     *             if the combination of all arguments do not represent a valid
-     *             URL or the protocol is invalid.
+     * @throws MalformedURLException if the combination of all arguments do not
+     *     represent a valid URL or if the protocol is invalid.
      */
-    public URL(String protocol, String host, String file)
-            throws MalformedURLException {
-        this(protocol, host, -1, file, (URLStreamHandler) null);
+    public URL(String protocol, String host, String file) throws MalformedURLException {
+        this(protocol, host, -1, file, null);
     }
 
     /**
-     * Creates a new URL instance using the given arguments. The URL uses the
-     * specified port instead of the default port for the given protocol.
+     * Creates a new URL of the given component parts. The URL uses the
+     * protocol's default port.
      *
-     * @param protocol
-     *            the protocol of the new URL.
-     * @param host
-     *            the host name or IP address of the new URL.
-     * @param port
-     *            the specific port number of the URL. {@code -1} represents the
-     *            default port of the protocol.
-     * @param file
-     *            the name of the resource.
-     * @throws MalformedURLException
-     *             if the combination of all arguments do not represent a valid
-     *             URL or the protocol is invalid.
+     * @param host the host name or IP address of the new URL.
+     * @param port the port, or {@code -1} for the protocol's default port.
+     * @param file the name of the resource.
+     * @throws MalformedURLException if the combination of all arguments do not
+     *     represent a valid URL or if the protocol is invalid.
      */
-    public URL(String protocol, String host, int port, String file)
-            throws MalformedURLException {
-        this(protocol, host, port, file, (URLStreamHandler) null);
+    public URL(String protocol, String host, int port, String file) throws MalformedURLException {
+        this(protocol, host, port, file, null);
     }
 
     /**
-     * Creates a new URL instance using the given arguments. The URL uses the
-     * specified port instead of the default port for the given protocol.
+     * Creates a new URL of the given component parts. The URL uses the
+     * protocol's default port.
      *
-     * @param protocol
-     *            the protocol of the new URL.
-     * @param host
-     *            the host name or IP address of the new URL.
-     * @param port
-     *            the specific port number of the URL. {@code -1} represents the
-     *            default port of the protocol.
-     * @param file
-     *            the name of the resource.
-     * @param handler
-     *            the stream handler to be used by this URL.
-     * @throws MalformedURLException
-     *             if the combination of all arguments do not represent a valid
-     *             URL or the protocol is invalid.
+     * @param host the host name or IP address of the new URL.
+     * @param port the port, or {@code -1} for the protocol's default port.
+     * @param file the name of the resource.
+     * @param handler the stream handler for this URL, or null for the
+     *     protocol's default stream handler.
+     * @throws MalformedURLException if the combination of all arguments do not
+     *     represent a valid URL or if the protocol is invalid.
      */
     public URL(String protocol, String host, int port, String file,
             URLStreamHandler handler) throws MalformedURLException {
@@ -368,7 +294,7 @@
             throw new MalformedURLException("Port out of range: " + port);
         }
 
-        if (host != null && host.indexOf(":") != -1 && host.charAt(0) != '[') {
+        if (host != null && host.contains(":") && host.charAt(0) != '[') {
             host = "[" + host + "]";
         }
 
@@ -397,11 +323,11 @@
         // receiver's protocol if the handler was null.
         if (handler == null) {
             setupStreamHandler();
-            if (strmHandler == null) {
+            if (streamHandler == null) {
                 throw new MalformedURLException("Unknown protocol: " + protocol);
             }
         } else {
-            strmHandler = handler;
+            streamHandler = handler;
         }
     }
 
@@ -434,20 +360,8 @@
      * Sets the properties of this URL using the provided arguments. Only a
      * {@code URLStreamHandler} can use this method to set fields of the
      * existing URL instance. A URL is generally constant.
-     *
-     * @param protocol
-     *            the protocol to be set.
-     * @param host
-     *            the host name to be set.
-     * @param port
-     *            the port number to be set.
-     * @param file
-     *            the file to be set.
-     * @param ref
-     *            the reference to be set.
      */
-    protected void set(String protocol, String host, int port, String file,
-            String ref) {
+    protected void set(String protocol, String host, int port, String file, String ref) {
         if (this.protocol == null) {
             this.protocol = protocol;
         }
@@ -497,59 +411,48 @@
         if (this.getClass() != o.getClass()) {
             return false;
         }
-        return strmHandler.equals(this, (URL) o);
+        return streamHandler.equals(this, (URL) o);
     }
 
     /**
-     * Returns whether this URL refers to the same resource as the given
-     * argument {@code otherURL}. All URL components except the reference field
-     * are compared.
-     *
-     * @param otherURL
-     *            the URL to compare against.
-     * @return {@code true} if both instances refer to the same resource,
-     *         {@code false} otherwise.
+     * Returns true if this URL refers to the same resource as {@code otherURL}.
+     * All URL components except the reference field are compared.
      */
     public boolean sameFile(URL otherURL) {
-        return strmHandler.sameFile(this, otherURL);
+        return streamHandler.sameFile(this, otherURL);
     }
 
-    /**
-     * Gets the hashcode value of this URL instance.
-     *
-     * @return the appropriate hashcode value.
-     */
-    @Override
-    public int hashCode() {
+    @Override public int hashCode() {
         if (hashCode == 0) {
-            hashCode = strmHandler.hashCode(this);
+            hashCode = streamHandler.hashCode(this);
         }
         return hashCode;
     }
 
     /**
      * Sets the receiver's stream handler to one which is appropriate for its
-     * protocol. Throws a MalformedURLException if no reasonable handler is
-     * available.
-     * <p>
-     * Note that this will overwrite any existing stream handler with the new
-     * one. Senders must check if the strmHandler is null before calling the
+     * protocol.
+     *
+     * <p>Note that this will overwrite any existing stream handler with the new
+     * one. Senders must check if the streamHandler is null before calling the
      * method if they do not want this behavior (a speed optimization).
+     *
+     * @throws MalformedURLException if no reasonable handler is available.
      */
     void setupStreamHandler() {
         // Check for a cached (previously looked up) handler for
         // the requested protocol.
-        strmHandler = streamHandlers.get(protocol);
-        if (strmHandler != null) {
+        streamHandler = streamHandlers.get(protocol);
+        if (streamHandler != null) {
             return;
         }
 
         // If there is a stream handler factory, then attempt to
         // use it to create the handler.
         if (streamHandlerFactory != null) {
-            strmHandler = streamHandlerFactory.createURLStreamHandler(protocol);
-            if (strmHandler != null) {
-                streamHandlers.put(protocol, strmHandler);
+            streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);
+            if (streamHandler != null) {
+                streamHandlers.put(protocol, streamHandler);
                 return;
             }
         }
@@ -561,10 +464,10 @@
             for (String packageName : packageList.split("\\|")) {
                 String className = packageName + "." + protocol + ".Handler";
                 try {
-                    Class<?> klass = Class.forName(className, true, ClassLoader.getSystemClassLoader());
-                    strmHandler = (URLStreamHandler) klass.newInstance();
-                    if (strmHandler != null) {
-                        streamHandlers.put(protocol, strmHandler);
+                    Class<?> c = Class.forName(className, true, ClassLoader.getSystemClassLoader());
+                    streamHandler = (URLStreamHandler) c.newInstance();
+                    if (streamHandler != null) {
+                        streamHandlers.put(protocol, streamHandler);
                     }
                     return;
                 } catch (IllegalAccessException ignored) {
@@ -576,87 +479,75 @@
 
         // Fall back to a built-in stream handler if the user didn't supply one
         if (protocol.equals("file")) {
-            strmHandler = new FileHandler();
+            streamHandler = new FileHandler();
         } else if (protocol.equals("ftp")) {
-            strmHandler = new FtpHandler();
+            streamHandler = new FtpHandler();
         } else if (protocol.equals("http")) {
-            strmHandler = new HttpHandler();
+            streamHandler = new HttpHandler();
         } else if (protocol.equals("https")) {
-            strmHandler = new HttpsHandler();
+            streamHandler = new HttpsHandler();
         } else if (protocol.equals("jar")) {
-            strmHandler = new JarHandler();
+            streamHandler = new JarHandler();
         }
-        if (strmHandler != null) {
-            streamHandlers.put(protocol, strmHandler);
+        if (streamHandler != null) {
+            streamHandlers.put(protocol, streamHandler);
         }
     }
 
     /**
-     * Gets the content of the resource which is referred by this URL. By
-     * default one of the following object types will be returned:
-     * <p>
-     * <li>Image for pictures</li>
-     * <li>AudioClip for audio sequences</li>
-     * <li>{@link InputStream} for all other data</li>
-     *
-     * @return the content of the referred resource.
-     * @throws IOException
-     *             if an error occurs obtaining the content.
+     * Returns the content of the resource which is referred by this URL. By
+     * default, this returns an {@code InputStream} or null if the content type
+     * of the response is unknown.
      */
     public final Object getContent() throws IOException {
         return openConnection().getContent();
     }
 
     /**
-     * Gets the content of the resource which is referred by this URL. The
-     * argument {@code types} is an array of allowed or expected object types.
-     * {@code null} will be returned if the obtained object type does not match
-     * with one from this list. Otherwise the first type that matches will be
-     * used.
-     *
-     * @param types
-     *            the list of allowed or expected object types.
-     * @return the object representing the resource referred by this URL,
-     *         {@code null} if the content does not match to a specified content
-     *         type.
-     * @throws IOException
-     *             if an error occurs obtaining the content.
+     * Equivalent to {@code openConnection().getContent(types)}.
      */
-    // Param not generic in spec
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings("unchecked") // Param not generic in spec
     public final Object getContent(Class[] types) throws IOException {
         return openConnection().getContent(types);
     }
 
     /**
-     * Opens an InputStream to read the resource referred by this URL.
-     *
-     * @return the stream which allows to read the resource.
-     * @throws IOException
-     *             if an error occurs while opening the InputStream.
+     * Equivalent to {@code openConnection().getInputStream(types)}.
      */
-    public final InputStream openStream() throws java.io.IOException {
+    public final InputStream openStream() throws IOException {
         return openConnection().getInputStream();
     }
 
     /**
-     * Opens a connection to the remote resource specified by this URL. This
-     * connection allows bidirectional data transfer.
+     * Returns a new connection to the resource referred to by this URL.
      *
-     * @return the connection to this URL.
-     * @throws IOException
-     *             if an error occurs while opening the connection.
+     * @throws IOException if an error occurs while opening the connection.
      */
     public URLConnection openConnection() throws IOException {
-        return strmHandler.openConnection(this);
+        return streamHandler.openConnection(this);
     }
 
     /**
-     * Converts this URL instance into an equivalent URI object.
+     * Returns a new connection to the resource referred to by this URL.
      *
-     * @return the URI instance that represents this URL.
-     * @throws URISyntaxException
-     *             if this URL cannot be converted into a URI.
+     * @param proxy the proxy through which the connection will be established.
+     * @throws IOException if an I/O error occurs while opening the connection.
+     * @throws IllegalArgumentException if the argument proxy is null or of is
+     *     an invalid type.
+     * @throws UnsupportedOperationException if the protocol handler does not
+     *     support opening connections through proxies.
+     */
+    public URLConnection openConnection(Proxy proxy) throws IOException {
+        if (proxy == null) {
+            throw new IllegalArgumentException("proxy == null");
+        }
+        return streamHandler.openConnection(this, proxy);
+    }
+
+    /**
+     * Returns the URI equivalent to this URL.
+     *
+     * @throws URISyntaxException if this URL cannot be converted into a URI.
      */
     public URI toURI() throws URISyntaxException {
         return new URI(toExternalForm());
@@ -669,74 +560,33 @@
      * @hide
      */
     public URI toURILenient() throws URISyntaxException {
-        if (strmHandler == null) {
+        if (streamHandler == null) {
             throw new IllegalStateException(protocol);
         }
-        return new URI(strmHandler.toExternalForm(this, true));
-    }
-
-    /**
-     * Opens a connection to the remote resource specified by this URL. The
-     * connection will be established through the given proxy and allows
-     * bidirectional data transfer.
-     *
-     * @param proxy
-     *            the proxy through which the connection will be established.
-     * @return the appropriate URLConnection instance representing the
-     *         connection to this URL.
-     * @throws IOException
-     *             if an I/O error occurs while opening the connection.
-     * @throws IllegalArgumentException
-     *             if the argument proxy is {@code null} or is an invalid type.
-     * @throws UnsupportedOperationException
-     *             if the protocol handler does not support opening connections
-     *             through proxies.
-     */
-    public URLConnection openConnection(Proxy proxy) throws IOException {
-        if (proxy == null) {
-            throw new IllegalArgumentException("proxy == null");
-        }
-        return strmHandler.openConnection(this, proxy);
+        return new URI(streamHandler.toExternalForm(this, true));
     }
 
     /**
      * Returns a string containing a concise, human-readable representation of
      * this URL. The returned string is the same as the result of the method
      * {@code toExternalForm()}.
-     *
-     * @return the string representation of this URL.
      */
-    @Override
-    public String toString() {
+    @Override public String toString() {
         return toExternalForm();
     }
 
     /**
      * Returns a string containing a concise, human-readable representation of
      * this URL.
-     *
-     * @return the string representation of this URL.
      */
     public String toExternalForm() {
-        if (strmHandler == null) {
+        if (streamHandler == null) {
             return "unknown protocol(" + protocol + ")://" + host + file;
         }
-        return strmHandler.toExternalForm(this);
+        return streamHandler.toExternalForm(this);
     }
 
-    /**
-     * This method is called to restore the state of a URL object that has been
-     * serialized. The stream handler is determined from the URL's protocol.
-     *
-     * @param stream
-     *            the stream to read from.
-     *
-     * @throws IOException
-     *             if an IO Exception occurs while reading the stream or the
-     *             handler can not be found.
-     */
-    private void readObject(java.io.ObjectInputStream stream)
-            throws java.io.IOException {
+    private void readObject(ObjectInputStream stream) throws IOException {
         try {
             stream.defaultReadObject();
             if (host != null && authority == null) {
@@ -754,142 +604,111 @@
                 }
             }
             setupStreamHandler();
-            if (strmHandler == null) {
+            if (streamHandler == null) {
                 throw new IOException("Unknown protocol: " + protocol);
             }
+            hashCode = 0; // necessary until http://b/4471249 is fixed
         } catch (ClassNotFoundException e) {
-            throw new IOException(e.toString());
+            throw new IOException(e);
         }
     }
 
-    /**
-     * This method is called to write any non-transient, non-static variables
-     * into the output stream.
-     * <p>
-     * Note that, we really only need the readObject method but the spec that
-     * says readObject will be ignored if no writeObject is present.
-     *
-     * @param s
-     *            the stream to write on.
-     * @throws IOException
-     *             if an IO Exception occurs during the write.
-     */
     private void writeObject(ObjectOutputStream s) throws IOException {
         s.defaultWriteObject();
     }
 
-    /**
-     * Gets the value of the file part of this URL.
-     *
-     * @return the file name this URL refers to or an empty string if the file
-     *         part is not set.
-     */
-    public String getFile() {
-        return file;
-    }
-
-    /**
-     * Gets the value of the host part of this URL.
-     *
-     * @return the host name or IP address of this URL.
-     */
-    public String getHost() {
-        return host;
-    }
-
-    /**
-     * Gets the port number of this URL or {@code -1} if the port is not set.
-     *
-     * @return the port number of this URL.
-     */
-    public int getPort() {
-        return port;
-    }
-
     /** @hide */
     public int getEffectivePort() {
         return URI.getEffectivePort(protocol, port);
     }
 
     /**
-     * Gets the protocol of this URL.
-     *
-     * @return the protocol type of this URL.
+     * Returns the protocol of this URL like "http" or "file". This is also
+     * known as the scheme. The returned string is lower case.
      */
     public String getProtocol() {
         return protocol;
     }
 
     /**
-     * Gets the value of the reference part of this URL.
-     *
-     * @return the reference part of this URL.
-     */
-    public String getRef() {
-        return ref;
-    }
-
-    /**
-     * Gets the value of the query part of this URL.
-     *
-     * @return the query part of this URL.
-     */
-    public String getQuery() {
-        return query;
-    }
-
-    /**
-     * Gets the value of the path part of this URL.
-     *
-     * @return the path part of this URL.
-     */
-    public String getPath() {
-        return path;
-    }
-
-    /**
-     * Gets the value of the user-info part of this URL.
-     *
-     * @return the user-info part of this URL.
-     */
-    public String getUserInfo() {
-        return userInfo;
-    }
-
-    /**
-     * Gets the value of the authority part of this URL.
-     *
-     * @return the authority part of this URL.
+     * Returns the authority part of this URL.
      */
     public String getAuthority() {
         return authority;
     }
 
     /**
+     * Returns the user-info part of this URL.
+     */
+    public String getUserInfo() {
+        return userInfo;
+    }
+
+    /**
+     * Returns the host name or IP address of this URL.
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * Returns the port number of this URL, or {@code -1} if this URL has no
+     * explicit port.
+     *
+     * <p>If this URL has no explicit port, connections opened using this URL
+     * will use its {@link #getDefaultPort() default port}.
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Returns the default port number of the protocol used by this URL. If no
+     * default port is defined by the protocol or the {@code URLStreamHandler},
+     * {@code -1} will be returned.
+     *
+     * @see URLStreamHandler#getDefaultPort
+     */
+    public int getDefaultPort() {
+        return streamHandler.getDefaultPort();
+    }
+
+    /**
+     * Returns the file of this URL, or an empty string if the file is not set.
+     */
+    public String getFile() {
+        return file;
+    }
+
+    /**
+     * Returns the path part of this URL.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns the query part of this URL.
+     */
+    public String getQuery() {
+        return query;
+    }
+
+    /**
+     * Returns the value of the reference part of this URL. This is also known
+     * as the fragment.
+     */
+    public String getRef() {
+        return ref;
+    }
+
+    /**
      * Sets the properties of this URL using the provided arguments. Only a
      * {@code URLStreamHandler} can use this method to set fields of the
      * existing URL instance. A URL is generally constant.
-     *
-     * @param protocol
-     *            the protocol to be set.
-     * @param host
-     *            the host name to be set.
-     * @param port
-     *            the port number to be set.
-     * @param authority
-     *            the authority to be set.
-     * @param userInfo
-     *            the user-info to be set.
-     * @param path
-     *            the path to be set.
-     * @param query
-     *            the query to be set.
-     * @param ref
-     *            the reference to be set.
      */
-    protected void set(String protocol, String host, int port,
-            String authority, String userInfo, String path, String query,
-            String ref) {
+    protected void set(String protocol, String host, int port, String authority, String userInfo,
+            String path, String query, String ref) {
         String filePart = path;
         if (query != null && !query.isEmpty()) {
             if (filePart != null) {
@@ -904,16 +723,4 @@
         this.path = path;
         this.query = query;
     }
-
-    /**
-     * Gets the default port number of the protocol used by this URL. If no
-     * default port is defined by the protocol or the {@code URLStreamHandler},
-     * {@code -1} will be returned.
-     *
-     * @return the default port number according to the protocol of this URL.
-     * @see URLStreamHandler#getDefaultPort
-     */
-    public int getDefaultPort() {
-        return strmHandler.getDefaultPort();
-    }
 }
diff --git a/luni/src/main/java/java/net/URLClassLoader.java b/luni/src/main/java/java/net/URLClassLoader.java
index 735793c..1c8bc43 100644
--- a/luni/src/main/java/java/net/URLClassLoader.java
+++ b/luni/src/main/java/java/net/URLClassLoader.java
@@ -51,6 +51,7 @@
  * loaded by this {@code URLClassLoader} are granted permission to access the
  * URLs contained in the URL search list.
  */
+@FindBugsSuppressWarnings({ "DMI_COLLECTION_OF_URLS", "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" })
 public class URLClassLoader extends SecureClassLoader {
 
     ArrayList<URL> originalUrls;
@@ -174,13 +175,11 @@
             if (is == null) {
                 return null;
             }
-            byte[] clBuf = null;
+            byte[] clBuf;
             try {
-                clBuf = getBytes(is);
+                clBuf = Streams.readFully(is);
             } catch (IOException e) {
                 return null;
-            } finally {
-                IoUtils.closeQuietly(is);
             }
             if (packageName != null) {
                 String packageDotName = packageName.replace('/', '.');
@@ -329,15 +328,12 @@
         }
 
         private Class<?> createClass(JarEntry entry, Manifest manifest, String packageName, String origName) {
-            InputStream is = null;
-            byte[] clBuf = null;
+            byte[] clBuf;
             try {
-                is = jf.getInputStream(entry);
-                clBuf = getBytes(is);
+                InputStream is = jf.getInputStream(entry);
+                clBuf = Streams.readFully(is);
             } catch (IOException e) {
                 return null;
-            } finally {
-                IoUtils.closeQuietly(is);
             }
             if (packageName != null) {
                 String packageDotName = packageName.replace('/', '.');
@@ -600,17 +596,6 @@
     }
 
     /**
-     * Converts an input stream into a byte array.
-     *
-     * @param is
-     *            the input stream
-     * @return byte[] the byte array
-     */
-    private static byte[] getBytes(InputStream is) throws IOException {
-        return Streams.readFully(is);
-    }
-
-    /**
      * Gets all permissions for the specified {@code codesource}. First, this
      * method retrieves the permissions from the system policy. If the protocol
      * is "file:/" then a new permission, {@code FilePermission}, granting the
diff --git a/luni/src/main/java/java/net/URLConnection.java b/luni/src/main/java/java/net/URLConnection.java
index 26b805f..a8d967c 100644
--- a/luni/src/main/java/java/net/URLConnection.java
+++ b/luni/src/main/java/java/net/URLConnection.java
@@ -353,13 +353,8 @@
     }
 
     /**
-     * Returns the default value for the specified request {@code field} or {@code
-     * null} if the field could not be found. The base implementation of this
-     * method returns always {@code null}.
+     * Returns null.
      *
-     * @param field
-     *            the request field whose default value shall be returned.
-     * @return the default value for the given field.
      * @deprecated Use {@link #getRequestProperty}
      */
     @Deprecated
@@ -856,16 +851,9 @@
     }
 
     /**
-     * Sets the default value of the specified request header field. This value
-     * will be used for the specific field of every newly created connection.
-     * The base implementation of this method does nothing.
+     * Does nothing.
      *
-     * @param field
-     *            the request header field to be set.
-     * @param value
-     *            the default value to be used.
-     * @deprecated Use {@link #setRequestProperty} of an existing {@code
-     *             URLConnection} instance.
+     * @deprecated Use {@link URLConnection#setRequestProperty(String, String)}.
      */
     @Deprecated
     public static void setDefaultRequestProperty(String field, String value) {
diff --git a/luni/src/main/java/java/net/URLStreamHandler.java b/luni/src/main/java/java/net/URLStreamHandler.java
index 7a86644..dc1c680 100644
--- a/luni/src/main/java/java/net/URLStreamHandler.java
+++ b/luni/src/main/java/java/net/URLStreamHandler.java
@@ -90,7 +90,7 @@
             throw new StringIndexOutOfBoundsException(end - 2 - start);
         }
         if (end < start) {
-            if (this != u.strmHandler) {
+            if (this != u.streamHandler) {
                 throw new SecurityException();
             }
             return;
@@ -251,7 +251,7 @@
     @Deprecated
     protected void setURL(URL u, String protocol, String host, int port,
             String file, String ref) {
-        if (this != u.strmHandler) {
+        if (this != u.streamHandler) {
             throw new SecurityException();
         }
         u.set(protocol, host, port, file, ref);
@@ -283,7 +283,7 @@
     protected void setURL(URL u, String protocol, String host, int port,
             String authority, String userInfo, String file, String query,
             String ref) {
-        if (this != u.strmHandler) {
+        if (this != u.streamHandler) {
             throw new SecurityException();
         }
         u.set(protocol, host, port, authority, userInfo, file, query, ref);
diff --git a/luni/src/main/java/java/net/UnknownServiceException.java b/luni/src/main/java/java/net/UnknownServiceException.java
index a8afa41..bdcf6a5 100644
--- a/luni/src/main/java/java/net/UnknownServiceException.java
+++ b/luni/src/main/java/java/net/UnknownServiceException.java
@@ -30,15 +30,23 @@
     private static final long serialVersionUID = -4169033248853639508L;
 
     /**
-     * Constructs a new instance with the current stack trace.
+     * Constructs a new instance.
      */
     public UnknownServiceException() {
     }
 
     /**
-     * Constructs a new instance with the current stack trace and given detail message.
+     * Constructs a new instance with the given detail message.
      */
     public UnknownServiceException(String detailMessage) {
         super(detailMessage);
     }
+
+    /**
+     * Constructs a new instance with given detail message and cause.
+     * @hide internal use only
+     */
+    public UnknownServiceException(String detailMessage, Throwable cause) {
+        super(detailMessage, cause);
+    }
 }
diff --git a/luni/src/main/java/java/nio/Buffer.java b/luni/src/main/java/java/nio/Buffer.java
index c0ba368..bc152ef 100644
--- a/luni/src/main/java/java/nio/Buffer.java
+++ b/luni/src/main/java/java/nio/Buffer.java
@@ -303,7 +303,7 @@
      */
     void limitImpl(int newLimit) {
         if (newLimit < 0 || newLimit > capacity) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad limit (capacity " + capacity + "): " + newLimit);
         }
 
         limit = newLimit;
@@ -355,7 +355,7 @@
 
     void positionImpl(int newPosition) {
         if (newPosition < 0 || newPosition > limit) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad position (limit " + limit + "): " + newPosition);
         }
 
         position = newPosition;
@@ -383,7 +383,7 @@
      */
     public final Buffer reset() {
         if (mark == UNSET_MARK) {
-            throw new InvalidMarkException();
+            throw new InvalidMarkException("Mark not set");
         }
         position = mark;
         return this;
diff --git a/luni/src/main/java/java/nio/CharBuffer.java b/luni/src/main/java/java/nio/CharBuffer.java
index cb041f5..03bac04 100644
--- a/luni/src/main/java/java/nio/CharBuffer.java
+++ b/luni/src/main/java/java/nio/CharBuffer.java
@@ -630,8 +630,6 @@
 
     /**
      * Returns a string representing the current remaining chars of this buffer.
-     *
-     * @return a string representing the current remaining chars of this buffer.
      */
     @Override
     public String toString() {
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index 970c116..a626857 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -36,9 +36,11 @@
 import java.nio.channels.NotYetConnectedException;
 import java.nio.channels.spi.SelectorProvider;
 import java.util.Arrays;
+import libcore.io.ErrnoException;
+import libcore.io.IoBridge;
 import libcore.io.IoUtils;
+import libcore.io.Libcore;
 import libcore.util.EmptyArray;
-import org.apache.harmony.luni.platform.Platform;
 
 /*
  * The default implementation class of java.nio.channels.DatagramChannel.
@@ -70,7 +72,7 @@
      */
     protected DatagramChannelImpl(SelectorProvider selectorProvider) throws IOException {
         super(selectorProvider);
-        fd = IoUtils.socket(false);
+        fd = IoBridge.socket(false);
     }
 
     /*
@@ -99,7 +101,7 @@
      * Returns the local address to which the socket is bound.
      */
     InetAddress getLocalAddress() {
-        return IoUtils.getSocketLocalAddress(fd);
+        return IoBridge.getSocketLocalAddress(fd);
     }
 
     /**
@@ -126,7 +128,7 @@
         InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
         try {
             begin();
-            IoUtils.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort());
+            IoBridge.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort());
         } catch (ConnectException e) {
             // ConnectException means connect fail, not exception
         } finally {
@@ -150,7 +152,11 @@
         }
         connected = false;
         connectAddress = null;
-        Platform.NETWORK.disconnectDatagram(fd);
+        try {
+            Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsIOException();
+        }
         if (socket != null) {
             socket.disconnect();
         }
@@ -193,21 +199,15 @@
         DatagramPacket receivePacket;
         int oldposition = target.position();
         int received = 0;
+        // TODO: disallow mapped buffers and lose this conditional?
         if (target.hasArray()) {
-            receivePacket = new DatagramPacket(target.array(), target
-                    .position()
-                    + target.arrayOffset(), target.remaining());
+            receivePacket = new DatagramPacket(target.array(), target.position() + target.arrayOffset(), target.remaining());
         } else {
-            receivePacket = new DatagramPacket(new byte[target.remaining()],
-                    target.remaining());
+            receivePacket = new DatagramPacket(new byte[target.remaining()], target.remaining());
         }
         do {
-            received = Platform.NETWORK.recv(fd, receivePacket,
-                    receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(),
-                    false, isConnected());
-
+            received = IoBridge.recvfrom(false, fd, receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), 0, receivePacket, isConnected());
             if (receivePacket != null && receivePacket.getAddress() != null) {
-
                 if (received > 0) {
                     if (target.hasArray()) {
                         target.position(oldposition + received);
@@ -229,10 +229,7 @@
         int oldposition = target.position();
         int received = 0;
         do {
-            int address = NioUtils.getDirectBufferAddress(target);
-            received = Platform.NETWORK.recvDirect(fd, receivePacket, address,
-                    target.position(), target.remaining(), false, isConnected());
-
+            received = IoBridge.recvfrom(false, fd, target, 0, receivePacket, isConnected());
             if (receivePacket != null && receivePacket.getAddress() != null) {
                 // copy the data of received packet
                 if (received > 0) {
@@ -245,18 +242,11 @@
         return retAddr;
     }
 
-    /**
-     * @see java.nio.channels.DatagramChannel#send(java.nio.ByteBuffer,
-     *      java.net.SocketAddress)
-     */
     @Override
     public int send(ByteBuffer source, SocketAddress socketAddress) throws IOException {
-        // must not null
         checkNotNull(source);
-        // must open
         checkOpen();
 
-        // transfer socketAddress
         InetSocketAddress isa = (InetSocketAddress) socketAddress;
         if (isa.getAddress() == null) {
             throw new IOException();
@@ -266,38 +256,19 @@
             throw new IllegalArgumentException();
         }
 
-        // the return value.
-        int sendCount = 0;
-        try {
-            begin();
-            byte[] array = null;
-            int length = source.remaining();
-            int oldposition = source.position();
-            int start = oldposition;
-            if (source.isDirect()) {
-                synchronized (writeLock) {
-                    int address = NioUtils.getDirectBufferAddress(source);
-                    sendCount = Platform.NETWORK.sendDirect(fd, address, start, length,
-                            isa.getPort(), isa.getAddress());
+        synchronized (writeLock) {
+            int sendCount = 0;
+            try {
+                begin();
+                int oldPosition = source.position();
+                sendCount = IoBridge.sendto(fd, source, 0, isa.getAddress(), isa.getPort());
+                if (sendCount > 0) {
+                    source.position(oldPosition + sendCount);
                 }
-            } else {
-                if (source.hasArray()) {
-                    array = source.array();
-                    start += source.arrayOffset();
-                } else {
-                    array = new byte[length];
-                    source.get(array);
-                    start = 0;
-                }
-                synchronized (writeLock) {
-                    sendCount = Platform.NETWORK.send(fd, array, start, length,
-                            isa.getPort(), isa.getAddress());
-                }
+            } finally {
+                end(sendCount >= 0);
             }
-            source.position(oldposition + sendCount);
             return sendCount;
-        } finally {
-            end(sendCount >= 0);
         }
     }
 
@@ -360,64 +331,34 @@
     /*
      * read from channel, and store the result in the target.
      */
-    private int readImpl(ByteBuffer readBuffer) throws IOException {
+    private int readImpl(ByteBuffer dst) throws IOException {
         synchronized (readLock) {
             int readCount = 0;
             try {
                 begin();
-                int start = readBuffer.position();
-                int length = readBuffer.remaining();
-                if (readBuffer.isDirect()) {
-                    int address = NioUtils.getDirectBufferAddress(readBuffer);
-                    readCount = Platform.NETWORK.recvDirect(fd, null, address, start, length,
-                            false, isConnected());
-                } else {
-                    // the target is assured to have array.
-                    byte[] target = readBuffer.array();
-                    start += readBuffer.arrayOffset();
-                    readCount = Platform.NETWORK.recv(fd, null, target, start, length, false,
-                            isConnected());
-                }
-                return readCount;
+                readCount = IoBridge.recvfrom(false, fd, dst, 0, null, isConnected());
             } catch (InterruptedIOException e) {
                 // InterruptedIOException will be thrown when timeout.
                 return 0;
             } finally {
                 end(readCount > 0);
             }
+            return readCount;
         }
     }
 
-    /**
-     * @see java.nio.channels.DatagramChannel#write(java.nio.ByteBuffer)
-     */
-    @Override
-    public int write(ByteBuffer source) throws IOException {
-        // source buffer must be not null
-        checkNotNull(source);
-        // status must be open and connected
+    @Override public int write(ByteBuffer src) throws IOException {
+        checkNotNull(src);
         checkOpenConnected();
-        // return immediately if source is full
-        if (!source.hasRemaining()) {
+        if (!src.hasRemaining()) {
             return 0;
         }
 
-        ByteBuffer writeBuffer = null;
-        byte[] writeArray = null;
-        int oldposition = source.position();
-        int result;
-        if (source.isDirect() || source.hasArray()) {
-            writeBuffer = source;
-        } else {
-            writeArray = new byte[source.remaining()];
-            source.get(writeArray);
-            writeBuffer = ByteBuffer.wrap(writeArray);
+        int writeCount = writeImpl(src);
+        if (writeCount > 0) {
+            src.position(src.position() + writeCount);
         }
-        result = writeImpl(writeBuffer);
-        if (result > 0) {
-            source.position(oldposition + result);
-        }
-        return result;
+        return writeCount;
     }
 
     /**
@@ -455,50 +396,32 @@
         return written;
     }
 
-    /*
-     * Write the source. Return the count of bytes written.
-     */
     private int writeImpl(ByteBuffer buf) throws IOException {
         synchronized (writeLock) {
             int result = 0;
             try {
                 begin();
-                int length = buf.remaining();
-                int start = buf.position();
-
-                if (buf.isDirect()) {
-                    int address = NioUtils.getDirectBufferAddress(buf);
-                    result = Platform.NETWORK.sendDirect(fd, address, start, length, 0, null);
-                } else {
-                    // buf is assured to have array.
-                    start += buf.arrayOffset();
-                    result = Platform.NETWORK.send(fd, buf.array(), start, length, 0, null);
-                }
-                return result;
+                result = IoBridge.sendto(fd, buf, 0, null, 0);
             } finally {
                 end(result > 0);
             }
+            return result;
         }
     }
 
-    /*
-     * Do really closing action here.
-     */
-    @Override
-    protected synchronized void implCloseSelectableChannel() throws IOException {
+    @Override protected synchronized void implCloseSelectableChannel() throws IOException {
         connected = false;
         if (socket != null && !socket.isClosed()) {
             socket.close();
         } else {
-            Platform.NETWORK.close(fd);
+            IoBridge.closeSocket(fd);
         }
     }
 
-    @Override
-    protected void implConfigureBlocking(boolean blockingMode) throws IOException {
-        // Do nothing here. For real read/write operation in nonblocking mode,
-        // it uses select system call. Whether a channel is blocking can be
-        // decided by isBlocking() method.
+    @Override protected void implConfigureBlocking(boolean blocking) throws IOException {
+        synchronized (blockingLock()) {
+            IoUtils.setBlocking(fd, blocking);
+        }
     }
 
     /*
diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java
index acc3725..379c99f 100644
--- a/luni/src/main/java/java/nio/FileChannelImpl.java
+++ b/luni/src/main/java/java/nio/FileChannelImpl.java
@@ -238,9 +238,6 @@
 
     public long position() throws IOException {
         checkOpen();
-        if ((mode & O_APPEND) != 0) {
-            return size();
-        }
         try {
             return Libcore.os.lseek(fd, 0L, SEEK_CUR);
         } catch (ErrnoException errnoException) {
@@ -388,12 +385,12 @@
         }
     }
 
-    public long transferTo(long position, long count, WritableByteChannel target)
-            throws IOException {
+    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
         checkOpen();
         if (!target.isOpen()) {
             throw new ClosedChannelException();
         }
+        checkReadable();
         if (target instanceof FileChannelImpl) {
             ((FileChannelImpl) target).checkWritable();
         }
@@ -482,9 +479,6 @@
     public int write(ByteBuffer buffer) throws IOException {
         checkOpen();
         checkWritable();
-        if ((mode & O_APPEND) != 0) {
-            position(size());
-        }
         return writeImpl(buffer);
     }
 
diff --git a/luni/src/main/java/java/nio/InvalidMarkException.java b/luni/src/main/java/java/nio/InvalidMarkException.java
index cb1050e..349cf3a 100644
--- a/luni/src/main/java/java/nio/InvalidMarkException.java
+++ b/luni/src/main/java/java/nio/InvalidMarkException.java
@@ -29,4 +29,12 @@
      */
     public InvalidMarkException() {
     }
+
+    /**
+     * Constructs an {@code InvalidMarkException} with the given detail message.
+     * @hide
+     */
+    public InvalidMarkException(String detailMessage) {
+        super(detailMessage);
+    }
 }
diff --git a/luni/src/main/java/java/nio/IoVec.java b/luni/src/main/java/java/nio/IoVec.java
index 77bddb6..948e658 100644
--- a/luni/src/main/java/java/nio/IoVec.java
+++ b/luni/src/main/java/java/nio/IoVec.java
@@ -22,7 +22,7 @@
 import libcore.io.ErrnoException;
 
 /**
- * Used to implement java.nio read(ByteBuffer[])/write(ByteBuffer[]) operations to POSIX readv(2)
+ * Used to implement java.nio read(ByteBuffer[])/write(ByteBuffer[]) operations as POSIX readv(2)
  * and writev(2) calls.
  */
 final class IoVec {
diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java
index 925c1e2..dfd3270 100644
--- a/luni/src/main/java/java/nio/SelectorImpl.java
+++ b/luni/src/main/java/java/nio/SelectorImpl.java
@@ -34,36 +34,20 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.UnsafeArrayList;
 import libcore.io.ErrnoException;
+import libcore.io.IoBridge;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
+import libcore.io.StructPollfd;
 import libcore.util.EmptyArray;
-import org.apache.harmony.luni.platform.Platform;
+import static libcore.io.OsConstants.*;
 
 /*
  * Default implementation of java.nio.channels.Selector
  */
 final class SelectorImpl extends AbstractSelector {
 
-    static final FileDescriptor[] EMPTY_FILE_DESCRIPTORS_ARRAY = new FileDescriptor[0];
-
-    private static final SelectionKeyImpl[] EMPTY_SELECTION_KEY_IMPLS_ARRAY
-            = new SelectionKeyImpl[0];
-
-    private static final int CONNECT_OR_WRITE = OP_CONNECT | OP_WRITE;
-
-    private static final int ACCEPT_OR_READ = OP_ACCEPT | OP_READ;
-
-    private static final int NA = 0;
-
-    private static final int READABLE = 1;
-
-    private static final int WRITABLE = 2;
-
-    private static final int SELECT_BLOCK = -1;
-
-    private static final int SELECT_NOW = 0;
-
     /**
      * Used to synchronize when a key's interest ops change.
      */
@@ -94,33 +78,7 @@
     private final FileDescriptor wakeupIn;
     private final FileDescriptor wakeupOut;
 
-    /**
-     * File descriptors we're interested in reading from. When actively
-     * selecting, the first element is always the wakeup channel's file
-     * descriptor, and the other elements are user-specified file descriptors.
-     * Otherwise, all elements are null.
-     */
-    private FileDescriptor[] readableFDs = EMPTY_FILE_DESCRIPTORS_ARRAY;
-
-    /**
-     * File descriptors we're interested in writing from. May be empty. When not
-     * actively selecting, all elements are null.
-     */
-    private FileDescriptor[] writableFDs = EMPTY_FILE_DESCRIPTORS_ARRAY;
-
-    /**
-     * Selection keys that correspond to the concatenation of readableFDs and
-     * writableFDs. This is used to interpret the results returned by select().
-     * When not actively selecting, all elements are null.
-     */
-    private SelectionKeyImpl[] readyKeys = EMPTY_SELECTION_KEY_IMPLS_ARRAY;
-
-    /**
-     * Selection flags that define the ready ops on the ready keys. When not
-     * actively selecting, all elements are 0. Corresponds to the ready keys
-     * set.
-     */
-    private int[] flags = EmptyArray.INT;
+    private final UnsafeArrayList<StructPollfd> pollFds = new UnsafeArrayList<StructPollfd>(StructPollfd.class, 8);
 
     public SelectorImpl(SelectorProvider selectorProvider) throws IOException {
         super(selectorProvider);
@@ -135,6 +93,8 @@
             wakeupIn = pipeFds[0];
             wakeupOut = pipeFds[1];
             IoUtils.setBlocking(wakeupIn, false);
+            pollFds.add(new StructPollfd());
+            setPollFd(0, wakeupIn, POLLIN, null);
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
         }
@@ -163,9 +123,10 @@
         }
         synchronized (this) {
             synchronized (unmodifiableKeys) {
-                SelectionKeyImpl selectionKey = new SelectionKeyImpl(
-                        channel, operations, attachment, this);
+                SelectionKeyImpl selectionKey = new SelectionKeyImpl(channel, operations,
+                        attachment, this);
                 mutableKeys.add(selectionKey);
+                ensurePollFdsCapacity();
                 return selectionKey;
             }
         }
@@ -183,18 +144,20 @@
     }
 
     @Override public int select() throws IOException {
-        return selectInternal(SELECT_BLOCK);
+        // Blocks until some fd is ready.
+        return selectInternal(-1);
     }
 
     @Override public int select(long timeout) throws IOException {
         if (timeout < 0) {
             throw new IllegalArgumentException();
         }
-        return selectInternal((timeout == 0) ? SELECT_BLOCK : timeout);
+        // Our timeout is interpreted differently to Unix's --- 0 means block. See selectNow.
+        return selectInternal((timeout == 0) ? -1 : timeout);
     }
 
     @Override public int selectNow() throws IOException {
-        return selectInternal(SELECT_NOW);
+        return selectInternal(0);
     }
 
     private int selectInternal(long timeout) throws IOException {
@@ -203,146 +166,113 @@
             synchronized (unmodifiableKeys) {
                 synchronized (selectedKeys) {
                     doCancel();
-                    boolean isBlock = (SELECT_NOW != timeout);
-                    int readableKeysCount = 1; // first is always the wakeup channel
-                    int writableKeysCount = 0;
+                    boolean isBlock = (timeout != 0);
                     synchronized (keysLock) {
-                        for (SelectionKeyImpl key : mutableKeys) {
-                            int ops = key.interestOpsNoCheck();
-                            if ((ACCEPT_OR_READ & ops) != 0) {
-                                readableKeysCount++;
-                            }
-                            if ((CONNECT_OR_WRITE & ops) != 0) {
-                                writableKeysCount++;
-                            }
-                        }
-                        prepareChannels(readableKeysCount, writableKeysCount);
+                        preparePollFds();
                     }
-                    boolean success;
+                    int rc;
                     try {
                         if (isBlock) {
                             begin();
                         }
-                        success = Platform.NETWORK.select(readableFDs, writableFDs,
-                                readableKeysCount, writableKeysCount, timeout, flags);
+                        rc = Libcore.os.poll(pollFds.array(), (int) timeout);
                     } finally {
                         if (isBlock) {
                             end();
                         }
                     }
 
-                    int selected = success ? processSelectResult() : 0;
-
-                    Arrays.fill(readableFDs, null);
-                    Arrays.fill(writableFDs, null);
-                    Arrays.fill(readyKeys, null);
-                    Arrays.fill(flags, 0);
-
-                    selected -= doCancel();
-
-                    return selected;
+                    int readyCount = (rc > 0) ? processPollFds() : 0;
+                    readyCount -= doCancel();
+                    return readyCount;
                 }
             }
         }
     }
 
-    private int getReadyOps(SelectionKeyImpl key) {
-        SelectableChannel channel = key.channel();
-        return ((channel instanceof SocketChannel) && !((SocketChannel) channel).isConnectionPending()) ?
-                OP_WRITE : CONNECT_OR_WRITE;
+    private void setPollFd(int i, FileDescriptor fd, int events, Object object) {
+        StructPollfd pollFd = pollFds.get(i);
+        pollFd.fd = fd;
+        pollFd.events = (short) events;
+        pollFd.userData = object;
     }
 
-    /**
-     * Prepare the readableFDs, writableFDs, readyKeys and flags arrays in
-     * preparation for a call to {@code INetworkSystem#select()}. After they're
-     * used, the array elements must be cleared.
-     */
-    private void prepareChannels(int numReadable, int numWritable) {
-        // grow each array to sufficient capacity. Always grow to at least 1.5x
-        // to avoid growing too frequently
-        if (readableFDs.length < numReadable) {
-            int newSize = Math.max((int) (readableFDs.length * 1.5f), numReadable);
-            readableFDs = new FileDescriptor[newSize];
-        }
-        if (writableFDs.length < numWritable) {
-            int newSize = Math.max((int) (writableFDs.length * 1.5f), numWritable);
-            writableFDs = new FileDescriptor[newSize];
-        }
-        int total = numReadable + numWritable;
-        if (readyKeys.length < total) {
-            int newSize = Math.max((int) (readyKeys.length * 1.5f), total);
-            readyKeys = new SelectionKeyImpl[newSize];
-            flags = new int[newSize];
-        }
-
-        // populate the FDs, including the wakeup channel
-        readableFDs[0] = wakeupIn;
-        int r = 1;
-        int w = 0;
+    private void preparePollFds() {
+        int i = 1; // Our wakeup pipe comes before all the user's fds.
         for (SelectionKeyImpl key : mutableKeys) {
             int interestOps = key.interestOpsNoCheck();
-            if ((ACCEPT_OR_READ & interestOps) != 0) {
-                readableFDs[r] = ((FileDescriptorChannel) key.channel()).getFD();
-                readyKeys[r] = key;
-                r++;
+            short eventMask = 0;
+            if (((OP_ACCEPT | OP_READ) & interestOps) != 0) {
+                eventMask |= POLLIN;
             }
-            if ((getReadyOps(key) & interestOps) != 0) {
-                writableFDs[w] = ((FileDescriptorChannel) key.channel()).getFD();
-                readyKeys[w + numReadable] = key;
-                w++;
+            if (((OP_CONNECT | OP_WRITE) & interestOps) != 0) {
+                eventMask |= POLLOUT;
+            }
+            if (eventMask != 0) {
+                setPollFd(i++, ((FileDescriptorChannel) key.channel()).getFD(), eventMask, key);
             }
         }
     }
 
+    private void ensurePollFdsCapacity() {
+        // We need one slot for each element of mutableKeys, plus one for the wakeup pipe.
+        while (pollFds.size() < mutableKeys.size() + 1) {
+            pollFds.add(new StructPollfd());
+        }
+    }
+
     /**
-     * Updates the key ready ops and selected key set with data from the flags
-     * array.
+     * Updates the key ready ops and selected key set.
      */
-    private int processSelectResult() throws IOException {
-        if (flags[0] == READABLE) {
+    private int processPollFds() throws IOException {
+        if (pollFds.get(0).revents == POLLIN) {
             // Read bytes from the wakeup pipe until the pipe is empty.
             byte[] buffer = new byte[8];
-            while (IoUtils.read(wakeupIn, buffer, 0, 1) > 0) {
+            while (IoBridge.read(wakeupIn, buffer, 0, 1) > 0) {
             }
         }
 
-        int selected = 0;
-        for (int i = 1; i < flags.length; i++) {
-            if (flags[i] == NA) {
+        int readyKeyCount = 0;
+        for (int i = 1; i < pollFds.size(); ++i) {
+            StructPollfd pollFd = pollFds.get(i);
+            if (pollFd.revents == 0) {
                 continue;
             }
+            if (pollFd.fd == null) {
+                break;
+            }
 
-            SelectionKeyImpl key = readyKeys[i];
+            SelectionKeyImpl key = (SelectionKeyImpl) pollFd.userData;
+
+            pollFd.fd = null;
+            pollFd.userData = null;
+
             int ops = key.interestOpsNoCheck();
             int selectedOp = 0;
-
-            switch (flags[i]) {
-                case READABLE:
-                    selectedOp = ACCEPT_OR_READ & ops;
-                    break;
-                case WRITABLE:
-                    if (key.isConnected()) {
-                        selectedOp = OP_WRITE & ops;
-                    } else {
-                        selectedOp = OP_CONNECT & ops;
-                    }
-                    break;
+            if ((pollFd.revents & POLLIN) != 0) {
+                selectedOp = ops & (OP_ACCEPT | OP_READ);
+            } else if ((pollFd.revents & POLLOUT) != 0) {
+                if (key.isConnected()) {
+                    selectedOp = ops & OP_WRITE;
+                } else {
+                    selectedOp = ops & OP_CONNECT;
+                }
             }
 
             if (selectedOp != 0) {
                 boolean wasSelected = mutableSelectedKeys.contains(key);
                 if (wasSelected && key.readyOps() != selectedOp) {
                     key.setReadyOps(key.readyOps() | selectedOp);
-                    selected++;
+                    ++readyKeyCount;
                 } else if (!wasSelected) {
                     key.setReadyOps(selectedOp);
                     mutableSelectedKeys.add(key);
-                    selected++;
+                    ++readyKeyCount;
                 }
             }
         }
 
-        return selected;
+        return readyKeyCount;
     }
 
     @Override public synchronized Set<SelectionKey> selectedKeys() {
diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
index e3103b3..5331158 100644
--- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java
@@ -31,16 +31,15 @@
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
-import org.apache.harmony.luni.platform.Platform;
+import libcore.io.IoUtils;
 
 /**
  * The default ServerSocketChannel.
  */
 final class ServerSocketChannelImpl extends ServerSocketChannel implements FileDescriptorChannel {
 
-    private final FileDescriptor fd = new FileDescriptor();
-    private final SocketImpl impl = new PlainServerSocketImpl(fd);
-    private final ServerSocketAdapter socket = new ServerSocketAdapter(impl, this);
+    private final ServerSocketAdapter socket;
+    private final SocketImpl impl;
 
     private boolean isBound = false;
 
@@ -48,12 +47,8 @@
 
     public ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
         super(sp);
-    }
-
-    // for native call
-    @SuppressWarnings("unused")
-    private ServerSocketChannelImpl() throws IOException {
-        this(SelectorProvider.provider());
+        this.socket = new ServerSocketAdapter(this);
+        this.impl = socket.getImpl$();
     }
 
     @Override public ServerSocket socket() {
@@ -68,47 +63,35 @@
             throw new NotYetBoundException();
         }
 
-        // TODO: pass in the SelectorProvider used to create this ServerSocketChannelImpl?
         // Create an empty socket channel. This will be populated by ServerSocketAdapter.accept.
-        SocketChannelImpl result = new SocketChannelImpl(SelectorProvider.provider(), false);
-        Socket resultSocket = result.socket();
-
+        SocketChannelImpl result = new SocketChannelImpl(provider(), false);
+        boolean connected = false;
         try {
             begin();
             synchronized (acceptLock) {
                 synchronized (blockingLock()) {
-                    boolean isBlocking = isBlocking();
-                    if (!isBlocking) {
-                        int[] tryResult = new int[1];
-                        boolean success = Platform.NETWORK.select(new FileDescriptor[] { fd },
-                                SelectorImpl.EMPTY_FILE_DESCRIPTORS_ARRAY, 1, 0, 0, tryResult);
-                        if (!success || tryResult[0] == 0) {
-                            // no pending connections, returns immediately.
-                            return null;
-                        }
-                    }
-                    // do accept.
                     do {
                         try {
-                            socket.accept(resultSocket, result);
+                            socket.implAccept(result);
                             // select successfully, break out immediately.
                             break;
                         } catch (SocketTimeoutException e) {
                             // continue to accept if the channel is in blocking mode.
+                            // TODO: does this make sense? why does blocking imply no timeouts?
                         }
-                    } while (isBlocking);
+                    } while (isBlocking());
                 }
             }
         } finally {
-            end(resultSocket.isConnected());
+            end(result.socket().isConnected());
         }
-        return result;
+        return result.socket().isConnected() ? result : null;
     }
 
-    protected void implConfigureBlocking(boolean blockingMode) throws IOException {
-        // Do nothing here. For real accept() operation in non-blocking mode,
-        // it uses INetworkSystem.select. Whether a channel is blocking can be
-        // decided by isBlocking() method.
+    @Override protected void implConfigureBlocking(boolean blocking) throws IOException {
+        synchronized (blockingLock()) {
+            IoUtils.setBlocking(impl.getFD$(), blocking);
+        }
     }
 
     synchronized protected void implCloseSelectableChannel() throws IOException {
@@ -118,14 +101,13 @@
     }
 
     public FileDescriptor getFD() {
-        return fd;
+        return impl.getFD$();
     }
 
     private static class ServerSocketAdapter extends ServerSocket {
         private final ServerSocketChannelImpl channelImpl;
 
-        ServerSocketAdapter(SocketImpl impl, ServerSocketChannelImpl aChannelImpl) {
-            super(impl);
+        ServerSocketAdapter(ServerSocketChannelImpl aChannelImpl) throws IOException {
             this.channelImpl = aChannelImpl;
         }
 
@@ -145,22 +127,23 @@
             return sc.socket();
         }
 
-        private Socket accept(Socket socket, SocketChannelImpl sockChannel) throws IOException {
+        public Socket implAccept(SocketChannelImpl clientSocketChannel) throws IOException {
+            Socket clientSocket = clientSocketChannel.socket();
             boolean connectOK = false;
             try {
                 synchronized (this) {
-                    super.implAccept(socket);
-                    sockChannel.setConnected();
-                    sockChannel.setBound(true);
-                    sockChannel.finishAccept();
+                    super.implAccept(clientSocket);
+                    clientSocketChannel.setConnected();
+                    clientSocketChannel.setBound(true);
+                    clientSocketChannel.finishAccept();
                 }
                 connectOK = true;
             } finally {
                 if (!connectOK) {
-                    socket.close();
+                    clientSocket.close();
                 }
             }
-            return socket;
+            return clientSocket;
         }
 
         @Override public ServerSocketChannel getChannel() {
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index 51cda16..9f8c690 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -44,52 +44,43 @@
 import java.util.Arrays;
 import libcore.io.ErrnoException;
 import libcore.io.Libcore;
+import libcore.io.IoBridge;
 import libcore.io.IoUtils;
-import org.apache.harmony.luni.platform.Platform;
 import static libcore.io.OsConstants.*;
 
 /*
  * The default implementation class of java.nio.channels.SocketChannel.
  */
 class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
-
-    private static final int EOF = -1;
-
-    // Status un-init, not initialized.
-    static final int SOCKET_STATUS_UNINIT = EOF;
+    private static final int SOCKET_STATUS_UNINITIALIZED = -1;
 
     // Status before connect.
-    static final int SOCKET_STATUS_UNCONNECTED = 0;
+    private static final int SOCKET_STATUS_UNCONNECTED = 0;
 
     // Status connection pending.
-    static final int SOCKET_STATUS_PENDING = 1;
+    private static final int SOCKET_STATUS_PENDING = 1;
 
     // Status after connection success.
-    static final int SOCKET_STATUS_CONNECTED = 2;
+    private static final int SOCKET_STATUS_CONNECTED = 2;
 
     // Status closed.
-    static final int SOCKET_STATUS_CLOSED = 3;
+    private static final int SOCKET_STATUS_CLOSED = 3;
 
-    // The descriptor to interact with native code.
-    final FileDescriptor fd;
+    private final FileDescriptor fd;
 
     // Our internal Socket.
     private SocketAdapter socket = null;
 
     // The address to be connected.
-    InetSocketAddress connectAddress = null;
+    private InetSocketAddress connectAddress = null;
 
-    // Local address of the this socket (package private for adapter).
-    InetAddress localAddress = null;
+    private InetAddress localAddress = null;
+    private int localPort;
 
-    // Local port number.
-    int localPort;
-
-    // At first, uninitialized.
-    int status = SOCKET_STATUS_UNINIT;
+    private int status = SOCKET_STATUS_UNINITIALIZED;
 
     // Whether the socket is bound.
-    volatile boolean isBound = false;
+    private volatile boolean isBound = false;
 
     private final Object readLock = new Object();
 
@@ -108,7 +99,7 @@
     public SocketChannelImpl(SelectorProvider selectorProvider, boolean connect) throws IOException {
         super(selectorProvider);
         status = SOCKET_STATUS_UNCONNECTED;
-        fd = (connect ? IoUtils.socket(true) : new FileDescriptor());
+        fd = (connect ? IoBridge.socket(true) : new FileDescriptor());
     }
 
     /*
@@ -174,7 +165,7 @@
             if (isBlocking()) {
                 begin();
             }
-            finished = IoUtils.connect(fd, normalAddr, port);
+            finished = IoBridge.connect(fd, normalAddr, port);
             isBound = finished;
         } catch (IOException e) {
             if (e instanceof ConnectException && !isBlocking()) {
@@ -236,7 +227,9 @@
         boolean finished = false;
         try {
             begin();
-            finished = Platform.NETWORK.isConnected(fd, 0);
+            InetAddress inetAddress = connectAddress.getAddress();
+            int port = connectAddress.getPort();
+            finished = IoBridge.isConnected(fd, inetAddress, port, 0, 0); // Return immediately.
             isBound = finished;
         } catch (ConnectException e) {
             if (isOpen()) {
@@ -260,30 +253,13 @@
     }
 
     @Override
-    public int read(ByteBuffer target) throws IOException {
-        FileChannelImpl.checkWritable(target);
+    public int read(ByteBuffer dst) throws IOException {
+        FileChannelImpl.checkWritable(dst);
         checkOpenConnected();
-        if (!target.hasRemaining()) {
+        if (!dst.hasRemaining()) {
             return 0;
         }
-
-        int readCount;
-        if (target.isDirect() || target.hasArray()) {
-            readCount = readImpl(target);
-            if (readCount > 0) {
-                target.position(target.position() + readCount);
-            }
-        } else {
-            ByteBuffer readBuffer = null;
-            byte[] readArray = null;
-            readArray = new byte[target.remaining()];
-            readBuffer = ByteBuffer.wrap(readArray);
-            readCount = readImpl(readBuffer);
-            if (readCount > 0) {
-                target.put(readArray, 0, readCount);
-            }
-        }
-        return readCount;
+        return readImpl(dst);
     }
 
     @Override
@@ -297,9 +273,9 @@
         byte[] readArray = new byte[totalCount];
         ByteBuffer readBuffer = ByteBuffer.wrap(readArray);
         int readCount;
-        // read data to readBuffer, and then transfer data from readBuffer to
-        // targets.
+        // read data to readBuffer, and then transfer data from readBuffer to targets.
         readCount = readImpl(readBuffer);
+        readBuffer.flip();
         if (readCount > 0) {
             int left = readCount;
             int index = offset;
@@ -314,49 +290,36 @@
         return readCount;
     }
 
-    /**
-     * Read from channel, and store the result in the target.
-     *
-     * @param target
-     *            output parameter
-     */
-    private int readImpl(ByteBuffer target) throws IOException {
+    private int readImpl(ByteBuffer dst) throws IOException {
         synchronized (readLock) {
             int readCount = 0;
             try {
                 if (isBlocking()) {
                     begin();
                 }
-                int offset = target.position();
-                int length = target.remaining();
-                if (target.isDirect()) {
-                    int address = NioUtils.getDirectBufferAddress(target);
-                    readCount = Platform.NETWORK.readDirect(fd, address + offset, length);
-                } else {
-                    // target is assured to have array.
-                    byte[] array = target.array();
-                    offset += target.arrayOffset();
-                    readCount = Platform.NETWORK.read(fd, array, offset, length);
+                readCount = IoBridge.recvfrom(true, fd, dst, 0, null, false);
+                if (readCount > 0) {
+                    dst.position(dst.position() + readCount);
                 }
-                return readCount;
             } finally {
                 if (isBlocking()) {
                     end(readCount > 0);
                 }
             }
+            return readCount;
         }
     }
 
     @Override
-    public int write(ByteBuffer source) throws IOException {
-        if (source == null) {
+    public int write(ByteBuffer src) throws IOException {
+        if (src == null) {
             throw new NullPointerException();
         }
         checkOpenConnected();
-        if (!source.hasRemaining()) {
+        if (!src.hasRemaining()) {
             return 0;
         }
-        return writeImpl(source);
+        return writeImpl(src);
     }
 
     @Override
@@ -388,33 +351,20 @@
         return written;
     }
 
-    /*
-     * Write the source. return the count of bytes written.
-     */
-    private int writeImpl(ByteBuffer source) throws IOException {
+    private int writeImpl(ByteBuffer src) throws IOException {
         synchronized (writeLock) {
-            if (!source.hasRemaining()) {
+            if (!src.hasRemaining()) {
                 return 0;
             }
             int writeCount = 0;
             try {
-                int pos = source.position();
-                int length = source.remaining();
                 if (isBlocking()) {
                     begin();
                 }
-                if (source.isDirect()) {
-                    int address = NioUtils.getDirectBufferAddress(source);
-                    writeCount = Platform.NETWORK.writeDirect(fd, address, pos, length);
-                } else if (source.hasArray()) {
-                    pos += source.arrayOffset();
-                    writeCount = Platform.NETWORK.write(fd, source.array(), pos, length);
-                } else {
-                    byte[] array = new byte[length];
-                    source.get(array);
-                    writeCount = Platform.NETWORK.write(fd, array, 0, length);
+                writeCount = IoBridge.sendto(fd, src, 0, null, 0);
+                if (writeCount > 0) {
+                    src.position(src.position() + writeCount);
                 }
-                source.position(pos + writeCount);
             } finally {
                 if (isBlocking()) {
                     end(writeCount >= 0);
@@ -486,15 +436,14 @@
             if (socket != null && !socket.isClosed()) {
                 socket.close();
             } else {
-                Platform.NETWORK.close(fd);
+                IoBridge.closeSocket(fd);
             }
         }
     }
 
-    @Override
-    protected void implConfigureBlocking(boolean blockMode) throws IOException {
+    @Override protected void implConfigureBlocking(boolean blocking) throws IOException {
         synchronized (blockingLock()) {
-            IoUtils.setBlocking(fd, blockMode);
+            IoUtils.setBlocking(fd, blocking);
         }
     }
 
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 8e98a02..ab76e8e 100644
--- a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
+++ b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
@@ -250,15 +250,13 @@
     }
 
     /**
-     * Implements the setting of the blocking mode.
+     * Implements the configuration of blocking/non-blocking mode.
      *
-     * @param blockingMode
-     *            {@code true} for setting this channel's mode to blocking,
-     *            {@code false} to set it to non-blocking.
+     * @param blocking true for blocking, false for non-blocking.
      * @throws IOException
      *             if an I/O error occurs.
      */
-    protected abstract void implConfigureBlocking(boolean blockingMode) throws IOException;
+    protected abstract void implConfigureBlocking(boolean blocking) throws IOException;
 
     /*
      * package private for deregister method in AbstractSelector.
diff --git a/luni/src/main/java/java/security/KeyStore.java b/luni/src/main/java/java/security/KeyStore.java
index 40f7f9d..c233a5b 100644
--- a/luni/src/main/java/java/security/KeyStore.java
+++ b/luni/src/main/java/java/security/KeyStore.java
@@ -1226,8 +1226,8 @@
             // clone chain - this.chain = (Certificate[])chain.clone();
             boolean isAllX509Certificates = true;
             // assert chain length > 0
-            for(Certificate cert: chain){
-                if(!(cert instanceof X509Certificate)){
+            for (Certificate cert: chain) {
+                if (!(cert instanceof X509Certificate)) {
                     isAllX509Certificates = false;
                     break;
                 }
diff --git a/luni/src/main/java/java/sql/Date.java b/luni/src/main/java/java/sql/Date.java
index adc35aa..2434fbd 100644
--- a/luni/src/main/java/java/sql/Date.java
+++ b/luni/src/main/java/java/sql/Date.java
@@ -32,6 +32,7 @@
  * java.sql.Date} class are "normalized" to the time 00:00:00.000 GMT on the
  * date implied by the time value.
  */
+@FindBugsSuppressWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS")
 public class Date extends java.util.Date {
 
     private static final long serialVersionUID = 1511598038487230103L;
diff --git a/luni/src/main/java/java/util/AbstractList.java b/luni/src/main/java/java/util/AbstractList.java
index f6e5903..3b522c0 100644
--- a/luni/src/main/java/java/util/AbstractList.java
+++ b/luni/src/main/java/java/util/AbstractList.java
@@ -27,8 +27,7 @@
  *
  * @since 1.2
  */
-public abstract class AbstractList<E> extends AbstractCollection<E> implements
-        List<E> {
+public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
 
     /**
      * A counter for changes to the list.
diff --git a/luni/src/main/java/java/util/EventObject.java b/luni/src/main/java/java/util/EventObject.java
index e8a169f..058e7e8 100644
--- a/luni/src/main/java/java/util/EventObject.java
+++ b/luni/src/main/java/java/util/EventObject.java
@@ -30,9 +30,7 @@
 
     private static final long serialVersionUID = 5516075349620653480L;
 
-    /**
-     * The event source.
-     */
+    @FindBugsSuppressWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
     protected transient Object source;
 
     /**
@@ -42,17 +40,14 @@
      *            the object which fired the event.
      */
     public EventObject(Object source) {
-        if (source != null) {
-            this.source = source;
-        } else {
+        if (source == null) {
             throw new IllegalArgumentException();
         }
+        this.source = source;
     }
 
     /**
-     * Returns the event source.
-     *
-     * @return the object which fired the event.
+     * Returns the object which fired the event.
      */
     public Object getSource() {
         return source;
@@ -60,11 +55,8 @@
 
     /**
      * Returns the string representation of this {@code EventObject}.
-     *
-     * @return the string representation of this {@code EventObject}.
      */
-    @Override
-    public String toString() {
+    @Override public String toString() {
         return getClass().getName() + "[source=" + source + ']';
     }
 }
diff --git a/luni/src/main/java/java/util/ResourceBundle.java b/luni/src/main/java/java/util/ResourceBundle.java
index a34b34a..ff38b5b 100644
--- a/luni/src/main/java/java/util/ResourceBundle.java
+++ b/luni/src/main/java/java/util/ResourceBundle.java
@@ -24,6 +24,8 @@
 import java.io.InputStreamReader;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.charset.Charsets;
+import static java.nio.charset.Charsets.UTF_8;
 import libcore.io.IoUtils;
 
 /**
@@ -514,7 +516,7 @@
                     : ClassLoader.getSystemResourceAsStream(fileName);
             if (stream != null) {
                 try {
-                    bundle = new PropertyResourceBundle(new InputStreamReader(stream));
+                    bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8));
                     bundle.setLocale(locale);
                 } catch (IOException ignored) {
                 } finally {
diff --git a/luni/src/main/java/java/util/TreeMap.java b/luni/src/main/java/java/util/TreeMap.java
index 8d878cd..7809110 100644
--- a/luni/src/main/java/java/util/TreeMap.java
+++ b/luni/src/main/java/java/util/TreeMap.java
@@ -1094,11 +1094,11 @@
      * A map with optional limits on its range.
      */
     final class BoundedMap extends AbstractMap<K, V> implements NavigableMap<K, V>, Serializable {
-        private final boolean ascending;
-        private final K from;
-        private final Bound fromBound;
-        private final K to;
-        private final Bound toBound;
+        private final transient boolean ascending;
+        private final transient K from;
+        private final transient Bound fromBound;
+        private final transient K to;
+        private final transient Bound toBound;
 
         BoundedMap(boolean ascending, K from, Bound fromBound, K to, Bound toBound) {
             /*
@@ -1375,8 +1375,8 @@
          * View factory methods.
          */
 
-        private BoundedEntrySet entrySet;
-        private BoundedKeySet keySet;
+        private transient BoundedEntrySet entrySet;
+        private transient BoundedKeySet keySet;
 
         @Override public Set<Entry<K, V>> entrySet() {
             BoundedEntrySet result = entrySet;
diff --git a/luni/src/main/java/java/util/TreeSet.java b/luni/src/main/java/java/util/TreeSet.java
index 63a51c9..502329e 100644
--- a/luni/src/main/java/java/util/TreeSet.java
+++ b/luni/src/main/java/java/util/TreeSet.java
@@ -490,7 +490,7 @@
                 (Comparator<? super E>) stream.readObject());
         int size = stream.readInt();
         if (size > 0) {
-            for(int i=0; i<size; i++) {
+            for (int i=0; i<size; i++) {
                 E elem = (E)stream.readObject();
                 map.put(elem, Boolean.TRUE);
             }
diff --git a/luni/src/main/java/java/util/UnsafeArrayList.java b/luni/src/main/java/java/util/UnsafeArrayList.java
new file mode 100644
index 0000000..106e73e
--- /dev/null
+++ b/luni/src/main/java/java/util/UnsafeArrayList.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Array;
+
+/**
+ * An array-backed list that exposes its array.
+ *
+ * @hide
+ */
+public class UnsafeArrayList<T> extends AbstractList<T> {
+    private final Class<T> elementType;
+    private T[] array;
+    private int size;
+
+    public UnsafeArrayList(Class<T> elementType, int initialCapacity) {
+        this.array = (T[]) Array.newInstance(elementType, initialCapacity);
+        this.elementType = elementType;
+    }
+
+    @Override public boolean add(T element) {
+        if (size == array.length) {
+            T[] newArray = (T[]) Array.newInstance(elementType, size * 2);
+            System.arraycopy(array, 0, newArray, 0, size);
+            array = newArray;
+        }
+        array[size++] = element;
+        ++modCount;
+        return true;
+    }
+
+    public T[] array() {
+        return array;
+    }
+
+    public T get(int i) {
+        return array[i];
+    }
+
+    public int size() {
+        return size;
+    }
+}
diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java
index a26a602..e129e82 100644
--- a/luni/src/main/java/java/util/jar/JarFile.java
+++ b/luni/src/main/java/java/util/jar/JarFile.java
@@ -289,11 +289,7 @@
         try {
             InputStream is = super.getInputStream(manifestEntry);
             if (verifier != null) {
-                try {
-                    verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is));
-                } finally {
-                    is.close();
-                }
+                verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is));
                 is = super.getInputStream(manifestEntry);
             }
             try {
@@ -344,11 +340,7 @@
                                 || endsWithIgnoreCase(entryName, ".RSA"))) {
                     signed = true;
                     InputStream is = super.getInputStream(entry);
-                    try {
-                        verifier.addMetaEntry(entryName, Streams.readFully(is));
-                    } finally {
-                        is.close();
-                    }
+                    verifier.addMetaEntry(entryName, Streams.readFully(is));
                 }
             }
         }
diff --git a/luni/src/main/java/java/util/jar/JarVerifier.java b/luni/src/main/java/java/util/jar/JarVerifier.java
index 81bbe77..ec0e088 100644
--- a/luni/src/main/java/java/util/jar/JarVerifier.java
+++ b/luni/src/main/java/java/util/jar/JarVerifier.java
@@ -33,7 +33,7 @@
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
 import org.apache.harmony.security.utils.JarUtils;
 
 /**
diff --git a/luni/src/main/java/java/util/prefs/AbstractPreferences.java b/luni/src/main/java/java/util/prefs/AbstractPreferences.java
index 06bef54..d86d789 100644
--- a/luni/src/main/java/java/util/prefs/AbstractPreferences.java
+++ b/luni/src/main/java/java/util/prefs/AbstractPreferences.java
@@ -24,12 +24,11 @@
 import java.util.EventListener;
 import java.util.EventObject;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
 import libcore.util.EmptyArray;
 
 /**
@@ -193,8 +192,8 @@
             checkState();
             AbstractPreferences result = null;
             String[] childrenNames = childrenNames();
-            for (int i = 0; i < childrenNames.length; i++) {
-                if (childrenNames[i].equals(name)) {
+            for (String childrenName : childrenNames) {
+                if (childrenName.equals(name)) {
                     result = childSpi(name);
                     break;
                 }
@@ -364,9 +363,8 @@
     @Override
     public void clear() throws BackingStoreException {
         synchronized (lock) {
-            String[] keyList = keys();
-            for (int i = 0; i < keyList.length; i++) {
-                remove(keyList[i]);
+            for (String key : keys()) {
+                remove(key);
             }
         }
     }
@@ -562,20 +560,17 @@
             throws BackingStoreException {
         String[] names = path.split("/");
         AbstractPreferences currentNode = this;
-        AbstractPreferences temp = null;
-        if (currentNode != null) {
-            for (int i = 0; i < names.length; i++) {
-                String name = names[i];
-                synchronized (currentNode.lock) {
-                    temp = currentNode.cachedNode.get(name);
-                    if (temp == null) {
-                        temp = getNodeFromBackend(createNew, currentNode, name);
-                    }
+        AbstractPreferences temp;
+        for (String name : names) {
+            synchronized (currentNode.lock) {
+                temp = currentNode.cachedNode.get(name);
+                if (temp == null) {
+                    temp = getNodeFromBackend(createNew, currentNode, name);
                 }
-                currentNode = temp;
-                if (currentNode == null) {
-                    break;
-                }
+            }
+            currentNode = temp;
+            if (currentNode == null) {
+                break;
             }
         }
         return currentNode;
@@ -660,37 +655,32 @@
 
     @Override
     public void putBoolean(String key, boolean value) {
-        String sval = String.valueOf(value);
-        put(key, sval);
+        put(key, String.valueOf(value));
     }
 
     @Override
     public void putByteArray(String key, byte[] value) {
-        put(key, Base64.encode(value, Charsets.US_ASCII));
+        put(key, Base64.encode(value));
     }
 
     @Override
     public void putDouble(String key, double value) {
-        String sval = Double.toString(value);
-        put(key, sval);
+        put(key, Double.toString(value));
     }
 
     @Override
     public void putFloat(String key, float value) {
-        String sval = Float.toString(value);
-        put(key, sval);
+        put(key, Float.toString(value));
     }
 
     @Override
     public void putInt(String key, int value) {
-        String sval = Integer.toString(value);
-        put(key, sval);
+        put(key, Integer.toString(value));
     }
 
     @Override
     public void putLong(String key, long value) {
-        String sval = Long.toString(value);
-        put(key, sval);
+        put(key, Long.toString(value));
     }
 
     @Override
@@ -716,10 +706,10 @@
         synchronized (lock) {
             checkState();
             String[] childrenNames = childrenNamesSpi();
-            for (int i = 0; i < childrenNames.length; i++) {
-                if (cachedNode.get(childrenNames[i]) == null) {
-                    AbstractPreferences child = childSpi(childrenNames[i]);
-                    cachedNode.put(childrenNames[i], child);
+            for (String childrenName : childrenNames) {
+                if (cachedNode.get(childrenName) == null) {
+                    AbstractPreferences child = childSpi(childrenName);
+                    cachedNode.put(childrenName, child);
                 }
             }
 
@@ -831,15 +821,14 @@
         @Override
         public void run() {
             while (true) {
-                EventObject event = null;
+                EventObject event;
                 try {
                     event = getEventObject();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                     continue;
                 }
-                AbstractPreferences pref = (AbstractPreferences) event
-                        .getSource();
+                AbstractPreferences pref = (AbstractPreferences) event.getSource();
                 if (event instanceof NodeAddEvent) {
                     dispatchNodeAdd((NodeChangeEvent) event,
                             pref.nodeChangeListeners);
@@ -867,11 +856,8 @@
         private void dispatchPrefChange(PreferenceChangeEvent event,
                 List<EventListener> preferenceChangeListeners) {
             synchronized (preferenceChangeListeners) {
-                Iterator<EventListener> i = preferenceChangeListeners.iterator();
-                while (i.hasNext()) {
-                    PreferenceChangeListener pcl = (PreferenceChangeListener) i
-                            .next();
-                    pcl.preferenceChange(event);
+                for (EventListener preferenceChangeListener : preferenceChangeListeners) {
+                    ((PreferenceChangeListener) preferenceChangeListener).preferenceChange(event);
                 }
             }
         }
@@ -879,10 +865,8 @@
         private void dispatchNodeRemove(NodeChangeEvent event,
                 List<EventListener> nodeChangeListeners) {
             synchronized (nodeChangeListeners) {
-                Iterator<EventListener> i = nodeChangeListeners.iterator();
-                while (i.hasNext()) {
-                    NodeChangeListener ncl = (NodeChangeListener) i.next();
-                    ncl.childRemoved(event);
+                for (EventListener nodeChangeListener : nodeChangeListeners) {
+                    ((NodeChangeListener) nodeChangeListener).childRemoved(event);
                 }
             }
         }
@@ -890,9 +874,8 @@
         private void dispatchNodeAdd(NodeChangeEvent event,
                 List<EventListener> nodeChangeListeners) {
             synchronized (nodeChangeListeners) {
-                Iterator<EventListener> i = nodeChangeListeners.iterator();
-                while (i.hasNext()) {
-                    NodeChangeListener ncl = (NodeChangeListener) i.next();
+                for (EventListener nodeChangeListener : nodeChangeListeners) {
+                    NodeChangeListener ncl = (NodeChangeListener) nodeChangeListener;
                     ncl.childAdded(event);
                 }
             }
diff --git a/luni/src/main/java/java/util/prefs/XMLParser.java b/luni/src/main/java/java/util/prefs/XMLParser.java
index 6546b76..6e25c7d 100644
--- a/luni/src/main/java/java/util/prefs/XMLParser.java
+++ b/luni/src/main/java/java/util/prefs/XMLParser.java
@@ -405,7 +405,7 @@
         NodeList childNodes = documentElement.getChildNodes();
 
         if(path[0].equals("entry") || path[0].equals("node")) {
-            for(int i = 0; i < childNodes.getLength(); i++) {
+            for (int i = 0; i < childNodes.getLength(); i++) {
                 Object next = childNodes.item(i);
                 if(next instanceof Element) {
                     if(((Element) next).getNodeName().equals(path[0])) {
@@ -414,12 +414,12 @@
                 }
             }
         } else if(path[0].equals("map") && path[1].equals("entry")) {
-            for(int i = 0; i < childNodes.getLength(); i++) {
+            for (int i = 0; i < childNodes.getLength(); i++) {
                 Object next = childNodes.item(i);
                 if(next instanceof Element) {
                     if(((Element) next).getNodeName().equals(path[0])) {
                         NodeList nextChildNodes = ((Node)next).getChildNodes();
-                        for(int j = 0; j < nextChildNodes.getLength(); j++) {
+                        for (int j = 0; j < nextChildNodes.getLength(); j++) {
                             Object subnext = nextChildNodes.item(j);
                             if(subnext instanceof Element) {
                                 if(((Element)subnext).getNodeName().equals(path[1])) {
diff --git a/luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java b/luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java
new file mode 100644
index 0000000..62eec24
--- /dev/null
+++ b/luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java
@@ -0,0 +1,26 @@
+/*
+ * 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.io.FileDescriptor;
+
+public final class AsynchronousCloseMonitor {
+    private AsynchronousCloseMonitor() {
+    }
+
+    public static native void signalBlockedThreads(FileDescriptor fd);
+}
diff --git a/luni/src/main/java/org/apache/harmony/luni/util/Base64.java b/luni/src/main/java/libcore/io/Base64.java
similarity index 87%
rename from luni/src/main/java/org/apache/harmony/luni/util/Base64.java
rename to luni/src/main/java/libcore/io/Base64.java
index 4c9b509..e4643b5 100644
--- a/luni/src/main/java/org/apache/harmony/luni/util/Base64.java
+++ b/luni/src/main/java/libcore/io/Base64.java
@@ -19,16 +19,16 @@
 * @author Alexander Y. Kleymenov
 */
 
-package org.apache.harmony.luni.util;
+package libcore.io;
 
-import java.nio.charset.Charset;
+import java.nio.charset.Charsets;
 import libcore.util.EmptyArray;
 
 /**
- * This class implements Base64 encoding/decoding functionality
- * as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt).
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder.
+ * In violation of the RFC, this encoder doesn't wrap lines at 76 columns.
  */
-public class Base64 {
+public final class Base64 {
     private Base64() {
     }
 
@@ -132,22 +132,15 @@
          'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
          '4', '5', '6', '7', '8', '9', '+', '/'};
 
-    public static String encode(byte[] in, Charset charset) {
-        int length = in.length * 4 / 3;
-        length += length / 76 + 3; // for crlr
+    public static String encode(byte[] in) {
+        int length = (in.length + 2) * 4 / 3;
         byte[] out = new byte[length];
-        int index = 0, i, crlr = 0, end = in.length - in.length%3;
-        for (i=0; i<end; i+=3) {
+        int index = 0, end = in.length - in.length % 3;
+        for (int i = 0; i < end; i += 3) {
             out[index++] = map[(in[i] & 0xff) >> 2];
             out[index++] = map[((in[i] & 0x03) << 4) | ((in[i+1] & 0xff) >> 4)];
             out[index++] = map[((in[i+1] & 0x0f) << 2) | ((in[i+2] & 0xff) >> 6)];
             out[index++] = map[(in[i+2] & 0x3f)];
-            if (((index - crlr)%76 == 0) && (index != 0)) {
-                out[index++] = '\n';
-                crlr++;
-                //out[index++] = '\r';
-                //crlr++;
-            }
         }
         switch (in.length % 3) {
             case 1:
@@ -163,6 +156,6 @@
                 out[index++] = '=';
                 break;
         }
-        return new String(out, 0, index, charset);
+        return new String(out, 0, index, Charsets.US_ASCII);
     }
 }
diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java
index cda6a58..e77e0bb 100644
--- a/luni/src/main/java/libcore/io/BlockGuardOs.java
+++ b/luni/src/main/java/libcore/io/BlockGuardOs.java
@@ -18,6 +18,8 @@
 
 import dalvik.system.BlockGuard;
 import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import static libcore.io.OsConstants.*;
@@ -30,26 +32,65 @@
         super(os);
     }
 
-    @Override
-    public void fdatasync(FileDescriptor fd) throws ErrnoException {
+    private FileDescriptor tagSocket(FileDescriptor fd) {
+        try {
+            BlockGuard.tagSocketFd(fd);
+            return fd;
+        } catch (SocketException e) {
+            throw new ErrnoException("socket", EINVAL, e);
+        }
+    }
+
+    @Override public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException {
+        BlockGuard.getThreadPolicy().onNetwork();
+        return tagSocket(os.accept(fd, peerAddress));
+    }
+
+    @Override public void close(FileDescriptor fd) throws ErrnoException {
+        // TODO: is there a way to avoid calling getsockopt(2) on non-socket fds?
+        if (isLingerSocket(fd)) {
+            // If the fd is a socket with SO_LINGER set, we might block indefinitely.
+            // We allow non-linger sockets so that apps can close their network connections in
+            // methods like onDestroy which will run on the UI thread.
+            BlockGuard.getThreadPolicy().onNetwork();
+        }
+        os.close(fd);
+    }
+
+    private static boolean isLingerSocket(FileDescriptor fd) {
+        try {
+            StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
+            return linger.isOn() && linger.l_linger > 0;
+        } catch (ErrnoException ignored) {
+            // We're called via Socket.close (which doesn't ask for us to be called), so we
+            // must not throw here, because Socket.close must not throw if asked to close an
+            // already-closed socket. Also, the passed-in FileDescriptor isn't necessarily
+            // a socket at all.
+            return false;
+        }
+    }
+
+    @Override public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException {
+        BlockGuard.getThreadPolicy().onNetwork();
+        os.connect(fd, address, port);
+    }
+
+    @Override public void fdatasync(FileDescriptor fd) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         os.fdatasync(fd);
     }
 
-    @Override
-    public void fsync(FileDescriptor fd) throws ErrnoException {
+    @Override public void fsync(FileDescriptor fd) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         os.fsync(fd);
     }
 
-    @Override
-    public void ftruncate(FileDescriptor fd, long length) throws ErrnoException {
+    @Override public void ftruncate(FileDescriptor fd, long length) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         os.ftruncate(fd, length);
     }
 
-    @Override
-    public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
+    @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
         BlockGuard.getThreadPolicy().onReadFromDisk();
         if ((mode & O_ACCMODE) != O_RDONLY) {
             BlockGuard.getThreadPolicy().onWriteToDisk();
@@ -57,49 +98,68 @@
         return os.open(path, flags, mode);
     }
 
-    @Override
-    public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+    @Override public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException {
+        // Greater than 0 is a timeout in milliseconds and -1 means "block forever",
+        // but 0 means "poll and return immediately", which shouldn't be subject to BlockGuard.
+        if (timeoutMs != 0) {
+            BlockGuard.getThreadPolicy().onNetwork();
+        }
+        return os.poll(fds, timeoutMs);
+    }
+
+    @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         BlockGuard.getThreadPolicy().onReadFromDisk();
         return os.read(fd, buffer);
     }
 
-    @Override
-    public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+    @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
         BlockGuard.getThreadPolicy().onReadFromDisk();
         return os.read(fd, bytes, byteOffset, byteCount);
     }
 
-    @Override
-    public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
+    @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
         BlockGuard.getThreadPolicy().onReadFromDisk();
         return os.readv(fd, buffers, offsets, byteCounts);
     }
 
-    @Override
-    public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException {
-        final FileDescriptor fd = os.socket(domain, type, protocol);
-        try {
-            BlockGuard.tagSocketFd(fd);
-        } catch (SocketException e) {
-            throw new ErrnoException("socket", EINVAL, e);
-        }
-        return fd;
+    @Override public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException {
+        BlockGuard.getThreadPolicy().onNetwork();
+        return os.recvfrom(fd, buffer, flags, srcAddress);
     }
 
-    @Override
-    public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
+    @Override public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException {
+        BlockGuard.getThreadPolicy().onNetwork();
+        return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);
+    }
+
+    @Override public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException {
+        BlockGuard.getThreadPolicy().onNetwork();
+        return os.sendto(fd, buffer, flags, inetAddress, port);
+    }
+
+    @Override public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException {
+        // We permit datagrams without hostname lookups.
+        if (inetAddress != null) {
+            BlockGuard.getThreadPolicy().onNetwork();
+        }
+        return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);
+    }
+
+    @Override public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException {
+        return tagSocket(os.socket(domain, type, protocol));
+    }
+
+    @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         return os.write(fd, buffer);
     }
 
-    @Override
-    public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+    @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         return os.write(fd, bytes, byteOffset, byteCount);
     }
 
-    @Override
-    public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
+    @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         return os.writev(fd, buffers, offsets, byteCounts);
     }
diff --git a/luni/src/main/java/libcore/io/DiskLruCache.java b/luni/src/main/java/libcore/io/DiskLruCache.java
new file mode 100644
index 0000000..c887541
--- /dev/null
+++ b/luni/src/main/java/libcore/io/DiskLruCache.java
@@ -0,0 +1,804 @@
+/*
+ * 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.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Values are byte
+ * sequences, accessible as streams or files. Each value must be between {@code
+ * 0} and {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ *     <li>When an entry is being <strong>created</strong> it is necessary to
+ *         supply a full set of values; the empty value should be used as a
+ *         placeholder if necessary.
+ *     <li>When an entry is being <strong>created</strong>, it is not necessary
+ *         to supply data for every value; values default to their previous
+ *         value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+    static final String JOURNAL_FILE = "journal";
+    static final String JOURNAL_FILE_TMP = "journal.tmp";
+    static final String MAGIC = "libcore.io.DiskLruCache";
+    static final String VERSION_1 = "1";
+    private static final String CLEAN = "CLEAN";
+    private static final String DIRTY = "DIRTY";
+    private static final String REMOVE = "REMOVE";
+    private static final String READ = "READ";
+
+    /*
+     * This cache uses a journal file named "journal". A typical journal file
+     * looks like this:
+     *     libcore.io.DiskLruCache
+     *     1
+     *     100
+     *     2
+     *
+     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
+     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
+     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
+     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+     *     READ 335c4c6028171cfddfbaae1a9c313c52
+     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+     *
+     * The first five lines of the journal form its header. They are the
+     * constant string "libcore.io.DiskLruCache", the disk cache's version,
+     * the application's version, the value count, and a blank line.
+     *
+     * Each of the subsequent lines in the file is a record of the state of a
+     * cache entry. Each line contains space-separated values: a state, a key,
+     * and optional state-specific values.
+     *   o DIRTY lines track that an entry is actively being created or updated.
+     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
+     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+     *     temporary files may need to be deleted.
+     *   o CLEAN lines track a cache entry that has been successfully published
+     *     and may be read. A publish line is followed by the lengths of each of
+     *     its values.
+     *   o READ lines track accesses for LRU.
+     *   o REMOVE lines track entries that have been deleted.
+     *
+     * The journal file is appended to as cache operations occur. The journal may
+     * occasionally be compacted by dropping redundant lines. A temporary file named
+     * "journal.tmp" will be used during compaction; that file should be deleted if
+     * it exists when the cache is opened.
+     */
+
+    private final File directory;
+    private final File journalFile;
+    private final File journalFileTmp;
+    private final int appVersion;
+    private final long maxSize;
+    private final int valueCount;
+    private long size = 0;
+    private Writer journalWriter;
+    private final LinkedHashMap<String, Entry> lruEntries
+            = new LinkedHashMap<String, Entry>(0, 0.75f, true);
+    private int redundantOpCount;
+
+    /** This cache uses a single background thread to evict entries. */
+    private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
+            60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+    private final Callable<Void> cleanupCallable = new Callable<Void>() {
+        @Override public Void call() throws Exception {
+            synchronized (DiskLruCache.this) {
+                if (journalWriter == null) {
+                    return null; // closed
+                }
+                trimToSize();
+                if (journalRebuildRequired()) {
+                    rebuildJournal();
+                    redundantOpCount = 0;
+                }
+            }
+            return null;
+        }
+    };
+
+    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+        this.directory = directory;
+        this.appVersion = appVersion;
+        this.journalFile = new File(directory, JOURNAL_FILE);
+        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
+        this.valueCount = valueCount;
+        this.maxSize = maxSize;
+    }
+
+    /**
+     * Opens the cache in {@code directory}, creating a cache if none exists
+     * there.
+     *
+     * @param directory a writable directory
+     * @param appVersion
+     * @param valueCount the number of values per cache entry. Must be positive.
+     * @param maxSize the maximum number of bytes this cache should use to store
+     * @throws IOException if reading or writing the cache directory fails
+     */
+    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+            throws IOException {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+        if (valueCount <= 0) {
+            throw new IllegalArgumentException("valueCount <= 0");
+        }
+
+        // prefer to pick up where we left off
+        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+        if (cache.journalFile.exists()) {
+            try {
+                cache.readJournal();
+                cache.processJournal();
+                cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true));
+                return cache;
+            } catch (IOException journalIsCorrupt) {
+                System.logW("DiskLruCache " + directory + " is corrupt: "
+                        + journalIsCorrupt.getMessage() + ", removing");
+                cache.delete();
+            }
+        }
+
+        // create a new empty cache
+        directory.mkdirs();
+        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+        cache.rebuildJournal();
+        return cache;
+    }
+
+    private void readJournal() throws IOException {
+        InputStream in = new BufferedInputStream(new FileInputStream(journalFile));
+        try {
+            String magic = Streams.readAsciiLine(in);
+            String version = Streams.readAsciiLine(in);
+            String appVersionString = Streams.readAsciiLine(in);
+            String valueCountString = Streams.readAsciiLine(in);
+            String blank = Streams.readAsciiLine(in);
+            if (!MAGIC.equals(magic)
+                    || !VERSION_1.equals(version)
+                    || !Integer.toString(appVersion).equals(appVersionString)
+                    || !Integer.toString(valueCount).equals(valueCountString)
+                    || !"".equals(blank)) {
+                throw new IOException("unexpected journal header: ["
+                        + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
+            }
+
+            while (true) {
+                try {
+                    readJournalLine(Streams.readAsciiLine(in));
+                } catch (EOFException endOfJournal) {
+                    break;
+                }
+            }
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    private void readJournalLine(String line) throws IOException {
+        String[] parts = line.split(" ");
+        if (parts.length < 2) {
+            throw new IOException("unexpected journal line: " + line);
+        }
+
+        String key = parts[1];
+        if (parts[0].equals(REMOVE) && parts.length == 2) {
+            lruEntries.remove(key);
+            return;
+        }
+
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            entry = new Entry(key);
+            lruEntries.put(key, entry);
+        }
+
+        if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
+            entry.readable = true;
+            entry.currentEditor = null;
+            entry.setLengths(Arrays.copyOfRange(parts, 2, parts.length));
+        } else if (parts[0].equals(DIRTY) && parts.length == 2) {
+            entry.currentEditor = new Editor(entry);
+        } else if (parts[0].equals(READ) && parts.length == 2) {
+            // this work was already done by calling lruEntries.get()
+        } else {
+            throw new IOException("unexpected journal line: " + line);
+        }
+    }
+
+    /**
+     * Computes the initial size and collects garbage as a part of opening the
+     * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+     */
+    private void processJournal() throws IOException {
+        deleteIfExists(journalFileTmp);
+        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+            Entry entry = i.next();
+            if (entry.currentEditor == null) {
+                for (int t = 0; t < valueCount; t++) {
+                    size += entry.lengths[t];
+                }
+            } else {
+                entry.currentEditor = null;
+                for (int t = 0; t < valueCount; t++) {
+                    deleteIfExists(entry.getCleanFile(t));
+                    deleteIfExists(entry.getDirtyFile(t));
+                }
+                i.remove();
+            }
+        }
+    }
+
+    /**
+     * Creates a new journal that omits redundant information. This replaces the
+     * current journal if it exists.
+     */
+    private synchronized void rebuildJournal() throws IOException {
+        if (journalWriter != null) {
+            journalWriter.close();
+        }
+
+        Writer writer = new BufferedWriter(new FileWriter(journalFileTmp));
+        writer.write(MAGIC);
+        writer.write("\n");
+        writer.write(VERSION_1);
+        writer.write("\n");
+        writer.write(Integer.toString(appVersion));
+        writer.write("\n");
+        writer.write(Integer.toString(valueCount));
+        writer.write("\n");
+        writer.write("\n");
+
+        for (Entry entry : lruEntries.values()) {
+            if (entry.currentEditor != null) {
+                writer.write(DIRTY + ' ' + entry.key + '\n');
+            } else {
+                writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+            }
+        }
+
+        writer.close();
+        journalFileTmp.renameTo(journalFile);
+        journalWriter = new BufferedWriter(new FileWriter(journalFile, true));
+    }
+
+    private static void deleteIfExists(File file) throws IOException {
+        try {
+            Libcore.os.remove(file.getPath());
+        } catch (ErrnoException e) {
+            if (e.errno != OsConstants.ENOENT) {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+     * exist is not currently readable. If a value is returned, it is moved to
+     * the head of the LRU queue.
+     */
+    public synchronized Snapshot get(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            return null;
+        }
+
+        if (!entry.readable) {
+            return null;
+        }
+
+        /*
+         * Open all streams eagerly to guarantee that we see a single published
+         * snapshot. If we opened streams lazily then the streams could come
+         * from different edits.
+         */
+        InputStream[] ins = new InputStream[valueCount];
+        try {
+            for (int i = 0; i < valueCount; i++) {
+                ins[i] = new FileInputStream(entry.getCleanFile(i));
+            }
+        } catch (FileNotFoundException e) {
+            // a file must have been deleted manually!
+            return null;
+        }
+
+        redundantOpCount++;
+        journalWriter.append(READ + ' ' + key + '\n');
+        if (journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+
+        return new Snapshot(ins);
+    }
+
+    /**
+     * Returns an editor for the entry named {@code key}, or null if it cannot
+     * currently be edited.
+     */
+    public synchronized Editor edit(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            entry = new Entry(key);
+            lruEntries.put(key, entry);
+        } else if (entry.currentEditor != null) {
+            return null;
+        }
+
+        Editor editor = new Editor(entry);
+        entry.currentEditor = editor;
+
+        // flush the journal before creating files to prevent file leaks
+        journalWriter.write(DIRTY + ' ' + key + '\n');
+        journalWriter.flush();
+        return editor;
+    }
+
+    /**
+     * Returns the directory where this cache stores its data.
+     */
+    public File getDirectory() {
+        return directory;
+    }
+
+    /**
+     * Returns the maximum number of bytes that this cache should use to store
+     * its data.
+     */
+    public long maxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Returns the number of bytes currently being used to store the values in
+     * this cache. This may be greater than the max size if a background
+     * deletion is pending.
+     */
+    public synchronized long size() {
+        return size;
+    }
+
+    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+        Entry entry = editor.entry;
+        if (entry.currentEditor != editor) {
+            throw new IllegalStateException();
+        }
+
+        // if this edit is creating the entry for the first time, every index must have a value
+        if (success && !entry.readable) {
+            for (int i = 0; i < valueCount; i++) {
+                if (!entry.getDirtyFile(i).exists()) {
+                    editor.abort();
+                    throw new IllegalStateException("edit didn't create file " + i);
+                }
+            }
+        }
+
+        for (int i = 0; i < valueCount; i++) {
+            File dirty = entry.getDirtyFile(i);
+            if (success) {
+                if (dirty.exists()) {
+                    File clean = entry.getCleanFile(i);
+                    dirty.renameTo(clean);
+                    long oldLength = entry.lengths[i];
+                    long newLength = clean.length();
+                    entry.lengths[i] = newLength;
+                    size = size - oldLength + newLength;
+                }
+            } else {
+                deleteIfExists(dirty);
+            }
+        }
+
+        redundantOpCount++;
+        entry.currentEditor = null;
+        if (entry.readable | success) {
+            entry.readable = true;
+            journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+        } else {
+            lruEntries.remove(entry.key);
+            journalWriter.write(REMOVE + ' ' + entry.key + '\n');
+        }
+
+        if (size > maxSize || journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+    }
+
+    /**
+     * We only rebuild the journal when it will halve the size of the journal
+     * and eliminate at least 2000 ops.
+     */
+    private boolean journalRebuildRequired() {
+        final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
+        return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
+                && redundantOpCount >= lruEntries.size();
+    }
+
+    /**
+     * Drops the entry for {@code key} if it exists and can be removed. Entries
+     * actively being edited cannot be removed.
+     *
+     * @return true if an entry was removed.
+     */
+    public synchronized boolean remove(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null || entry.currentEditor != null) {
+            return false;
+        }
+
+        for (int i = 0; i < valueCount; i++) {
+            File file = entry.getCleanFile(i);
+            if (!file.delete()) {
+                throw new IOException("failed to delete " + file);
+            }
+            size -= entry.lengths[i];
+            entry.lengths[i] = 0;
+        }
+
+        redundantOpCount++;
+        journalWriter.append(REMOVE + ' ' + key + '\n');
+        lruEntries.remove(key);
+
+        if (journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if this cache has been closed.
+     */
+    public boolean isClosed() {
+        return journalWriter == null;
+    }
+
+    private void checkNotClosed() {
+        if (journalWriter == null) {
+            throw new IllegalStateException("cache is closed");
+        }
+    }
+
+    /**
+     * Force buffered operations to the filesystem.
+     */
+    public synchronized void flush() throws IOException {
+        checkNotClosed();
+        trimToSize();
+        journalWriter.flush();
+    }
+
+    /**
+     * Closes this cache. Stored values will remain on the filesystem.
+     */
+    public synchronized void close() throws IOException {
+        if (journalWriter == null) {
+            return; // already closed
+        }
+        for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
+            if (entry.currentEditor != null) {
+                entry.currentEditor.abort();
+            }
+        }
+        trimToSize();
+        journalWriter.close();
+        journalWriter = null;
+    }
+
+    private void trimToSize() throws IOException {
+        while (size > maxSize) {
+            Map.Entry<String, Entry> toEvict = lruEntries.eldest();
+            remove(toEvict.getKey());
+        }
+    }
+
+    /**
+     * Closes the cache and deletes all of its stored values. This will delete
+     * all files in the cache directory including files that weren't created by
+     * the cache.
+     */
+    public void delete() throws IOException {
+        close();
+        IoUtils.deleteContents(directory);
+    }
+
+    private void validateKey(String key) {
+        if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
+            throw new IllegalArgumentException(
+                    "keys must not contain spaces or newlines: \"" + key + "\"");
+        }
+    }
+
+    private static String inputStreamToString(InputStream in) throws IOException {
+        return Streams.readFully(new InputStreamReader(in, Charsets.UTF_8));
+    }
+
+    /**
+     * A snapshot of the values for an entry.
+     */
+    public static final class Snapshot implements Closeable {
+        private final InputStream[] ins;
+
+        private Snapshot(InputStream[] ins) {
+            this.ins = ins;
+        }
+
+        /**
+         * Returns the unbuffered stream with the value for {@code index}.
+         */
+        public InputStream getInputStream(int index) {
+            return ins[index];
+        }
+
+        /**
+         * Returns the string value for {@code index}.
+         */
+        public String getString(int index) throws IOException {
+            return inputStreamToString(getInputStream(index));
+        }
+
+        @Override public void close() {
+            for (InputStream in : ins) {
+                IoUtils.closeQuietly(in);
+            }
+        }
+    }
+
+    /**
+     * Edits the values for an entry.
+     */
+    public final class Editor {
+        private final Entry entry;
+        private boolean hasErrors;
+
+        private Editor(Entry entry) {
+            this.entry = entry;
+        }
+
+        /**
+         * Returns an unbuffered input stream to read the last committed value,
+         * or null if no value has been committed.
+         */
+        public InputStream newInputStream(int index) throws IOException {
+            synchronized (DiskLruCache.this) {
+                if (entry.currentEditor != this) {
+                    throw new IllegalStateException();
+                }
+                if (!entry.readable) {
+                    return null;
+                }
+                return new FileInputStream(entry.getCleanFile(index));
+            }
+        }
+
+        /**
+         * Returns the last committed value as a string, or null if no value
+         * has been committed.
+         */
+        public String getString(int index) throws IOException {
+            InputStream in = newInputStream(index);
+            return in != null ? inputStreamToString(in) : null;
+        }
+
+        /**
+         * Returns a new unbuffered output stream to write the value at
+         * {@code index}. If the underlying output stream encounters errors
+         * when writing to the filesystem, this edit will be aborted when
+         * {@link #commit} is called. The returned output stream does not throw
+         * IOExceptions.
+         */
+        public OutputStream newOutputStream(int index) throws IOException {
+            synchronized (DiskLruCache.this) {
+                if (entry.currentEditor != this) {
+                    throw new IllegalStateException();
+                }
+                return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
+            }
+        }
+
+        /**
+         * Sets the value at {@code index} to {@code value}.
+         */
+        public void set(int index, String value) throws IOException {
+            Writer writer = null;
+            try {
+                writer = new OutputStreamWriter(newOutputStream(index), Charsets.UTF_8);
+                writer.write(value);
+            } finally {
+                IoUtils.closeQuietly(writer);
+            }
+        }
+
+        /**
+         * Commits this edit so it is visible to readers.  This releases the
+         * edit lock so another edit may be started on the same key.
+         */
+        public void commit() throws IOException {
+            if (hasErrors) {
+                completeEdit(this, false);
+                remove(entry.key); // the previous entry is stale
+            } else {
+                completeEdit(this, true);
+            }
+        }
+
+        /**
+         * Aborts this edit. This releases the edit lock so another edit may be
+         * started on the same key.
+         */
+        public void abort() throws IOException {
+            completeEdit(this, false);
+        }
+
+        private class FaultHidingOutputStream extends FilterOutputStream {
+            private FaultHidingOutputStream(OutputStream out) {
+                super(out);
+            }
+
+            @Override public void write(int oneByte) {
+                try {
+                    out.write(oneByte);
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override public void write(byte[] buffer, int offset, int length) {
+                try {
+                    out.write(buffer, offset, length);
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override public void close() {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override public void flush() {
+                try {
+                    out.flush();
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+        }
+    }
+
+    private final class Entry {
+        private final String key;
+
+        /** Lengths of this entry's files. */
+        private final long[] lengths;
+
+        /** True if this entry has ever been published */
+        private boolean readable;
+
+        /** The ongoing edit or null if this entry is not being edited. */
+        private Editor currentEditor;
+
+        private Entry(String key) {
+            this.key = key;
+            this.lengths = new long[valueCount];
+        }
+
+        public String getLengths() throws IOException {
+            StringBuilder result = new StringBuilder();
+            for (long size : lengths) {
+                result.append(' ').append(size);
+            }
+            return result.toString();
+        }
+
+        /**
+         * Set lengths using decimal numbers like "10123".
+         */
+        private void setLengths(String[] strings) throws IOException {
+            if (strings.length != valueCount) {
+                throw invalidLengths(strings);
+            }
+
+            try {
+                for (int i = 0; i < strings.length; i++) {
+                    lengths[i] = Long.parseLong(strings[i]);
+                }
+            } catch (NumberFormatException e) {
+                throw invalidLengths(strings);
+            }
+        }
+
+        private IOException invalidLengths(String[] strings) throws IOException {
+            throw new IOException("unexpected journal line: " + Arrays.toString(strings));
+        }
+
+        public File getCleanFile(int i) {
+            return new File(directory, key + "." + i);
+        }
+
+        public File getDirtyFile(int i) {
+            return new File(directory, key + "." + i + ".tmp");
+        }
+    }
+}
\ No newline at end of file
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 9e2b2c9..f205994 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -18,6 +18,7 @@
 
 import java.io.FileDescriptor;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
 import libcore.util.MutableInt;
@@ -33,9 +34,14 @@
         this.os = os;
     }
 
+    public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException { return os.accept(fd, peerAddress); }
     public boolean access(String path, int mode) throws ErrnoException { return os.access(path, mode); }
+    public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException { os.bind(fd, address, port); }
     public void chmod(String path, int mode) throws ErrnoException { os.chmod(path, mode); }
     public void close(FileDescriptor fd) throws ErrnoException { os.close(fd); }
+    public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException { os.connect(fd, address, port); }
+    public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return os.dup(oldFd); }
+    public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return os.dup2(oldFd, newFd); }
     public String[] environ() { return os.environ(); }
     public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return os.fcntlVoid(fd, cmd); }
     public int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException { return os.fcntlLong(fd, cmd, arg); }
@@ -47,14 +53,22 @@
     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 int getegid() { return os.getegid(); }
+    public int geteuid() { return os.geteuid(); }
+    public int getgid() { return os.getgid(); }
     public String getenv(String name) { return os.getenv(name); }
     public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); }
+    public int getpid() { return os.getpid(); }
+    public int getppid() { return os.getppid(); }
+    public StructPasswd getpwnam(String name) throws ErrnoException { return os.getpwnam(name); }
+    public StructPasswd getpwuid(int uid) throws ErrnoException { return os.getpwuid(uid); }
     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 int getuid() { return os.getuid(); }
     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); }
@@ -73,12 +87,20 @@
     public void munmap(long address, long byteCount) throws ErrnoException { os.munmap(address, byteCount); }
     public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return os.open(path, flags, mode); }
     public FileDescriptor[] pipe() throws ErrnoException { return os.pipe(); }
+    public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); }
     public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.read(fd, buffer); }
     public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.read(fd, bytes, byteOffset, byteCount); }
     public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.readv(fd, buffers, offsets, byteCounts); }
+    public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException { return os.recvfrom(fd, buffer, flags, srcAddress); }
+    public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); }
     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 int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException { return os.sendto(fd, buffer, flags, inetAddress, port); }
+    public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException { return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
+    public void setegid(int egid) throws ErrnoException { os.setegid(egid); }
+    public void seteuid(int euid) throws ErrnoException { os.seteuid(euid); }
+    public void setgid(int gid) throws ErrnoException { os.setgid(gid); }
     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); }
@@ -86,6 +108,7 @@
     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 setuid(int uid) throws ErrnoException { os.setuid(uid); }
     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); }
@@ -94,6 +117,7 @@
     public void symlink(String oldPath, String newPath) throws ErrnoException { os.symlink(oldPath, newPath); }
     public long sysconf(int name) { return os.sysconf(name); }
     public StructUtsname uname() throws ErrnoException { return os.uname(); }
+    public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); }
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); }
     public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); }
     public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.writev(fd, buffers, offsets, byteCounts); }
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
new file mode 100644
index 0000000..c26b565
--- /dev/null
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -0,0 +1,593 @@
+/*
+ * 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.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.BindException;
+import java.net.ConnectException;
+import java.net.DatagramPacket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.PortUnreachableException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketOptions;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.util.MutableInt;
+import static libcore.io.OsConstants.*;
+
+/**
+ * Implements java.io/java.net/java.nio semantics in terms of the underlying POSIX system calls.
+ */
+public final class IoBridge {
+
+    private IoBridge() {
+    }
+
+    public static int available(FileDescriptor fd) throws IOException {
+        try {
+            MutableInt available = new MutableInt(0);
+            int rc = Libcore.os.ioctlInt(fd, FIONREAD, available);
+            if (available.value < 0) {
+                // If the fd refers to a regular file, the result is the difference between
+                // the file size and the file position. This may be negative if the position
+                // is past the end of the file. If the fd refers to a special file masquerading
+                // as a regular file, the result may be negative because the special file
+                // may appear to have zero size and yet a previous read call may have
+                // read some amount of data and caused the file position to be advanced.
+                available.value = 0;
+            }
+            return available.value;
+        } catch (ErrnoException errnoException) {
+            if (errnoException.errno == ENOTTY) {
+                // The fd is unwilling to opine about its read buffer.
+                return 0;
+            }
+            throw errnoException.rethrowAsIOException();
+        }
+    }
+
+
+    public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException {
+        if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) {
+            // Linux won't let you bind a link-local address without a scope id. Find one.
+            NetworkInterface nif = NetworkInterface.getByInetAddress(address);
+            if (nif == null) {
+                throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
+            }
+            try {
+                address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
+            } catch (UnknownHostException ex) {
+                throw new AssertionError(ex); // Can't happen.
+            }
+        }
+        try {
+            Libcore.os.bind(fd, address, port);
+        } catch (ErrnoException errnoException) {
+            throw new BindException(errnoException.getMessage(), errnoException);
+        }
+    }
+
+
+    /**
+     * 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 IoBridge.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 (ErrnoException errnoException) {
+            throw new ConnectException(connectDetail(inetAddress, port, timeoutMs, errnoException), errnoException);
+        } 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);
+        }
+    }
+
+    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) {
+            Libcore.os.connect(fd, inetAddress, port);
+            return true;
+        }
+
+        // With a timeout, we set the socket to non-blocking, connect(2), and then loop
+        // using poll(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 {
+            try {
+                Libcore.os.connect(fd, inetAddress, port);
+                return true; // We connected immediately.
+            } catch (ErrnoException errnoException) {
+                if (errnoException.errno != EINPROGRESS) {
+                    throw errnoException;
+                }
+                // EINPROGRESS means we should keep trying...
+            }
+            int remainingTimeoutMs;
+            do {
+                remainingTimeoutMs = (int) (finishTimeMs - System.currentTimeMillis());
+                if (remainingTimeoutMs <= 0) {
+                    throw new SocketTimeoutException(connectDetail(inetAddress, port, timeoutMs, null));
+                }
+            } while (!IoBridge.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs));
+            return true; // Or we'd have thrown.
+        } finally {
+            IoUtils.setBlocking(fd, true);
+        }
+    }
+
+    private static String connectDetail(InetAddress inetAddress, int port, int timeoutMs, ErrnoException cause) {
+        String detail = "failed to connect to " + inetAddress + " (port " + port + ")";
+        if (timeoutMs > 0) {
+            detail += " after " + timeoutMs + "ms";
+        }
+        if (cause != null) {
+            detail += ": " + cause.getMessage();
+        }
+        return detail;
+    }
+
+    public static void closeSocket(FileDescriptor fd) throws IOException {
+        if (!fd.valid()) {
+            // Socket.close doesn't throw if you try to close an already-closed socket.
+            return;
+        }
+        int intFd = fd.getInt$();
+        fd.setInt$(-1);
+        FileDescriptor oldFd = new FileDescriptor();
+        oldFd.setInt$(intFd);
+        AsynchronousCloseMonitor.signalBlockedThreads(oldFd);
+        try {
+            Libcore.os.close(oldFd);
+        } catch (ErrnoException errnoException) {
+            // TODO: are there any cases in which we should throw?
+        }
+    }
+
+    public static boolean isConnected(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs, int remainingTimeoutMs) throws IOException {
+        ErrnoException cause = null;
+        try {
+            StructPollfd[] pollFds = new StructPollfd[] { new StructPollfd() };
+            pollFds[0].fd = fd;
+            pollFds[0].events = (short) POLLOUT;
+            int rc = Libcore.os.poll(pollFds, remainingTimeoutMs);
+            if (rc == 0) {
+                return false; // Timeout.
+            }
+            int connectError = Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_ERROR);
+            if (connectError == 0) {
+                return true; // Success!
+            }
+            throw new ErrnoException("isConnected", connectError); // The connect(2) failed.
+        } catch (ErrnoException errnoException) {
+            if (errnoException.errno == EINTR) {
+                return false; // Punt and ask the caller to try again.
+            } else {
+                cause = errnoException;
+            }
+        }
+        // TODO: is it really helpful/necessary to throw so many different exceptions?
+        String detail = connectDetail(inetAddress, port, timeoutMs, cause);
+        if (cause.errno == ECONNRESET || cause.errno == ECONNREFUSED ||
+        cause.errno == EADDRNOTAVAIL || cause.errno == EADDRINUSE ||
+        cause.errno == ENETUNREACH) {
+            throw new ConnectException(detail, cause);
+        } else if (cause.errno == EACCES) {
+            throw new SecurityException(detail, cause);
+        } else if (cause.errno == ETIMEDOUT) {
+            throw new SocketTimeoutException(detail, cause);
+        }
+        throw new SocketException(detail, cause);
+    }
+
+    // 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.
+     */
+    public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException {
+        try {
+            return getSocketOptionErrno(fd, option);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
+
+    private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException {
+        switch (option) {
+        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:
+            // 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 IoBridge.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:
+            // 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:
+            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE));
+        case SocketOptions.SO_LINGER:
+            StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
+            if (!linger.isOn()) {
+                return false;
+            }
+            return linger.l_linger;
+        case SocketOptions.SO_OOBINLINE:
+            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE));
+        case SocketOptions.SO_RCVBUF:
+            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
+        case SocketOptions.SO_REUSEADDR:
+            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR));
+        case SocketOptions.SO_SNDBUF:
+            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
+        case SocketOptions.SO_TIMEOUT:
+            return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis();
+        case SocketOptions.TCP_NODELAY:
+            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
+        default:
+            throw new SocketException("Unknown socket option: " + option);
+        }
+    }
+
+    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 IoBridge.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 IoBridge.JAVA_MCAST_JOIN_GROUP:
+        case IoBridge.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);
+        }
+    }
+
+    /**
+     * java.io only throws FileNotFoundException when opening files, regardless of what actually
+     * went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening
+     * directories: POSIX says read-only is okay, but java.io doesn't even allow that. We also
+     * have an Android-specific hack to alter the default permissions.
+     */
+    public static FileDescriptor open(String path, int flags) throws FileNotFoundException {
+        FileDescriptor fd = null;
+        try {
+            // On Android, we don't want default permissions to allow global access.
+            int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600;
+            fd = Libcore.os.open(path, flags, mode);
+            if (fd.valid()) {
+                // Posix open(2) fails with EISDIR only if you ask for write permission.
+                // Java disallows reading directories too.
+                boolean isDirectory = false;
+                if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
+                    throw new ErrnoException("open", EISDIR);
+                }
+            }
+            return fd;
+        } catch (ErrnoException errnoException) {
+            try {
+                if (fd != null) {
+                    IoUtils.close(fd);
+                }
+            } catch (IOException ignored) {
+            }
+            FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage());
+            ex.initCause(errnoException);
+            throw ex;
+        }
+    }
+
+    /**
+     * java.io thinks that a read at EOF is an error and should return -1, contrary to traditional
+     * Unix practice where you'd read until you got 0 bytes (and any future read would return -1).
+     */
+    public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
+        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
+        if (byteCount == 0) {
+            return 0;
+        }
+        try {
+            int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount);
+            if (readCount == 0) {
+                return -1;
+            }
+            return readCount;
+        } catch (ErrnoException errnoException) {
+            if (errnoException.errno == EAGAIN) {
+                // We return 0 rather than throw if we try to read from an empty non-blocking pipe.
+                return 0;
+            }
+            throw errnoException.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * java.io always writes every byte it's asked to, or fails with an error. (That is, unlike
+     * Unix it never just writes as many bytes as happens to be convenient.)
+     */
+    public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
+        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
+        if (byteCount == 0) {
+            return;
+        }
+        try {
+            while (byteCount > 0) {
+                int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount);
+                byteCount -= bytesWritten;
+                byteOffset += bytesWritten;
+            }
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsIOException();
+        }
+    }
+
+    public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws IOException {
+        boolean isDatagram = (inetAddress != null);
+        if (!isDatagram && byteCount <= 0) {
+            return 0;
+        }
+        int result;
+        try {
+            result = Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);
+        } catch (ErrnoException errnoException) {
+            result = maybeThrowAfterSendto(isDatagram, errnoException);
+        }
+        return result;
+    }
+
+    public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws IOException {
+        boolean isDatagram = (inetAddress != null);
+        if (!isDatagram && buffer.remaining() == 0) {
+            return 0;
+        }
+        int result;
+        try {
+            result = Libcore.os.sendto(fd, buffer, flags, inetAddress, port);
+        } catch (ErrnoException errnoException) {
+            result = maybeThrowAfterSendto(isDatagram, errnoException);
+        }
+        return result;
+    }
+
+    private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException) throws SocketException {
+        if (isDatagram) {
+            if (errnoException.errno == ECONNRESET || errnoException.errno == ECONNREFUSED) {
+                return 0;
+            }
+        } else {
+            if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+                // We were asked to write to a non-blocking socket, but were told
+                // it would block, so report "no bytes written".
+                return 0;
+            }
+        }
+        throw errnoException.rethrowAsSocketException();
+    }
+
+    public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
+        int result;
+        try {
+            InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null;
+            result = Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);
+            result = postRecvfrom(isRead, packet, isConnected, srcAddress, result);
+        } catch (ErrnoException errnoException) {
+            result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
+        }
+        return result;
+    }
+
+    public static int recvfrom(boolean isRead, FileDescriptor fd, ByteBuffer buffer, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
+        int result;
+        try {
+            InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null;
+            result = Libcore.os.recvfrom(fd, buffer, flags, srcAddress);
+            result = postRecvfrom(isRead, packet, isConnected, srcAddress, result);
+        } catch (ErrnoException errnoException) {
+            result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
+        }
+        return result;
+    }
+
+    private static int postRecvfrom(boolean isRead, DatagramPacket packet, boolean isConnected, InetSocketAddress srcAddress, int byteCount) {
+        if (isRead && byteCount == 0) {
+            return -1;
+        }
+        if (packet != null) {
+            packet.setLength(byteCount);
+            if (!isConnected) {
+                packet.setAddress(srcAddress.getAddress());
+                packet.setPort(srcAddress.getPort());
+            }
+        }
+        return byteCount;
+    }
+
+    private static int maybeThrowAfterRecvfrom(boolean isRead, boolean isConnected, ErrnoException errnoException) throws SocketException, SocketTimeoutException {
+        if (isRead) {
+            if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+                return 0;
+            } else {
+                throw errnoException.rethrowAsSocketException();
+            }
+        } else {
+            if (isConnected && errnoException.errno == ECONNREFUSED) {
+                throw new PortUnreachableException("", errnoException);
+            } else if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+                throw new SocketTimeoutException(errnoException);
+            } else {
+                throw errnoException.rethrowAsSocketException();
+            }
+        }
+    }
+
+    public static FileDescriptor socket(boolean stream) throws SocketException {
+        FileDescriptor fd;
+        try {
+            fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
+
+            // The RFC (http://www.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults
+            // to 1. The Linux kernel (at least up to 2.6.38) accidentally defaults to 64 (which
+            // would be correct for the *unicast* hop limit).
+            // See http://www.spinics.net/lists/netdev/msg129022.html, though no patch appears to
+            // have been applied as a result of that discussion. If that bug is ever fixed, we can
+            // remove this code. Until then, we manually set the hop limit on IPv6 datagram sockets.
+            // (IPv4 is already correct.)
+            if (!stream) {
+                Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 1);
+            }
+
+            return fd;
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
+
+    public static InetAddress getSocketLocalAddress(FileDescriptor fd) {
+        SocketAddress sa = Libcore.os.getsockname(fd);
+        InetSocketAddress isa = (InetSocketAddress) sa;
+        return isa.getAddress();
+    }
+
+    public static int getSocketLocalPort(FileDescriptor fd) {
+        SocketAddress sa = Libcore.os.getsockname(fd);
+        InetSocketAddress isa = (InetSocketAddress) sa;
+        return isa.getPort();
+    }
+
+}
diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java
index eeb8d99..6680882 100644
--- a/luni/src/main/java/libcore/io/IoUtils.java
+++ b/luni/src/main/java/libcore/io/IoUtils.java
@@ -17,137 +17,19 @@
 package libcore.io;
 
 import java.io.Closeable;
+import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.net.InetSocketAddress;
 import java.net.Socket;
-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() {
     }
 
     /**
-     * Implements java.io/java.net "available" semantics.
-     */
-    public static int available(FileDescriptor fd) throws IOException {
-        try {
-            MutableInt available = new MutableInt(0);
-            int rc = Libcore.os.ioctlInt(fd, FIONREAD, available);
-            if (available.value < 0) {
-                // If the fd refers to a regular file, the result is the difference between
-                // the file size and the file position. This may be negative if the position
-                // is past the end of the file. If the fd refers to a special file masquerading
-                // as a regular file, the result may be negative because the special file
-                // may appear to have zero size and yet a previous read call may have
-                // read some amount of data and caused the file position to be advanced.
-                available.value = 0;
-            }
-            return available.value;
-        } catch (ErrnoException errnoException) {
-            if (errnoException.errno == ENOTTY) {
-                // The fd is unwilling to opine about its read buffer.
-                return 0;
-            }
-            throw errnoException.rethrowAsIOException();
-        }
-    }
-
-    /**
-     * java.io only throws FileNotFoundException when opening files, regardless of what actually
-     * went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening
-     * directories: POSIX says read-only is okay, but java.io doesn't even allow that. We also
-     * have an Android-specific hack to alter the default permissions.
-     */
-    public static FileDescriptor open(String path, int flags) throws FileNotFoundException {
-        FileDescriptor fd = null;
-        try {
-            // On Android, we don't want default permissions to allow global access.
-            int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600;
-            fd = Libcore.os.open(path, flags, mode);
-            if (fd.valid()) {
-                // Posix open(2) fails with EISDIR only if you ask for write permission.
-                // Java disallows reading directories too.
-                boolean isDirectory = false;
-                if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
-                    throw new ErrnoException("open", EISDIR);
-                }
-            }
-            return fd;
-        } catch (ErrnoException errnoException) {
-            try {
-                if (fd != null) {
-                    close(fd);
-                }
-            } catch (IOException ignored) {
-            }
-            FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage());
-            ex.initCause(errnoException);
-            throw ex;
-        }
-    }
-
-    /**
-     * java.io thinks that a read at EOF is an error and should return -1, contrary to traditional
-     * Unix practice where you'd read until you got 0 bytes (and any future read would return -1).
-     */
-    public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
-        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
-        if (byteCount == 0) {
-            return 0;
-        }
-        try {
-            int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount);
-            if (readCount == 0) {
-                return -1;
-            }
-            return readCount;
-        } catch (ErrnoException errnoException) {
-            if (errnoException.errno == EAGAIN) {
-                // We return 0 rather than throw if we try to read from an empty non-blocking pipe.
-                return 0;
-            }
-            throw errnoException.rethrowAsIOException();
-        }
-    }
-
-    /**
-     * java.io always writes every byte it's asked to, or fails with an error. (That is, unlike
-     * Unix it never just writes as many bytes as happens to be convenient.)
-     */
-    public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
-        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
-        if (byteCount == 0) {
-            return;
-        }
-        try {
-            while (byteCount > 0) {
-                int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount);
-                byteCount -= bytesWritten;
-                byteOffset += bytesWritten;
-            }
-        } catch (ErrnoException errnoException) {
-            throw errnoException.rethrowAsIOException();
-        }
-    }
-
-    /**
      * Calls close(2) on 'fd'. Also resets the internal int to -1. Does nothing if 'fd' is null
      * or invalid.
      */
@@ -196,67 +78,6 @@
     }
 
     /**
-     * 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 {
@@ -273,196 +94,6 @@
         }
     }
 
-    public static FileDescriptor socket(boolean stream) throws SocketException {
-        FileDescriptor fd;
-        try {
-            fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
-
-            // The RFC (http://www.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults
-            // to 1. The Linux kernel (at least up to 2.6.38) accidentally defaults to 64 (which
-            // would be correct for the *unicast* hop limit).
-            // See http://www.spinics.net/lists/netdev/msg129022.html, though no patch appears to
-            // have been applied as a result of that discussion. If that bug is ever fixed, we can
-            // remove this code. Until then, we manually set the hop limit on IPv6 datagram sockets.
-            // (IPv4 is already correct.)
-            if (!stream) {
-                Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 1);
-            }
-
-            return fd;
-        } catch (ErrnoException errnoException) {
-            throw errnoException.rethrowAsSocketException();
-        }
-    }
-
-    // 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.
-     */
-    public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException {
-        try {
-            return getSocketOptionErrno(fd, option);
-        } catch (ErrnoException errnoException) {
-            throw errnoException.rethrowAsSocketException();
-        }
-    }
-
-    private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException {
-        switch (option) {
-        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:
-            // 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:
-            // 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:
-            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE));
-        case SocketOptions.SO_LINGER:
-            StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
-            if (!linger.isOn()) {
-                return false;
-            }
-            return linger.l_linger;
-        case SocketOptions.SO_OOBINLINE:
-            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE));
-        case SocketOptions.SO_RCVBUF:
-            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
-        case SocketOptions.SO_REUSEADDR:
-            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR));
-        case SocketOptions.SO_SNDBUF:
-            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
-        case SocketOptions.SO_TIMEOUT:
-            return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis();
-        case SocketOptions.TCP_NODELAY:
-            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
-        default:
-            throw new SocketException("Unknown socket option: " + option);
-        }
-    }
-
-    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;
-        return isa.getAddress();
-    }
-
-    public static int getSocketLocalPort(FileDescriptor fd) {
-        SocketAddress sa = Libcore.os.getsockname(fd);
-        InetSocketAddress isa = (InetSocketAddress) sa;
-        return isa.getPort();
-    }
-
     /**
      * Returns the contents of 'path' as a byte array.
      */
@@ -494,4 +125,23 @@
             IoUtils.closeQuietly(f);
         }
     }
+
+    /**
+     * Recursively delete everything in {@code dir}.
+     */
+    // TODO: this should specify paths as Strings rather than as Files
+    public static void deleteContents(File dir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files == null) {
+            throw new IllegalArgumentException("not a directory: " + dir);
+        }
+        for (File file : files) {
+            if (file.isDirectory()) {
+                deleteContents(file);
+            }
+            if (!file.delete()) {
+                throw new IOException("failed to delete file: " + file);
+            }
+        }
+    }
 }
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 7fceedc..bd50998 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -18,15 +18,21 @@
 
 import java.io.FileDescriptor;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
 import libcore.util.MutableInt;
 import libcore.util.MutableLong;
 
 public interface Os {
+    public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException;
     public boolean access(String path, int mode) throws ErrnoException;
+    public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException;
     public void chmod(String path, int mode) throws ErrnoException;
     public void close(FileDescriptor fd) throws ErrnoException;
+    public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException;
+    public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException;
+    public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException;
     public String[] environ();
     public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException;
     public int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException;
@@ -38,15 +44,23 @@
     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 int getegid();
+    public int geteuid();
+    public int getgid();
     public String getenv(String name);
     /* TODO: break into getnameinfoHost and getnameinfoService? */
     public String getnameinfo(InetAddress address, int flags) throws GaiException;
+    public int getpid();
+    public int getppid();
+    public StructPasswd getpwnam(String name) throws ErrnoException;
+    public StructPasswd getpwuid(int uid) throws ErrnoException;
     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 int getuid();
     public String if_indextoname(int index);
     public InetAddress inet_aton(String address);
     public InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException;
@@ -64,13 +78,22 @@
     public void munmap(long address, long byteCount) throws ErrnoException;
     public FileDescriptor open(String path, int flags, int mode) throws ErrnoException;
     public FileDescriptor[] pipe() throws ErrnoException;
+    /* TODO: if we used the non-standard ppoll(2) behind the scenes, we could take a long timeout. */
+    public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException;
     public StructStat lstat(String path) throws ErrnoException;
     public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
     public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
     public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
+    public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException;
+    public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException;
     public void remove(String path) throws ErrnoException;
     public void rename(String oldPath, String newPath) throws ErrnoException;
+    public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException;
+    public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException;
     public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException;
+    public void setegid(int egid) throws ErrnoException;
+    public void seteuid(int euid) throws ErrnoException;
+    public void setgid(int gid) 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;
@@ -78,6 +101,7 @@
     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 setuid(int uid) 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;
@@ -87,6 +111,7 @@
     public void symlink(String oldPath, String newPath) throws ErrnoException;
     public long sysconf(int name);
     public StructUtsname uname() throws ErrnoException;
+    public int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
     public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
     public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/libcore/io/OsConstants.java
index 34a931c..68a165c 100644
--- a/luni/src/main/java/libcore/io/OsConstants.java
+++ b/luni/src/main/java/libcore/io/OsConstants.java
@@ -230,6 +230,16 @@
     public static final int O_SYNC = placeholder();
     public static final int O_TRUNC = placeholder();
     public static final int O_WRONLY = placeholder();
+    public static final int POLLERR = placeholder();
+    public static final int POLLHUP = placeholder();
+    public static final int POLLIN = placeholder();
+    public static final int POLLNVAL = placeholder();
+    public static final int POLLOUT = placeholder();
+    public static final int POLLPRI = placeholder();
+    public static final int POLLRDBAND = placeholder();
+    public static final int POLLRDNORM = placeholder();
+    public static final int POLLWRBAND = placeholder();
+    public static final int POLLWRNORM = placeholder();
     public static final int PROT_EXEC = placeholder();
     public static final int PROT_NONE = placeholder();
     public static final int PROT_READ = placeholder();
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index 4e23dc0..f976209 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -18,6 +18,7 @@
 
 import java.io.FileDescriptor;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.NioUtils;
@@ -27,9 +28,14 @@
 public final class Posix implements Os {
     Posix() { }
 
+    public native FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException;
     public native boolean access(String path, int mode) throws ErrnoException;
+    public native void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException;
     public native void chmod(String path, int mode) throws ErrnoException;
     public native void close(FileDescriptor fd) throws ErrnoException;
+    public native void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException;
+    public native FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException;
+    public native FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException;
     public native String[] environ();
     public native int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException;
     public native int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException;
@@ -41,16 +47,24 @@
     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 int getegid();
+    public native int geteuid();
+    public native int getgid();
     public native String getenv(String name);
     public native String getnameinfo(InetAddress address, int flags) throws GaiException;
+    public native int getpid();
+    public native int getppid();
+    public native StructPasswd getpwnam(String name) throws ErrnoException;
+    public native StructPasswd getpwuid(int uid) throws ErrnoException;
     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 int getuid();
     public native String if_indextoname(int index);
+    public native InetAddress inet_aton(String address);
     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);
@@ -66,6 +80,7 @@
     public native void munmap(long address, long byteCount) throws ErrnoException;
     public native FileDescriptor open(String path, int flags, int mode) throws ErrnoException;
     public native FileDescriptor[] pipe() throws ErrnoException;
+    public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException;
     public native StructStat lstat(String path) throws ErrnoException;
     public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         if (buffer.isDirect()) {
@@ -80,9 +95,36 @@
     }
     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 int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException {
+        if (buffer.isDirect()) {
+            return recvfromBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, srcAddress);
+        } else {
+            return recvfromBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), flags, srcAddress);
+        }
+    }
+    public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException {
+        // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
+        return recvfromBytes(fd, bytes, byteOffset, byteCount, flags, srcAddress);
+    }
+    private native int recvfromBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) 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 int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException {
+        if (buffer.isDirect()) {
+            return sendtoBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, inetAddress, port);
+        } else {
+            return sendtoBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), flags, inetAddress, port);
+        }
+    }
+    public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException {
+        // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
+        return sendtoBytes(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);
+    }
+    private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException;
+    public native void setegid(int egid) throws ErrnoException;
+    public native void seteuid(int euid) throws ErrnoException;
+    public native void setgid(int gid) 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;
@@ -90,6 +132,7 @@
     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 setuid(int uid) 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;
@@ -98,6 +141,7 @@
     public native void symlink(String oldPath, String newPath) throws ErrnoException;
     public native long sysconf(int name);
     public native StructUtsname uname() throws ErrnoException;
+    public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         if (buffer.isDirect()) {
             return writeBytes(fd, buffer, buffer.position(), buffer.remaining());
diff --git a/luni/src/main/java/libcore/io/Streams.java b/luni/src/main/java/libcore/io/Streams.java
index 2ece93c..ab8795c 100644
--- a/luni/src/main/java/libcore/io/Streams.java
+++ b/luni/src/main/java/libcore/io/Streams.java
@@ -21,6 +21,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -86,18 +88,36 @@
     }
 
     /**
-     * Returns a new byte[] containing the entire contents of the given InputStream.
-     * Useful when you don't know in advance how much data there is to be read.
+     * Returns a byte[] containing the remainder of 'in', closing it when done.
      */
     public static byte[] readFully(InputStream in) throws IOException {
-        byte[] buffer = new byte[1024];
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-        while (true) {
-            int byteCount = in.read(buffer);
-            if (byteCount == -1) {
-                return bytes.toByteArray();
+        try {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int count;
+            while ((count = in.read(buffer)) != -1) {
+                bytes.write(buffer, 0, count);
             }
-            bytes.write(buffer, 0, byteCount);
+            return bytes.toByteArray();
+        } finally {
+            in.close();
+        }
+    }
+
+    /**
+     * Returns the remainder of 'reader' as a string, closing it when done.
+     */
+    public static String readFully(Reader reader) throws IOException {
+        try {
+            StringWriter writer = new StringWriter();
+            char[] buffer = new char[1024];
+            int count;
+            while ((count = reader.read(buffer)) != -1) {
+                writer.write(buffer, 0, count);
+            }
+            return writer.toString();
+        } finally {
+            reader.close();
         }
     }
 
@@ -158,4 +178,32 @@
         }
         return total;
     }
+
+    /**
+     * Returns the ASCII characters up to but not including the next "\r\n", or
+     * "\n".
+     *
+     * @throws java.io.EOFException if the stream is exhausted before the next newline
+     *     character.
+     */
+    public static String readAsciiLine(InputStream in) throws IOException {
+        // TODO: support UTF-8 here instead
+
+        StringBuilder result = new StringBuilder(80);
+        while (true) {
+            int c = in.read();
+            if (c == -1) {
+                throw new EOFException();
+            } else if (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();
+    }
 }
diff --git a/luni/src/main/java/libcore/io/StructPasswd.java b/luni/src/main/java/libcore/io/StructPasswd.java
new file mode 100644
index 0000000..6f5e058
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructPasswd.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Information returned by getpwnam(3) and getpwuid(3). Corresponds to C's
+ * {@code struct passwd} from
+ * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pwd.h.html">&lt;pwd.h&gt;</a>
+ */
+public final class StructPasswd {
+    public String pw_name;
+    public int pw_uid; /* uid_t */
+    public int pw_gid; /* gid_t */
+    public String pw_dir;
+    public String pw_shell;
+
+    public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) {
+        this.pw_name = pw_name;
+        this.pw_uid = pw_uid;
+        this.pw_gid = pw_gid;
+        this.pw_dir = pw_dir;
+        this.pw_shell = pw_shell;
+    }
+}
diff --git a/luni/src/main/java/libcore/io/StructPollfd.java b/luni/src/main/java/libcore/io/StructPollfd.java
new file mode 100644
index 0000000..c659d6e
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructPollfd.java
@@ -0,0 +1,48 @@
+/*
+ * 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.io.FileDescriptor;
+
+/**
+ * Corresponds to C's {@code struct pollfd} from
+ * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html">&lt;poll.h&gt;</a>
+ */
+public final class StructPollfd {
+    /** The file descriptor to poll. */
+    public FileDescriptor fd;
+
+    /**
+     * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
+     * POLLOUT to the write fd set.
+     */
+    public short events;
+
+    /** The events that actually happened. */
+    public short revents;
+
+    /**
+     * A non-standard extension that lets callers conveniently map back to the object
+     * their fd belongs to. This is used by Selector, for example, to associate each
+     * FileDescriptor with the corresponding SelectionKey.
+     */
+    public Object userData;
+
+    @Override public String toString() {
+        return "StructPollfd[fd=" + fd + ",events=" + events + ",revents=" + revents + "]";
+    }
+}
diff --git a/luni/src/main/java/libcore/net/RawSocket.java b/luni/src/main/java/libcore/net/RawSocket.java
index 255b0b9c..bb29fb0 100644
--- a/luni/src/main/java/libcore/net/RawSocket.java
+++ b/luni/src/main/java/libcore/net/RawSocket.java
@@ -22,7 +22,7 @@
 import java.io.IOException;
 import java.net.SocketException;
 import java.util.Arrays;
-import org.apache.harmony.luni.platform.Platform;
+import libcore.io.IoBridge;
 
 /**
  * This class allows raw L2 packets to be sent and received via the
@@ -111,7 +111,7 @@
      */
     public void close() throws IOException {
         guard.close();
-        Platform.NETWORK.close(fd);
+        IoBridge.closeSocket(fd);
     }
 
     @Override protected void finalize() throws Throwable {
diff --git a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
index 755bc96..70f76b7 100644
--- a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
+++ b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
@@ -83,7 +83,7 @@
         if (cacheRequest != null) {
             cacheBody.close();
         }
-        httpEngine.releaseSocket(reuseSocket);
+        httpEngine.release(reuseSocket);
     }
 
     /**
@@ -102,6 +102,6 @@
         if (cacheRequest != null) {
             cacheRequest.abort();
         }
-        httpEngine.releaseSocket(false);
+        httpEngine.release(false);
     }
 }
diff --git a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
index 274f03c..380ed02 100644
--- a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
+++ b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
@@ -20,6 +20,7 @@
 import java.io.InputStream;
 import java.net.CacheRequest;
 import java.util.Arrays;
+import libcore.io.Streams;
 
 /**
  * An HTTP body with alternating chunk sizes and chunk bodies.
@@ -73,9 +74,9 @@
     private void readChunkSize() throws IOException {
         // read the suffix of the previous chunk
         if (bytesRemainingInChunk != NO_CHUNK_YET) {
-            HttpEngine.readLine(in);
+            Streams.readAsciiLine(in);
         }
-        String chunkSizeString = HttpEngine.readLine(in);
+        String chunkSizeString = Streams.readAsciiLine(in);
         int index = chunkSizeString.indexOf(";");
         if (index != -1) {
             chunkSizeString = chunkSizeString.substring(0, index);
diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java
index d023cbc..d95e21c 100644
--- a/luni/src/main/java/libcore/net/http/HttpEngine.java
+++ b/luni/src/main/java/libcore/net/http/HttpEngine.java
@@ -26,7 +26,6 @@
 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;
@@ -34,6 +33,7 @@
 import java.net.URL;
 import java.nio.charset.Charsets;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -61,16 +61,15 @@
  * 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()}.
+ * recycled. By default, this socket connection is held when the last byte of
+ * the response is consumed. To release the connection when it is no longer
+ * required, use {@link #automaticallyReleaseConnectionToPool()}.
  */
 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 {
@@ -151,10 +150,10 @@
 
     private final URI uri;
 
-    private final RawHeaders rawRequestHeaders;
+    private final RequestHeaders requestHeaders;
 
     /** Null until a response is received from the network or the cache */
-    private RawHeaders rawResponseHeaders;
+    private ResponseHeaders responseHeaders;
 
     /*
      * The cache response currently being validated on a conditional get. Null
@@ -162,16 +161,21 @@
      * 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;
+    private ResponseHeaders cachedResponseHeaders;
+    private InputStream cachedResponseBody;
 
     /**
      * True if the socket connection should be released to the connection pool
      * when the response has been fully read.
      */
-    private boolean dontReleaseSocketToPool;
+    private boolean automaticallyReleaseConnectionToPool;
+
+    /** True if the socket connection is no longer needed by this engine. */
+    private boolean connectionReleased;
 
     /**
+     * @param requestHeaders the client's supplied request headers. This class
+     *     creates a private copy that it can mutate.
      * @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
@@ -179,19 +183,8 @@
      */
     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;
 
@@ -200,10 +193,8 @@
         } catch (URISyntaxException e) {
             throw new IOException(e);
         }
-    }
 
-    public final void dontReleaseSocketToPool() {
-        this.dontReleaseSocketToPool = true;
+        this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
     }
 
     /**
@@ -217,30 +208,31 @@
         }
 
         prepareRawRequestHeaders();
-        RequestHeaders cacheRequestHeaders = new RequestHeaders(uri, rawRequestHeaders);
-        initResponseSource(cacheRequestHeaders);
+        initResponseSource();
+        if (responseCache instanceof HttpResponseCache) {
+            ((HttpResponseCache) responseCache).trackResponse(responseSource);
+        }
 
         /*
          * 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 (requestHeaders.onlyIfCached && responseSource.requiresConnection()) {
             if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
-                this.responseHeadersToValidate = null;
-                IoUtils.closeQuietly(responseBodyToValidate);
-                this.responseBodyToValidate = null;
+                IoUtils.closeQuietly(cachedResponseBody);
             }
             this.responseSource = ResponseSource.CACHE;
             this.cacheResponse = BAD_GATEWAY_RESPONSE;
-            setResponse(RawHeaders.fromMultimap(cacheResponse.getHeaders()),
-                    cacheResponse.getBody());
+            RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders());
+            setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
         }
 
         if (responseSource.requiresConnection()) {
             sendSocketRequest();
-        } else {
-            // TODO: release 'connection' if it is non-null
+        } else if (connection != null) {
+            HttpConnectionPool.INSTANCE.recycle(connection);
+            connection = null;
         }
     }
 
@@ -248,38 +240,38 @@
      * 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 {
+    private void initResponseSource() 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) {
+        CacheResponse candidate = responseCache.get(uri, method,
+                requestHeaders.headers.toMultimap());
+        if (candidate == null) {
             return;
         }
 
-        RawHeaders headers = RawHeaders.fromMultimap(responseHeaders);
-        ResponseHeaders cacheResponseHeaders = new ResponseHeaders(uri, headers);
+        Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
+        cachedResponseBody = candidate.getBody();
+        if (!acceptCacheResponseType(candidate)
+                || responseHeadersMap == null
+                || cachedResponseBody == null) {
+            IoUtils.closeQuietly(cachedResponseBody);
+            return;
+        }
+
+        RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap);
+        cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
         long now = System.currentTimeMillis();
-        this.responseSource = cacheResponseHeaders.chooseResponseSource(now, cacheRequestHeaders);
+        this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
         if (responseSource == ResponseSource.CACHE) {
             this.cacheResponse = candidate;
-            setResponse(headers, cacheBodyIn);
+            setResponse(cachedResponseHeaders, cachedResponseBody);
         } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
             this.cacheResponse = candidate;
-            this.responseHeadersToValidate = cacheResponseHeaders;
-            this.responseBodyToValidate = cacheBodyIn;
         } else if (responseSource == ResponseSource.NETWORK) {
-            IoUtils.closeQuietly(cacheBodyIn);
+            IoUtils.closeQuietly(cachedResponseBody);
         } else {
             throw new AssertionError();
         }
@@ -298,7 +290,7 @@
         requestOut = socketOut;
         socketIn = connection.getInputStream();
 
-        if (policy.getDoOutput() && method != CONNECT) {
+        if (hasRequestBody()) {
             initRequestBodyOut();
         }
     }
@@ -324,17 +316,9 @@
     }
 
     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)) {
+        if (chunkLength > 0 || requestHeaders.isChunked()) {
             sendChunked = true;
-            contentLength = -1;
             if (chunkLength == -1) {
                 chunkLength = DEFAULT_CHUNK_LENGTH;
             }
@@ -357,9 +341,9 @@
         } else if (sendChunked) {
             writeRequestHeaders(-1);
             requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength);
-        } else if (contentLength != -1) {
-            writeRequestHeaders(contentLength);
-            requestBodyOut = new RetryableOutputStream(contentLength);
+        } else if (requestHeaders.contentLength != -1) {
+            writeRequestHeaders(requestHeaders.contentLength);
+            requestBodyOut = new RetryableOutputStream(requestHeaders.contentLength);
         } else {
             requestBodyOut = new RetryableOutputStream();
         }
@@ -369,17 +353,21 @@
      * @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 {
+    private void setResponse(ResponseHeaders headers, InputStream body) throws IOException {
         if (this.responseBodyIn != null) {
             throw new IllegalStateException();
         }
-        this.rawResponseHeaders = headers;
-        this.httpMinorVersion = rawResponseHeaders.getHttpMinorVersion();
+        this.responseHeaders = headers;
+        this.httpMinorVersion = responseHeaders.headers.getHttpMinorVersion();
         if (body != null) {
             initContentStream(body);
         }
     }
 
+    private boolean hasRequestBody() {
+        return method == POST || method == PUT;
+    }
+
     /**
      * Returns the request body or null if this request doesn't have a body.
      */
@@ -391,29 +379,36 @@
     }
 
     public final boolean hasResponse() {
-        return rawResponseHeaders != null;
+        return responseHeaders != null;
     }
 
-    public final RawHeaders getRequestHeaders() {
-        return rawRequestHeaders;
+    public final RequestHeaders getRequestHeaders() {
+        return requestHeaders;
     }
 
-    public final RawHeaders getResponseHeaders() {
-        if (rawResponseHeaders == null) {
+    public final ResponseHeaders getResponseHeaders() {
+        if (responseHeaders == null) {
             throw new IllegalStateException();
         }
-        return rawResponseHeaders;
+        return responseHeaders;
+    }
+
+    public final int getResponseCode() {
+        if (responseHeaders == null) {
+            throw new IllegalStateException();
+        }
+        return responseHeaders.headers.getResponseCode();
     }
 
     public final InputStream getResponseBody() {
-        if (rawResponseHeaders == null) {
+        if (responseHeaders == null) {
             throw new IllegalStateException();
         }
         return responseBodyIn;
     }
 
     public final CacheResponse getCacheResponse() {
-        if (rawResponseHeaders == null) {
+        if (responseHeaders == null) {
             throw new IllegalStateException();
         }
         return cacheResponse;
@@ -439,9 +434,7 @@
         }
 
         // Should we cache this response for this request?
-        RequestHeaders requestCacheHeaders = new RequestHeaders(uri, rawRequestHeaders);
-        ResponseHeaders responseCacheHeaders = new ResponseHeaders(uri, rawResponseHeaders);
-        if (!responseCacheHeaders.isCacheable(requestCacheHeaders)) {
+        if (!responseHeaders.isCacheable(requestHeaders)) {
             return;
         }
 
@@ -454,67 +447,71 @@
     }
 
     /**
-     * Releases this connection so that it may be either reused or closed.
+     * Cause the socket connection to be released to the connection pool when
+     * it is no longer needed. If it is already unneeded, it will be pooled
+     * immediately.
      */
-    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();
-            }
+    public final void automaticallyReleaseConnectionToPool() {
+        automaticallyReleaseConnectionToPool = true;
+        if (connection != null && connectionReleased) {
+            HttpConnectionPool.INSTANCE.recycle(connection);
             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.
+     * Releases this engine so that its resources may be either reused or
+     * closed.
      */
-    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);
+    public final void release(boolean reusable) {
+        // If the response body comes from the cache, close it.
+        if (responseBodyIn == cachedResponseBody) {
+            IoUtils.closeQuietly(responseBodyIn);
+        }
+
+        if (!connectionReleased && connection != null) {
+            connectionReleased = true;
+
+            // We cannot reuse sockets that have incomplete output.
+            if (requestBodyOut != null && !requestBodyOut.closed) {
+                reusable = false;
             }
-            responseBodyIn.close();
+
+            // If the headers specify that the connection shouldn't be reused, don't reuse it.
+            if (hasConnectionCloseHeader()) {
+                reusable = false;
+            }
+
+            if (responseBodyIn instanceof UnknownLengthHttpInputStream) {
+                reusable = false;
+            }
+
+            if (reusable && responseBodyIn != null) {
+                // We must discard the response body before the connection can be reused.
+                try {
+                    Streams.skipAll(responseBodyIn);
+                } catch (IOException e) {
+                    reusable = false;
+                }
+            }
+
+            if (!reusable) {
+                connection.closeSocketAndStreams();
+                connection = null;
+            } else if (automaticallyReleaseConnectionToPool) {
+                HttpConnectionPool.INSTANCE.recycle(connection);
+                connection = null;
+            }
         }
     }
 
     private void initContentStream(InputStream transferStream) throws IOException {
-        if (transparentGzip
-                && "gzip".equalsIgnoreCase(rawResponseHeaders.get("Content-Encoding"))) {
+        if (transparentGzip && responseHeaders.isContentEncodingGzip()) {
             /*
              * If the response was transparently gzipped, remove the gzip header field
              * so clients don't double decompress. http://b/3009828
              */
-            rawResponseHeaders.removeAll("Content-Encoding");
+            responseHeaders.stripContentEncoding();
             responseBodyIn = new GZIPInputStream(transferStream);
         } else {
             responseBodyIn = transferStream;
@@ -526,17 +523,13 @@
             return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
         }
 
-        if ("chunked".equalsIgnoreCase(rawResponseHeaders.get("Transfer-Encoding"))) {
+        if (responseHeaders.isChunked()) {
             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) {
-            }
+        if (responseHeaders.contentLength != -1) {
+            return new FixedLengthInputStream(socketIn, cacheRequest, this,
+                    responseHeaders.contentLength);
         }
 
         /*
@@ -547,35 +540,14 @@
         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());
+            headers.setStatusLine(Streams.readAsciiLine(socketIn));
             readHeaders(headers);
-            setResponse(headers, null);
         } while (headers.getResponseCode() == HTTP_CONTINUE);
+        setResponse(new ResponseHeaders(uri, headers), null);
     }
 
     /**
@@ -583,7 +555,7 @@
      * See RFC 2616 section 4.3.
      */
     public final boolean hasResponseBody() {
-        int responseCode = rawResponseHeaders.getResponseCode();
+        int responseCode = responseHeaders.headers.getResponseCode();
         if (method != HEAD
                 && method != CONNECT
                 && (responseCode < HTTP_CONTINUE || responseCode >= 200)
@@ -597,11 +569,7 @@
          * 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"))) {
+        if (responseHeaders.contentLength != -1 || responseHeaders.isChunked()) {
             return true;
         }
 
@@ -613,20 +581,14 @@
      * with chunked encoding.
      */
     final void readTrailers() throws IOException {
-        readHeaders(rawResponseHeaders);
+        readHeaders(responseHeaders.headers);
     }
 
     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));
-            }
+        while (!(line = Streams.readAsciiLine(socketIn)).isEmpty()) {
+            headers.addLine(line);
         }
 
         CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -676,23 +638,19 @@
      * the connection is using a proxy.
      */
     protected RawHeaders getNetworkRequestHeaders() throws IOException {
-        rawRequestHeaders.setStatusLine(getRequestLine());
+        requestHeaders.headers.setStatusLine(getRequestLine());
 
         int fixedContentLength = policy.getFixedContentLength();
         if (fixedContentLength != -1) {
-            rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(fixedContentLength));
+            requestHeaders.setContentLength(fixedContentLength);
         } else if (sendChunked) {
-            rawRequestHeaders.addIfAbsent("Transfer-Encoding", "chunked");
+            requestHeaders.setChunked();
         } else if (requestBodyOut instanceof RetryableOutputStream) {
-            int size = ((RetryableOutputStream) requestBodyOut).contentLength();
-            rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(size));
+            int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength();
+            requestHeaders.setContentLength(contentLength);
         }
 
-        if (requestBodyOut != null) {
-            rawRequestHeaders.addIfAbsent("Content-Type", "application/x-www-form-urlencoded");
-        }
-
-        return rawRequestHeaders;
+        return requestHeaders.headers;
     }
 
     /**
@@ -702,35 +660,37 @@
      * doesn't know what content types the application is interested in.
      */
     private void prepareRawRequestHeaders() throws IOException {
-        rawRequestHeaders.setStatusLine(getRequestLine());
+        requestHeaders.headers.setStatusLine(getRequestLine());
 
-        if (rawRequestHeaders.get("User-Agent") == null) {
-            rawRequestHeaders.add("User-Agent", getDefaultUserAgent());
+        if (requestHeaders.userAgent == null) {
+            requestHeaders.setUserAgent(getDefaultUserAgent());
         }
 
-        if (rawRequestHeaders.get("Host") == null) {
-            rawRequestHeaders.add("Host", getOriginAddress(policy.getURL()));
+        if (requestHeaders.host == null) {
+            requestHeaders.setHost(getOriginAddress(policy.getURL()));
         }
 
-        if (httpMinorVersion > 0) {
-            rawRequestHeaders.addIfAbsent("Connection", "Keep-Alive");
+        if (httpMinorVersion > 0 && requestHeaders.connection == null) {
+            requestHeaders.setConnection("Keep-Alive");
         }
 
-        if (rawRequestHeaders.get("Accept-Encoding") == null) {
+        if (requestHeaders.acceptEncoding == null) {
             transparentGzip = true;
-            rawRequestHeaders.set("Accept-Encoding", "gzip");
+            requestHeaders.setAcceptEncoding("gzip");
+        }
+
+        if (hasRequestBody() && requestHeaders.contentType == null) {
+            requestHeaders.setContentType("application/x-www-form-urlencoded");
+        }
+
+        long ifModifiedSince = policy.getIfModifiedSince();
+        if (ifModifiedSince != 0) {
+            requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
         }
 
         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());
-                }
-            }
+            requestHeaders.addCookies(cookieHandler.get(uri, requestHeaders.headers.toMultimap()));
         }
     }
 
@@ -769,10 +729,9 @@
         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")));
+    private boolean hasConnectionCloseHeader() {
+        return (responseHeaders != null && responseHeaders.hasConnectionClose())
+                || requestHeaders.hasConnectionClose();
     }
 
     protected final String getOriginAddress(URL url) {
@@ -823,23 +782,19 @@
         requestOut = socketOut;
 
         readResponseHeaders();
-        rawResponseHeaders.add(ResponseHeaders.SENT_MILLIS, Long.toString(sentRequestMillis));
-        rawResponseHeaders.add(ResponseHeaders.RECEIVED_MILLIS,
-                Long.toString(System.currentTimeMillis()));
+        responseHeaders.setLocalTimestamps(sentRequestMillis, 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;
+            if (cachedResponseHeaders.validate(responseHeaders)) {
+                if (responseCache instanceof HttpResponseCache) {
+                    ((HttpResponseCache) responseCache).trackConditionalCacheHit();
+                }
+                // Discard the network response body. Combine the headers.
+                release(true);
+                setResponse(cachedResponseHeaders.combine(responseHeaders), cachedResponseBody);
                 return;
             } else {
-                IoUtils.closeQuietly(responseBodyToValidate);
-                responseBodyToValidate = null;
-                responseHeadersToValidate = null;
+                IoUtils.closeQuietly(cachedResponseBody);
             }
         }
 
diff --git a/luni/src/main/java/libcore/net/http/HttpResponseCache.java b/luni/src/main/java/libcore/net/http/HttpResponseCache.java
new file mode 100644
index 0000000..50ad5e2
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HttpResponseCache.java
@@ -0,0 +1,543 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+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.nio.charset.Charsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import libcore.io.Base64;
+import libcore.io.DiskLruCache;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Cache responses in a directory on the file system. Most clients should use
+ * {@code android.net.HttpResponseCache}, the stable, documented front end for
+ * this.
+ */
+public final class HttpResponseCache extends ResponseCache {
+    // TODO: add APIs to iterate the cache?
+    private static final int VERSION = 201105;
+    private static final int ENTRY_METADATA = 0;
+    private static final int ENTRY_BODY = 1;
+    private static final int ENTRY_COUNT = 2;
+
+    private final DiskLruCache cache;
+
+    /* read and write statistics, all guarded by 'this' */
+    private int writeSuccessCount;
+    private int writeAbortCount;
+    private int networkCount;
+    private int hitCount;
+    private int requestCount;
+
+    public HttpResponseCache(File directory, long maxSize) throws IOException {
+        cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
+    }
+
+    private String uriToKey(URI uri) {
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+            byte[] md5bytes = messageDigest.digest(uri.toString().getBytes(Charsets.UTF_8));
+            return IntegralToString.bytesToHexString(md5bytes, false);
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    @Override public CacheResponse get(URI uri, String requestMethod,
+            Map<String, List<String>> requestHeaders) {
+        String key = uriToKey(uri);
+        DiskLruCache.Snapshot snapshot;
+        Entry entry;
+        try {
+            snapshot = cache.get(key);
+            if (snapshot == null) {
+                return null;
+            }
+            entry = new Entry(new BufferedInputStream(snapshot.getInputStream(ENTRY_METADATA)));
+        } catch (IOException e) {
+            // Give up because the cache cannot be read.
+            return null;
+        }
+
+        if (!entry.matches(uri, requestMethod, requestHeaders)) {
+            snapshot.close();
+            return null;
+        }
+
+        InputStream body = newBodyInputStream(snapshot);
+        return entry.isHttps()
+                ? entry.newSecureCacheResponse(body)
+                : entry.newCacheResponse(body);
+    }
+
+    /**
+     * Returns an input stream that reads the body of a snapshot, closing the
+     * snapshot when the stream is closed.
+     */
+    private InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
+        return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
+            @Override public void close() throws IOException {
+                snapshot.close();
+                super.close();
+            }
+        };
+    }
+
+    @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+        if (!(urlConnection instanceof HttpURLConnection)) {
+            return null;
+        }
+
+        HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
+        String requestMethod = httpConnection.getRequestMethod();
+        String key = uriToKey(uri);
+
+        if (requestMethod.equals(HttpEngine.POST)
+                || requestMethod.equals(HttpEngine.PUT)
+                || requestMethod.equals(HttpEngine.DELETE)) {
+            try {
+                cache.remove(key);
+            } catch (IOException ignored) {
+                // The cache cannot be written.
+            }
+            return null;
+        } else if (!requestMethod.equals(HttpEngine.GET)) {
+            /*
+             * Don't cache non-GET responses. We're technically allowed to cache
+             * HEAD requests and some POST requests, but the complexity of doing
+             * so is high and the benefit is low.
+             */
+            return null;
+        }
+
+        HttpEngine httpEngine = getHttpEngine(httpConnection);
+        if (httpEngine == null) {
+            // Don't cache unless the HTTP implementation is ours.
+            return null;
+        }
+
+        ResponseHeaders response = httpEngine.getResponseHeaders();
+        if (response.hasVaryAll()) {
+            return null;
+        }
+
+        RawHeaders varyHeaders = httpEngine.getRequestHeaders().headers.getAll(response.varyFields);
+        Entry entry = new Entry(uri, varyHeaders, httpConnection);
+        DiskLruCache.Editor editor = null;
+        try {
+            editor = cache.edit(key);
+            if (editor == null) {
+                return null;
+            }
+            entry.writeTo(editor);
+            return new CacheRequestImpl(editor);
+        } catch (IOException e) {
+            // Give up because the cache cannot be written.
+            try {
+                if (editor != null) {
+                    editor.abort();
+                }
+            } catch (IOException ignored) {
+            }
+            return null;
+        }
+    }
+
+    private HttpEngine getHttpEngine(HttpURLConnection httpConnection) {
+        if (httpConnection instanceof HttpURLConnectionImpl) {
+            return ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
+        } else if (httpConnection instanceof HttpsURLConnectionImpl) {
+            return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine();
+        } else {
+            return null;
+        }
+    }
+
+    public DiskLruCache getCache() {
+        return cache;
+    }
+
+    synchronized int getWriteAbortCount() {
+        return writeAbortCount;
+    }
+
+    synchronized int getWriteSuccessCount() {
+        return writeSuccessCount;
+    }
+
+    synchronized void trackResponse(ResponseSource source) {
+        requestCount++;
+
+        switch (source) {
+        case CACHE:
+            hitCount++;
+            break;
+        case CONDITIONAL_CACHE:
+        case NETWORK:
+            networkCount++;
+            break;
+        }
+    }
+
+    synchronized void trackConditionalCacheHit() {
+        hitCount++;
+    }
+
+    public synchronized int getNetworkCount() {
+        return networkCount;
+    }
+
+    public synchronized int getHitCount() {
+        return hitCount;
+    }
+
+    public synchronized int getRequestCount() {
+        return requestCount;
+    }
+
+    private final class CacheRequestImpl extends CacheRequest {
+        private final DiskLruCache.Editor editor;
+        private OutputStream cacheOut;
+        private boolean done;
+        private OutputStream body;
+
+        public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
+            this.editor = editor;
+            this.cacheOut = editor.newOutputStream(ENTRY_BODY);
+            this.body = new FilterOutputStream(cacheOut) {
+                @Override public void close() throws IOException {
+                    synchronized (HttpResponseCache.this) {
+                        if (done) {
+                            return;
+                        }
+                        done = true;
+                        writeSuccessCount++;
+                    }
+                    super.close();
+                    editor.commit();
+                }
+            };
+        }
+
+        @Override public void abort() {
+            synchronized (HttpResponseCache.this) {
+                if (done) {
+                    return;
+                }
+                done = true;
+                writeAbortCount++;
+            }
+            IoUtils.closeQuietly(cacheOut);
+            try {
+                editor.abort();
+            } catch (IOException ignored) {
+            }
+        }
+
+        @Override public OutputStream getBody() throws IOException {
+            return body;
+        }
+    }
+
+    private static final class Entry {
+        private final String uri;
+        private final RawHeaders varyHeaders;
+        private final String requestMethod;
+        private final RawHeaders responseHeaders;
+        private final String cipherSuite;
+        private final Certificate[] peerCertificates;
+        private final Certificate[] localCertificates;
+
+        /*
+         * Reads an entry from an input stream. A typical entry looks like this:
+         *   http://google.com/foo
+         *   GET
+         *   2
+         *   Accept-Language: fr-CA
+         *   Accept-Charset: UTF-8
+         *   HTTP/1.1 200 OK
+         *   3
+         *   Content-Type: image/png
+         *   Content-Length: 100
+         *   Cache-Control: max-age=600
+         *
+         * A typical HTTPS file looks like this:
+         *   https://google.com/foo
+         *   GET
+         *   2
+         *   Accept-Language: fr-CA
+         *   Accept-Charset: UTF-8
+         *   HTTP/1.1 200 OK
+         *   3
+         *   Content-Type: image/png
+         *   Content-Length: 100
+         *   Cache-Control: max-age=600
+         *
+         *   AES_256_WITH_MD5
+         *   2
+         *   base64-encoded peerCertificate[0]
+         *   base64-encoded peerCertificate[1]
+         *   -1
+         *
+         * The file is newline separated. The first two lines are the URL and
+         * the request method. Next is the number of HTTP Vary request header
+         * lines, followed by those lines.
+         *
+         * Next is the response status line, followed by the number of HTTP
+         * response header lines, followed by those lines.
+         *
+         * HTTPS responses also contain SSL session information. This begins
+         * with a blank line, and then a line containing the cipher suite. Next
+         * is the length of the peer certificate chain. These certificates are
+         * base64-encoded and appear each on their own line. The next line
+         * contains the length of the local certificate chain. These
+         * certificates are also base64-encoded and appear each on their own
+         * line. A length of -1 is used to encode a null array.
+         */
+        public Entry(InputStream in) throws IOException {
+            try {
+                uri = Streams.readAsciiLine(in);
+                requestMethod = Streams.readAsciiLine(in);
+                varyHeaders = new RawHeaders();
+                int varyRequestHeaderLineCount = readInt(in);
+                for (int i = 0; i < varyRequestHeaderLineCount; i++) {
+                    varyHeaders.addLine(Streams.readAsciiLine(in));
+                }
+
+                responseHeaders = new RawHeaders();
+                responseHeaders.setStatusLine(Streams.readAsciiLine(in));
+                int responseHeaderLineCount = readInt(in);
+                for (int i = 0; i < responseHeaderLineCount; i++) {
+                    responseHeaders.addLine(Streams.readAsciiLine(in));
+                }
+
+                if (isHttps()) {
+                    String blank = Streams.readAsciiLine(in);
+                    if (!blank.isEmpty()) {
+                        throw new IOException("expected \"\" but was \"" + blank + "\"");
+                    }
+                    cipherSuite = Streams.readAsciiLine(in);
+                    peerCertificates = readCertArray(in);
+                    localCertificates = readCertArray(in);
+                } else {
+                    cipherSuite = null;
+                    peerCertificates = null;
+                    localCertificates = null;
+                }
+            } finally {
+                in.close();
+            }
+        }
+
+        public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) {
+            this.uri = uri.toString();
+            this.varyHeaders = varyHeaders;
+            this.requestMethod = httpConnection.getRequestMethod();
+            this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields());
+
+            if (isHttps()) {
+                HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
+                cipherSuite = httpsConnection.getCipherSuite();
+                Certificate[] peerCertificatesNonFinal = null;
+                try {
+                    peerCertificatesNonFinal = httpsConnection.getServerCertificates();
+                } catch (SSLPeerUnverifiedException ignored) {
+                }
+                peerCertificates = peerCertificatesNonFinal;
+                localCertificates = httpsConnection.getLocalCertificates();
+            } else {
+                cipherSuite = null;
+                peerCertificates = null;
+                localCertificates = null;
+            }
+        }
+
+        public void writeTo(DiskLruCache.Editor editor) throws IOException {
+            OutputStream out = editor.newOutputStream(0);
+            Writer writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.UTF_8));
+
+            writer.write(uri + '\n');
+            writer.write(requestMethod + '\n');
+            writer.write(Integer.toString(varyHeaders.length()) + '\n');
+            for (int i = 0; i < varyHeaders.length(); i++) {
+                writer.write(varyHeaders.getFieldName(i) + ": "
+                        + varyHeaders.getValue(i) + '\n');
+            }
+
+            writer.write(responseHeaders.getStatusLine() + '\n');
+            writer.write(Integer.toString(responseHeaders.length()) + '\n');
+            for (int i = 0; i < responseHeaders.length(); i++) {
+                writer.write(responseHeaders.getFieldName(i) + ": "
+                        + responseHeaders.getValue(i) + '\n');
+            }
+
+            if (isHttps()) {
+                writer.write('\n');
+                writer.write(cipherSuite + '\n');
+                writeCertArray(writer, peerCertificates);
+                writeCertArray(writer, localCertificates);
+            }
+            writer.close();
+        }
+
+        private boolean isHttps() {
+            return uri.startsWith("https://");
+        }
+
+        private int readInt(InputStream in) throws IOException {
+            String intString = Streams.readAsciiLine(in);
+            try {
+                return Integer.parseInt(intString);
+            } catch (NumberFormatException e) {
+                throw new IOException("expected an int but was \"" + intString + "\"");
+            }
+        }
+
+        private Certificate[] readCertArray(InputStream in) throws IOException {
+            int length = readInt(in);
+            if (length == -1) {
+                return null;
+            }
+            try {
+                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+                Certificate[] result = new Certificate[length];
+                for (int i = 0; i < result.length; i++) {
+                    String line = Streams.readAsciiLine(in);
+                    byte[] bytes = Base64.decode(line.getBytes(Charsets.US_ASCII));
+                    result[i] = certificateFactory.generateCertificate(
+                            new ByteArrayInputStream(bytes));
+                }
+                return result;
+            } catch (CertificateException e) {
+                throw new IOException(e);
+            }
+        }
+
+        private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
+            if (certificates == null) {
+                writer.write("-1\n");
+                return;
+            }
+            try {
+                writer.write(Integer.toString(certificates.length) + '\n');
+                for (Certificate certificate : certificates) {
+                    byte[] bytes = certificate.getEncoded();
+                    String line = Base64.encode(bytes);
+                    writer.write(line + '\n');
+                }
+            } catch (CertificateEncodingException e) {
+                throw new IOException(e);
+            }
+        }
+
+        public boolean matches(URI uri, String requestMethod,
+                Map<String, List<String>> requestHeaders) {
+            return this.uri.equals(uri.toString())
+                    && this.requestMethod.equals(requestMethod)
+                    && new ResponseHeaders(uri, responseHeaders)
+                            .varyMatches(varyHeaders.toMultimap(), requestHeaders);
+        }
+
+        public CacheResponse newCacheResponse(final InputStream in) {
+            return new CacheResponse() {
+                @Override public Map<String, List<String>> getHeaders() {
+                    return responseHeaders.toMultimap();
+                }
+
+                @Override public InputStream getBody() {
+                    return in;
+                }
+            };
+        }
+
+        public SecureCacheResponse newSecureCacheResponse(final InputStream in) {
+            return new SecureCacheResponse() {
+                @Override public Map<String, List<String>> getHeaders() {
+                    return responseHeaders.toMultimap();
+                }
+
+                @Override public InputStream getBody() {
+                    return in;
+                }
+
+                @Override public String getCipherSuite() {
+                    return cipherSuite;
+                }
+
+                @Override public List<Certificate> getServerCertificateChain()
+                        throws SSLPeerUnverifiedException {
+                    if (peerCertificates == null || peerCertificates.length == 0) {
+                        throw new SSLPeerUnverifiedException(null);
+                    }
+                    return Arrays.asList(peerCertificates.clone());
+                }
+
+                @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+                    if (peerCertificates == null || peerCertificates.length == 0) {
+                        throw new SSLPeerUnverifiedException(null);
+                    }
+                    return ((X509Certificate) peerCertificates[0]).getSubjectX500Principal();
+                }
+
+                @Override public List<Certificate> getLocalCertificateChain() {
+                    if (localCertificates == null || localCertificates.length == 0) {
+                        return null;
+                    }
+                    return Arrays.asList(localCertificates.clone());
+                }
+
+                @Override public Principal getLocalPrincipal() {
+                    if (localCertificates == null || localCertificates.length == 0) {
+                        return null;
+                    }
+                    return ((X509Certificate) localCertificates[0]).getSubjectX500Principal();
+                }
+            };
+        }
+    }
+}
diff --git a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
index 95b37fd..be4c132 100644
--- a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
@@ -33,10 +33,9 @@
 import java.net.URL;
 import java.nio.charset.Charsets;
 import java.security.Permission;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
 
 /**
  * This implementation uses HttpEngine to send requests and receive responses.
@@ -52,16 +51,13 @@
  * 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 {
+class HttpURLConnectionImpl extends HttpURLConnection {
 
     private final int defaultPort;
 
     private Proxy proxy;
 
-    // TODO: should these be set by URLConnection.setDefaultRequestProperty ?
-    private static RawHeaders defaultRequestHeaders = new RawHeaders();
-
-    protected RawHeaders rawRequestHeaders = new RawHeaders(defaultRequestHeaders);
+    private final RawHeaders rawRequestHeaders = new RawHeaders();
 
     private int redirectionCount;
 
@@ -88,13 +84,10 @@
         }
     }
 
-    /**
-     * Close the socket connection to the remote origin server or proxy.
-     */
     @Override public final void disconnect() {
-        // TODO: what happens if they call disconnect() before connect?
+        // Calling disconnect() before a connection exists should have no effect.
         if (httpEngine != null) {
-            httpEngine.releaseSocket(false);
+            httpEngine.release(false);
         }
     }
 
@@ -106,7 +99,7 @@
         try {
             HttpEngine response = getResponse();
             if (response.hasResponseBody()
-                    && response.getResponseHeaders().getResponseCode() >= HTTP_BAD_REQUEST) {
+                    && response.getResponseCode() >= HTTP_BAD_REQUEST) {
                 return response.getResponseBody();
             }
             return null;
@@ -121,7 +114,7 @@
      */
     @Override public final String getHeaderField(int position) {
         try {
-            return getResponse().getResponseHeaders().getValue(position);
+            return getResponse().getResponseHeaders().headers.getValue(position);
         } catch (IOException e) {
             return null;
         }
@@ -134,10 +127,10 @@
      */
     @Override public final String getHeaderField(String fieldName) {
         try {
-            RawHeaders responseHeaders = getResponse().getResponseHeaders();
+            RawHeaders rawHeaders = getResponse().getResponseHeaders().headers;
             return fieldName == null
-                    ? responseHeaders.getStatusLine()
-                    : responseHeaders.get(fieldName);
+                    ? rawHeaders.getStatusLine()
+                    : rawHeaders.get(fieldName);
         } catch (IOException e) {
             return null;
         }
@@ -145,7 +138,7 @@
 
     @Override public final String getHeaderFieldKey(int position) {
         try {
-            return getResponse().getResponseHeaders().getFieldName(position);
+            return getResponse().getResponseHeaders().headers.getFieldName(position);
         } catch (IOException e) {
             return null;
         }
@@ -153,7 +146,7 @@
 
     @Override public final Map<String, List<String>> getHeaderFields() {
         try {
-            return getResponse().getResponseHeaders().toMultimap();
+            return getResponse().getResponseHeaders().headers.toMultimap();
         } catch (IOException e) {
             return null;
         }
@@ -193,7 +186,15 @@
 
     @Override public final OutputStream getOutputStream() throws IOException {
         connect();
-        return httpEngine.getRequestBody();
+
+        OutputStream result = httpEngine.getRequestBody();
+        if (result == null) {
+            throw new ProtocolException("method does not support a request body: " + method);
+        } else if (httpEngine.hasResponse()) {
+            throw new ProtocolException("cannot write request body after response has been read");
+        }
+
+        return result;
     }
 
     @Override public final Permission getPermission() throws IOException {
@@ -230,6 +231,15 @@
 
         connected = true;
         try {
+            if (doOutput) {
+                if (method == HttpEngine.GET) {
+                    // they are requesting a stream to write to. This implies a POST method
+                    method = HttpEngine.POST;
+                } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {
+                    // If the request method is neither POST nor PUT, then you're not writing
+                    throw new ProtocolException(method + " does not support writing");
+                }
+            }
             httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
         } catch (IOException e) {
             httpEngineFailure = e;
@@ -265,34 +275,41 @@
 
                 Retry retry = processResponseHeaders();
                 if (retry == Retry.NONE) {
+                    httpEngine.automaticallyReleaseConnectionToPool();
                     break;
                 }
 
                 /*
                  * The first request was insufficient. Prepare for another...
                  */
+                String retryMethod = method;
                 OutputStream requestBody = httpEngine.getRequestBody();
+
+                /*
+                 * Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
+                 * redirect should keep the same method, Chrome, Firefox and the
+                 * RI all issue GETs when following any redirect.
+                 */
+                int responseCode = getResponseCode();
+                if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM
+                        || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) {
+                    retryMethod = HttpEngine.GET;
+                    requestBody = null;
+                }
+
                 if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
                     throw new HttpRetryException("Cannot retry streamed HTTP body",
-                            httpEngine.getResponseHeaders().getResponseCode());
+                            httpEngine.getResponseCode());
                 }
 
-                if (retry == Retry.SAME_CONNECTION && httpEngine.hasConnectionCloseHeaders()) {
-                    retry = Retry.NEW_CONNECTION;
+                if (retry == Retry.DIFFERENT_CONNECTION) {
+                    httpEngine.automaticallyReleaseConnectionToPool();
                 }
 
-                HttpConnection connection = null;
-                if (retry == Retry.NEW_CONNECTION) {
-                    httpEngine.discardResponseBody();
-                    httpEngine.releaseSocket(true);
-                } else {
-                    httpEngine.dontReleaseSocketToPool();
-                    httpEngine.discardResponseBody();
-                    connection = httpEngine.getConnection();
-                }
+                httpEngine.release(true);
 
-                httpEngine = newHttpEngine(method, rawRequestHeaders, connection,
-                        (RetryableOutputStream) requestBody);
+                httpEngine = newHttpEngine(retryMethod, rawRequestHeaders,
+                        httpEngine.getConnection(), (RetryableOutputStream) requestBody);
             }
             return httpEngine;
         } catch (IOException e) {
@@ -301,10 +318,14 @@
         }
     }
 
+    HttpEngine getHttpEngine() {
+        return httpEngine;
+    }
+
     enum Retry {
         NONE,
         SAME_CONNECTION,
-        NEW_CONNECTION
+        DIFFERENT_CONNECTION
     }
 
     /**
@@ -313,49 +334,32 @@
      * 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 ?
+        switch (getResponseCode()) {
+        case HTTP_PROXY_AUTH:
             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");
+            // fall-through
+        case HTTP_UNAUTHORIZED:
+            boolean credentialsFound = processAuthHeader(getResponseCode(),
+                    httpEngine.getResponseHeaders(), rawRequestHeaders);
+            return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
 
         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");
+            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())) {
@@ -365,9 +369,7 @@
                     && previousUrl.getEffectivePort() == url.getEffectivePort()) {
                 return Retry.SAME_CONNECTION;
             } else {
-                // TODO: strip cookies?
-                rawRequestHeaders.removeAll("Host");
-                return Retry.NEW_CONNECTION;
+                return Retry.DIFFERENT_CONNECTION;
             }
 
         default:
@@ -375,37 +377,36 @@
         }
     }
 
-    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.
+     *
+     * @return true if credentials have been added to successorRequestHeaders
+     *     and another request should be attempted.
      */
-    private Retry processAuthHeader(String fieldName, String value) throws IOException {
+    final boolean processAuthHeader(int responseCode, ResponseHeaders response,
+            RawHeaders successorRequestHeaders) throws IOException {
+        if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
+            throw new IllegalArgumentException();
+        }
+
         // keep asking for username/password until authorized
-        String challenge = httpEngine.getResponseHeaders().get(fieldName);
+        String challenge = responseCode == HTTP_PROXY_AUTH
+                ? response.proxyAuthenticate
+                : response.wwwAuthenticate;
         if (challenge == null) {
             throw new IOException("Received authentication challenge is null");
         }
         String credentials = getAuthorizationCredentials(challenge);
         if (credentials == null) {
-            return Retry.NONE; // could not find credentials, end request cycle
+            return false; // could not find credentials, end request cycle
         }
+
         // add authorization credentials, bypassing the already-connected check
-        rawRequestHeaders.set(value, credentials);
-        return Retry.SAME_CONNECTION;
+        String fieldName = responseCode == HTTP_PROXY_AUTH
+                ? "Proxy-Authorization"
+                : "Authorization";
+        successorRequestHeaders.set(fieldName, credentials);
+        return true;
     }
 
     /**
@@ -434,7 +435,7 @@
         // base64 encode the username and password
         String usernameAndPassword = pa.getUserName() + ":" + new String(pa.getPassword());
         byte[] bytes = usernameAndPassword.getBytes(Charsets.ISO_8859_1);
-        String encoded = Base64.encode(bytes, Charsets.ISO_8859_1);
+        String encoded = Base64.encode(bytes);
         return scheme + " " + encoded;
     }
 
@@ -466,23 +467,16 @@
         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();
+        return getResponse().getResponseHeaders().headers.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)));
+        return getResponse().getResponseCode();
     }
 
     @Override public final void setRequestProperty(String field, String newValue) {
diff --git a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
index 46f74f4..e385c9e 100644
--- a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
@@ -57,6 +57,10 @@
         }
     }
 
+    HttpEngine getHttpEngine() {
+        return delegate.getHttpEngine();
+    }
+
     @Override
     public String getCipherSuite() {
         SecureCacheResponse cacheResponse = delegate.getCacheResponse();
@@ -382,7 +386,8 @@
         }
 
         public SecureCacheResponse getCacheResponse() {
-            return (SecureCacheResponse) httpEngine.getCacheResponse();
+            HttpsEngine engine = (HttpsEngine) httpEngine;
+            return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null;
         }
 
         public SSLSocket getSSLSocket() {
@@ -432,7 +437,7 @@
                         && e.getCause() instanceof CertificateException) {
                     throw e;
                 }
-                releaseSocket(false);
+                release(false);
                 connectionReused = makeSslConnection(false);
             }
 
@@ -473,11 +478,36 @@
             return false;
         }
 
+        /**
+         * To make an HTTPS connection over an HTTP proxy, send an unencrypted
+         * CONNECT request to create the proxy connection. This may need to be
+         * retried if the proxy requires authorization.
+         */
         private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection,
-                RawHeaders requestHeaders) throws IOException {
-            HttpEngine connect = new ProxyConnectEngine(policy, requestHeaders, connection);
-            connect.sendRequest();
-            connect.readResponse();
+                RequestHeaders requestHeaders) throws IOException {
+            RawHeaders rawRequestHeaders = requestHeaders.headers;
+            while (true) {
+                HttpEngine connect = new ProxyConnectEngine(policy, rawRequestHeaders, connection);
+                connect.sendRequest();
+                connect.readResponse();
+
+                int responseCode = connect.getResponseCode();
+                switch (connect.getResponseCode()) {
+                case HTTP_OK:
+                    return;
+                case HTTP_PROXY_AUTH:
+                    rawRequestHeaders = new RawHeaders(rawRequestHeaders);
+                    boolean credentialsFound = policy.processAuthHeader(HTTP_PROXY_AUTH,
+                            connect.getResponseHeaders(), rawRequestHeaders);
+                    if (credentialsFound) {
+                        continue;
+                    } else {
+                        throw new IOException("Failed to authenticate with proxy");
+                    }
+                default:
+                    throw new IOException("Unexpected response code for CONNECT: " + responseCode);
+                }
+            }
         }
 
         @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
@@ -497,8 +527,7 @@
     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;
+            super(policy, HttpEngine.CONNECT, requestHeaders, connection, null);
         }
 
         /**
@@ -507,7 +536,7 @@
          * sensitive data like HTTP cookies to the proxy unencrypted.
          */
         @Override protected RawHeaders getNetworkRequestHeaders() throws IOException {
-            RawHeaders privateHeaders = getRequestHeaders();
+            RequestHeaders privateHeaders = getRequestHeaders();
             URL url = policy.getURL();
 
             RawHeaders result = new RawHeaders();
@@ -515,20 +544,20 @@
                     + " HTTP/1.1");
 
             // Always set Host and User-Agent.
-            String host = privateHeaders.get("Host");
+            String host = privateHeaders.host;
             if (host == null) {
                 host = getOriginAddress(url);
             }
             result.set("Host", host);
 
-            String userAgent = privateHeaders.get("User-Agent");
+            String userAgent = privateHeaders.userAgent;
             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");
+            String proxyAuthorization = privateHeaders.proxyAuthorization;
             if (proxyAuthorization != null) {
                 result.set("Proxy-Authorization", proxyAuthorization);
             }
diff --git a/luni/src/main/java/libcore/net/http/RawHeaders.java b/luni/src/main/java/libcore/net/http/RawHeaders.java
index a6ec3c3..75a1392a 100644
--- a/luni/src/main/java/libcore/net/http/RawHeaders.java
+++ b/luni/src/main/java/libcore/net/http/RawHeaders.java
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.TreeMap;
 
 /**
@@ -41,9 +42,9 @@
  * <p>This class trims whitespace from values. It never returns values with
  * leading or trailing whitespace.
  */
-final class RawHeaders implements Cloneable {
-
+final class RawHeaders {
     private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+        @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
         @Override public int compare(String a, String b) {
             if (a == b) {
                 return 0;
@@ -78,6 +79,7 @@
      * (like "GET / HTTP/1.1").
      */
     public void setStatusLine(String statusLine) {
+        statusLine = statusLine.trim();
         this.statusLine = statusLine;
 
         if (statusLine == null || !statusLine.startsWith("HTTP/")) {
@@ -128,6 +130,19 @@
     }
 
     /**
+     * Add an HTTP header line containing a field name, a literal colon, and a
+     * value.
+     */
+    public void addLine(String line) {
+        int index = line.indexOf(":");
+        if (index == -1) {
+            add("", line);
+        } else {
+            add(line.substring(0, index), line.substring(index + 1));
+        }
+    }
+
+    /**
      * Add a field with the specified value.
      */
     public void add(String fieldName, String value) {
@@ -162,12 +177,6 @@
         }
     }
 
-    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.
@@ -218,6 +227,20 @@
         return null;
     }
 
+    /**
+     * @param fieldNames a case-insensitive set of HTTP header field names.
+     */
+    public RawHeaders getAll(Set<String> fieldNames) {
+        RawHeaders result = new RawHeaders();
+        for (int i = 0; i < namesAndValues.size(); i += 2) {
+            String fieldName = namesAndValues.get(i);
+            if (fieldNames.contains(fieldName)) {
+                result.add(fieldName, namesAndValues.get(i + 1));
+            }
+        }
+        return result;
+    }
+
     public String toHeaderString() {
         StringBuilder result = new StringBuilder(256);
         result.append(statusLine).append("\r\n");
diff --git a/luni/src/main/java/libcore/net/http/RequestHeaders.java b/luni/src/main/java/libcore/net/http/RequestHeaders.java
index ce9354b..b143f81 100644
--- a/luni/src/main/java/libcore/net/http/RequestHeaders.java
+++ b/luni/src/main/java/libcore/net/http/RequestHeaders.java
@@ -17,6 +17,9 @@
 package libcore.net.http;
 
 import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Parsed HTTP request headers.
@@ -39,14 +42,6 @@
      * 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
@@ -55,6 +50,17 @@
      */
     boolean hasAuthorization;
 
+    int contentLength = -1;
+    String transferEncoding;
+    String userAgent;
+    String host;
+    String connection;
+    String acceptEncoding;
+    String contentType;
+    String ifModifiedSince;
+    String ifNoneMatch;
+    String proxyAuthorization;
+
     public RequestHeaders(URI uri, RawHeaders headers) {
         this.uri = uri;
         this.headers = headers;
@@ -63,7 +69,6 @@
             @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")) {
@@ -86,11 +91,129 @@
                     noCache = true;
                 }
             } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
-                hasConditions = true;
+                ifNoneMatch = value;
             } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
-                hasConditions = true;
+                ifModifiedSince = value;
             } else if ("Authorization".equalsIgnoreCase(fieldName)) {
                 hasAuthorization = true;
+            } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
+                try {
+                    contentLength = Integer.parseInt(value);
+                } catch (NumberFormatException ignored) {
+                }
+            } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
+                transferEncoding = value;
+            } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
+                userAgent = value;
+            } else if ("Host".equalsIgnoreCase(fieldName)) {
+                host = value;
+            } else if ("Connection".equalsIgnoreCase(fieldName)) {
+                connection = value;
+            } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) {
+                acceptEncoding = value;
+            } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
+                contentType = value;
+            } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
+                proxyAuthorization = value;
+            }
+        }
+    }
+
+    public boolean isChunked() {
+        return "chunked".equalsIgnoreCase(transferEncoding);
+    }
+
+    public boolean hasConnectionClose() {
+        return "close".equalsIgnoreCase(connection);
+    }
+
+    public void setChunked() {
+        if (this.transferEncoding != null) {
+            headers.removeAll("Transfer-Encoding");
+        }
+        headers.add("Transfer-Encoding", "chunked");
+        this.transferEncoding = "chunked";
+    }
+
+    public void setContentLength(int contentLength) {
+        if (this.contentLength != -1) {
+            headers.removeAll("Content-Length");
+        }
+        headers.add("Content-Length", Integer.toString(contentLength));
+        this.contentLength = contentLength;
+    }
+
+    public void setUserAgent(String userAgent) {
+        if (this.userAgent != null) {
+            headers.removeAll("User-Agent");
+        }
+        headers.add("User-Agent", userAgent);
+        this.userAgent = userAgent;
+    }
+
+    public void setHost(String host) {
+        if (this.host != null) {
+            headers.removeAll("Host");
+        }
+        headers.add("Host", host);
+        this.host = host;
+    }
+
+    public void setConnection(String connection) {
+        if (this.connection != null) {
+            headers.removeAll("Connection");
+        }
+        headers.add("Connection", connection);
+        this.connection = connection;
+    }
+
+    public void setAcceptEncoding(String acceptEncoding) {
+        if (this.acceptEncoding != null) {
+            headers.removeAll("Accept-Encoding");
+        }
+        headers.add("Accept-Encoding", acceptEncoding);
+        this.acceptEncoding = acceptEncoding;
+    }
+
+    public void setContentType(String contentType) {
+        if (this.contentType != null) {
+            headers.removeAll("Content-Type");
+        }
+        headers.add("Content-Type", contentType);
+        this.contentType = contentType;
+    }
+
+    public void setIfModifiedSince(Date date) {
+        if (ifModifiedSince != null) {
+            headers.removeAll("If-Modified-Since");
+        }
+        String formattedDate = HttpDate.format(date);
+        headers.add("If-Modified-Since", formattedDate);
+        ifModifiedSince = formattedDate;
+    }
+
+    public void setIfNoneMatch(String ifNoneMatch) {
+        if (this.ifNoneMatch != null) {
+            headers.removeAll("If-None-Match");
+        }
+        headers.add("If-None-Match", ifNoneMatch);
+        this.ifNoneMatch = ifNoneMatch;
+    }
+
+    /**
+     * Returns 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.
+     */
+    public boolean hasConditions() {
+        return ifModifiedSince != null || ifNoneMatch != null;
+    }
+
+    public void addCookies(Map<String, List<String>> allCookieHeaders) {
+        for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
+            String key = entry.getKey();
+            if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
+                headers.addAll(key, entry.getValue());
             }
         }
     }
diff --git a/luni/src/main/java/libcore/net/http/ResponseHeaders.java b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
index 2ee5a58..0764a53 100644
--- a/luni/src/main/java/libcore/net/http/ResponseHeaders.java
+++ b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
@@ -18,8 +18,14 @@
 
 import java.net.HttpURLConnection;
 import java.net.URI;
+import java.util.Collections;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
+import libcore.util.Objects;
 
 /**
  * Parsed HTTP response headers.
@@ -27,10 +33,10 @@
 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";
+    private 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";
+    private static final String RECEIVED_MILLIS = "X-Android-Received-Millis";
 
     final URI uri;
     final RawHeaders headers;
@@ -91,11 +97,20 @@
      * not permitted if this header is set.
      */
     boolean isPublic;
-    String privateField;
     boolean mustRevalidate;
     String etag;
     int ageSeconds = -1;
 
+    /** Case-insensitive set of field names. */
+    Set<String> varyFields = Collections.emptySet();
+
+    String contentEncoding;
+    String transferEncoding;
+    int contentLength = -1;
+    String connection;
+    String proxyAuthenticate;
+    String wwwAuthenticate;
+
     public ResponseHeaders(URI uri, RawHeaders headers) {
         this.uri = uri;
         this.headers = headers;
@@ -112,8 +127,6 @@
                     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;
                 }
@@ -139,6 +152,29 @@
                 }
             } else if ("Age".equalsIgnoreCase(fieldName)) {
                 ageSeconds = HeaderParser.parseSeconds(value);
+            } else if ("Vary".equalsIgnoreCase(fieldName)) {
+                // Replace the immutable empty set with something we can mutate.
+                if (varyFields.isEmpty()) {
+                    varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+                }
+                for (String varyField : value.split(",")) {
+                    varyFields.add(varyField.trim());
+                }
+            } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) {
+                contentEncoding = value;
+            } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
+                transferEncoding = value;
+            } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
+                try {
+                    contentLength = Integer.parseInt(value);
+                } catch (NumberFormatException ignored) {
+                }
+            } else if ("Connection".equalsIgnoreCase(fieldName)) {
+                connection = value;
+            } else if ("Proxy-Authenticate".equalsIgnoreCase(fieldName)) {
+                proxyAuthenticate = value;
+            } else if ("WWW-Authenticate".equalsIgnoreCase(fieldName)) {
+                wwwAuthenticate = value;
             } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
                 sentRequestMillis = Long.parseLong(value);
             } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
@@ -147,6 +183,30 @@
         }
     }
 
+    public boolean isContentEncodingGzip() {
+        return "gzip".equalsIgnoreCase(contentEncoding);
+    }
+
+    public void stripContentEncoding() {
+        contentEncoding = null;
+        headers.removeAll("Content-Encoding");
+    }
+
+    public boolean isChunked() {
+        return "chunked".equalsIgnoreCase(transferEncoding);
+    }
+
+    public boolean hasConnectionClose() {
+        return "close".equalsIgnoreCase(connection);
+    }
+
+    public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) {
+        this.sentRequestMillis = sentRequestMillis;
+        headers.add(SENT_MILLIS, Long.toString(sentRequestMillis));
+        this.receivedResponseMillis = receivedResponseMillis;
+        headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis));
+    }
+
     /**
      * Returns the current age of the response, in milliseconds. The calculation
      * is specified by RFC 2616, 13.2.3 Age Calculations.
@@ -234,6 +294,28 @@
     }
 
     /**
+     * Returns true if a Vary header contains an asterisk. Such responses cannot
+     * be cached.
+     */
+    public boolean hasVaryAll() {
+        return varyFields.contains("*");
+    }
+
+    /**
+     * Returns true if none of the Vary headers on this response have changed
+     * between {@code cachedRequest} and {@code newRequest}.
+     */
+    public boolean varyMatches(Map<String, List<String>> cachedRequest,
+            Map<String, List<String>> newRequest) {
+        for (String field : varyFields) {
+            if (!Objects.equal(cachedRequest.get(field), newRequest.get(field))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns the source to satisfy {@code request} given this cached response.
      */
     public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) {
@@ -246,7 +328,7 @@
             return ResponseSource.NETWORK;
         }
 
-        if (request.noCache || request.hasConditions) {
+        if (request.noCache || request.hasConditions()) {
             return ResponseSource.NETWORK;
         }
 
@@ -264,36 +346,31 @@
         }
 
         long maxStaleMillis = 0;
-        if (request.maxStaleSeconds != -1) {
+        if (!mustRevalidate && 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\"");
+                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\"");
+                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;
+            request.setIfModifiedSince(lastModified);
         } else if (servedDate != null) {
-            request.headers.add("If-Modified-Since", HttpDate.format(servedDate));
-            request.hasConditions = true;
+            request.setIfModifiedSince(servedDate);
         }
 
         if (etag != null) {
-            request.headers.add("If-None-Match", etag);
-            request.hasConditions = true;
+            request.setIfNoneMatch(etag);
         }
 
-        return request.hasConditions
+        return request.hasConditions()
                 ? ResponseSource.CONDITIONAL_CACHE
                 : ResponseSource.NETWORK;
     }
@@ -320,4 +397,47 @@
 
         return false;
     }
+
+    /**
+     * Combines this cached header with a network header as defined by RFC 2616,
+     * 13.5.3.
+     */
+    public ResponseHeaders combine(ResponseHeaders network) {
+        RawHeaders result = new RawHeaders();
+
+        for (int i = 0; i < headers.length(); i++) {
+            String fieldName = headers.getFieldName(i);
+            String value = headers.getValue(i);
+            if (fieldName.equals("Warning") && value.startsWith("1")) {
+                continue; // drop 100-level freshness warnings
+            }
+            if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) {
+                result.add(fieldName, value);
+            }
+        }
+
+        for (int i = 0; i < network.headers.length(); i++) {
+            String fieldName = network.headers.getFieldName(i);
+            if (isEndToEnd(fieldName)) {
+                result.add(fieldName, network.headers.getValue(i));
+            }
+        }
+
+        return new ResponseHeaders(uri, result);
+    }
+
+    /**
+     * Returns true if {@code fieldName} is an end-to-end HTTP header, as
+     * defined by RFC 2616, 13.5.1.
+     */
+    private static boolean isEndToEnd(String fieldName) {
+        return !fieldName.equalsIgnoreCase("Connection")
+                && !fieldName.equalsIgnoreCase("Keep-Alive")
+                && !fieldName.equalsIgnoreCase("Proxy-Authenticate")
+                && !fieldName.equalsIgnoreCase("Proxy-Authorization")
+                && !fieldName.equalsIgnoreCase("TE")
+                && !fieldName.equalsIgnoreCase("Trailers")
+                && !fieldName.equalsIgnoreCase("Transfer-Encoding")
+                && !fieldName.equalsIgnoreCase("Upgrade");
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java b/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java
index 4667416..3fd13f5 100644
--- a/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java
+++ b/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java
@@ -68,7 +68,7 @@
             Method[] m = annotationType.getDeclaredMethods();
             desc = new AnnotationMember[m.length];
             int idx = 0;
-            for(Method element : m) {
+            for (Method element : m) {
                 String name = element.getName();
                 Class<?> type = element.getReturnType();
                 try {
@@ -260,7 +260,7 @@
         result.append('@');
         result.append(klazz.getName());
         result.append('(');
-        for(int i = 0; i < elements.length; ++i) {
+        for (int i = 0; i < elements.length; ++i) {
             if (i != 0) {
                 result.append(", ");
             }
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
deleted file mode 100644
index 485d722..0000000
--- a/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
+++ /dev/null
@@ -1,100 +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 org.apache.harmony.luni.platform;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.SocketImpl;
-
-/*
- * The interface for network methods.
- */
-public interface INetworkSystem {
-    public void accept(FileDescriptor serverFd, SocketImpl newSocket, FileDescriptor clientFd)
-            throws IOException;
-
-    public void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException;
-
-    public int read(FileDescriptor fd, byte[] data, int offset, int count) throws IOException;
-
-    public int readDirect(FileDescriptor fd, int address, int count) throws IOException;
-
-    public int write(FileDescriptor fd, byte[] data, int offset, int count) throws IOException;
-
-    public int writeDirect(FileDescriptor fd, int address, int offset, int count) 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,
-            int port, InetAddress inetAddress) throws IOException;
-    public int sendDirect(FileDescriptor fd, int address, int offset, int length,
-            int port, InetAddress inetAddress) throws IOException;
-
-    public int recv(FileDescriptor fd, DatagramPacket packet, byte[] data, int offset,
-            int length, boolean peek, boolean connected) throws IOException;
-    public int recvDirect(FileDescriptor fd, DatagramPacket packet, int address, int offset,
-            int length, boolean peek, boolean connected) throws IOException;
-
-    public void disconnectDatagram(FileDescriptor fd) throws SocketException;
-
-    public void sendUrgentData(FileDescriptor fd, byte value);
-
-    /**
-     * Select the given file descriptors for read and write operations.
-     *
-     * <p>The first {@code numReadable} file descriptors of {@code readFDs} will
-     * be selected for read-ready operations. The first {@code numWritable} file
-     * descriptors in {@code writeFDs} will be selected for write-ready
-     * operations. A file descriptor can appear in either or both and must not
-     * be null. If the file descriptor is closed during the select the behavior
-     * depends upon the underlying OS.
-     *
-     * @param readFDs
-     *            all sockets interested in read and accept
-     * @param writeFDs
-     *            all sockets interested in write and connect
-     * @param numReadable
-     *            the size of the subset of readFDs to read or accept.
-     * @param numWritable
-     *            the size of the subset of writeFDs to write or connect
-     * @param timeout
-     *            timeout in milliseconds
-     * @param flags
-     *            for output. Length must be at least {@code numReadable
-     *            + numWritable}. Upon returning, each element describes the
-     *            state of the descriptor in the corresponding read or write
-     *            array. See {@code SelectorImpl.READABLE} and {@code
-     *            SelectorImpl.WRITEABLE}
-     * @return true
-     *            unless selection timed out or was interrupted
-     * @throws SocketException
-     */
-    public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
-            int numReadable, int numWritable, long timeout, int[] flags)
-            throws SocketException;
-
-    /**
-     * It is an error to close the same file descriptor from multiple threads
-     * concurrently.
-     */
-    public void close(FileDescriptor fd) throws IOException;
-}
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
deleted file mode 100644
index e98b41cd..0000000
--- a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
+++ /dev/null
@@ -1,97 +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 org.apache.harmony.luni.platform;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.SocketImpl;
-
-/**
- * This wraps native code that implements the INetworkSystem interface.
- * Address length was changed from long to int for performance reasons.
- */
-final class OSNetworkSystem implements INetworkSystem {
-    private static final OSNetworkSystem singleton = new OSNetworkSystem();
-
-    public static OSNetworkSystem getOSNetworkSystem() {
-        return singleton;
-    }
-
-    private OSNetworkSystem() {
-    }
-
-    public native void accept(FileDescriptor serverFd, SocketImpl newSocket,
-            FileDescriptor clientFd) throws IOException;
-
-    public native void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException;
-
-    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;
-
-    public native int read(FileDescriptor fd, byte[] data, int offset, int count)
-            throws IOException;
-
-    public native int readDirect(FileDescriptor fd, int address, int count) throws IOException;
-
-    public native int recv(FileDescriptor fd, DatagramPacket packet,
-            byte[] data, int offset, int length,
-            boolean peek, boolean connected) throws IOException;
-
-    public native int recvDirect(FileDescriptor fd, DatagramPacket packet,
-            int address, int offset, int length,
-            boolean peek, boolean connected) throws IOException;
-
-    public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
-            int numReadable, int numWritable, long timeout, int[] flags)
-            throws SocketException {
-        if (numReadable < 0 || numWritable < 0) {
-            throw new IllegalArgumentException();
-        }
-
-        int total = numReadable + numWritable;
-        if (total == 0) {
-            return true;
-        }
-
-        return selectImpl(readFDs, writeFDs, numReadable, numWritable, flags, timeout);
-    }
-
-    static native boolean selectImpl(FileDescriptor[] readfd,
-            FileDescriptor[] writefd, int cread, int cwirte, int[] flags,
-            long timeout);
-
-    public native int send(FileDescriptor fd, byte[] data, int offset, int length,
-            int port, InetAddress inetAddress) throws IOException;
-    public native int sendDirect(FileDescriptor fd, int address, int offset, int length,
-            int port, InetAddress inetAddress) throws IOException;
-
-    public native void sendUrgentData(FileDescriptor fd, byte value);
-
-    public native void close(FileDescriptor fd) throws IOException;
-
-    public native int write(FileDescriptor fd, byte[] data, int offset, int count)
-            throws IOException;
-
-    public native int writeDirect(FileDescriptor fd, int address, int offset, int count)
-            throws IOException;
-}
diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/Platform.java b/luni/src/main/java/org/apache/harmony/luni/platform/Platform.java
deleted file mode 100644
index db79a59..0000000
--- a/luni/src/main/java/org/apache/harmony/luni/platform/Platform.java
+++ /dev/null
@@ -1,43 +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 org.apache.harmony.luni.platform;
-
-import dalvik.system.BlockGuard;
-import dalvik.system.VMStack;
-
-/**
- * The Platform class gives access to the low-level underlying capabilities of
- * the operating system.
- *
- * The platform is structured into operations on the process heap memory,
- * network subsystem, and file system through different OS components.
- *
- * OS components are 'dangerous' in that they pass through the calls and
- * arguments to the OS with very little checking, and as such may cause fatal
- * exceptions in the runtime. Access to the OS components is restricted to
- * trusted code running on the system classpath.
- *
- * @see INetworkSystem
- */
-public class Platform {
-    // BlockGuard-policy-free threads should have no extra overhead, but for
-    // now they do because ThreadLocal lookups will be done on most operations, which
-    // should be relatively less than the speed of the operation.
-    // TODO: measure & fix if needed.
-    public static final INetworkSystem NETWORK =
-            new BlockGuard.WrappedNetworkSystem(OSNetworkSystem.getOSNetworkSystem());
-}
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
index ac76a89..7207002 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
@@ -36,8 +36,8 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import libcore.io.Base64;
 import libcore.io.Streams;
-import org.apache.harmony.luni.util.Base64;
 import org.apache.harmony.security.asn1.ASN1Constants;
 import org.apache.harmony.security.asn1.BerInputStream;
 import org.apache.harmony.security.pkcs7.ContentInfo;
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
index a144727..35bcb39 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
@@ -90,10 +90,6 @@
     // encoding of the certificate
     private volatile byte[] encoding;
 
-    //
-    // ---------------------- Constructors -------------------------------
-    //
-
     /**
      * Constructs the instance on the base of ASN.1 encoded
      * form of X.509 certificate provided via stream parameter.
@@ -133,10 +129,6 @@
         this((Certificate) Certificate.ASN1.decode(encoding));
     }
 
-    //
-    // ----------------- Public methods implementations ------------------
-    //
-
     public void checkValidity()
             throws CertificateExpiredException, CertificateNotYetValidException {
         checkValidity(System.currentTimeMillis());
@@ -351,11 +343,7 @@
         }
     }
 
-    //
-    // ----- java.security.cert.Certificate methods implementations ------
-    //
-
-    public byte[] getEncoded() throws CertificateEncodingException {
+    @Override public byte[] getEncoded() throws CertificateEncodingException {
         return getEncodedInternal().clone();
     }
     private byte[] getEncodedInternal() throws CertificateEncodingException {
@@ -366,7 +354,7 @@
         return result;
     }
 
-    public PublicKey getPublicKey() {
+    @Override public PublicKey getPublicKey() {
         PublicKey result = publicKey;
         if (result == null) {
             publicKey = result = tbsCert.getSubjectPublicKeyInfo().getPublicKey();
@@ -374,15 +362,14 @@
         return result;
     }
 
-    public String toString() {
+    @Override public String toString() {
         return certificate.toString();
     }
 
-    public void verify(PublicKey key)
-                         throws CertificateException, NoSuchAlgorithmException,
-                                InvalidKeyException, NoSuchProviderException,
-                                SignatureException {
-        if (getSigAlgName().endsWith("withRSA")) {
+    @Override public void verify(PublicKey key)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        if (getSigAlgName().endsWith("withRSA") || getSigAlgName().endsWith("WithRSAEncryption")) {
             fastVerify(key);
             return;
         }
@@ -398,11 +385,11 @@
         }
     }
 
-    public void verify(PublicKey key, String sigProvider)
-                         throws CertificateException, NoSuchAlgorithmException,
-                                InvalidKeyException, NoSuchProviderException,
-                                SignatureException {
-        if (getSigAlgName().endsWith("withRSA") && sigProvider == null) {
+    @Override public void verify(PublicKey key, String sigProvider)
+            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+            NoSuchProviderException, SignatureException {
+        if ((getSigAlgName().endsWith("withRSA") || getSigAlgName().endsWith("WithRSAEncryption"))
+                && sigProvider == null) {
             fastVerify(key);
             return;
         }
@@ -457,11 +444,7 @@
         }
     }
 
-    //
-    // ----- java.security.cert.X509Extension methods implementations ----
-    //
-
-    public Set<String> getNonCriticalExtensionOIDs() {
+    @Override public Set<String> getNonCriticalExtensionOIDs() {
         if (extensions == null) {
             return null;
         }
@@ -469,7 +452,7 @@
         return extensions.getNonCriticalExtensions();
     }
 
-    public Set<String> getCriticalExtensionOIDs() {
+    @Override public Set<String> getCriticalExtensionOIDs() {
         if (extensions == null) {
             return null;
         }
@@ -477,7 +460,7 @@
         return extensions.getCriticalExtensions();
     }
 
-    public byte[] getExtensionValue(String oid) {
+    @Override public byte[] getExtensionValue(String oid) {
         if (extensions == null) {
             return null;
         }
@@ -486,7 +469,7 @@
         return (ext == null) ? null : ext.getRawExtnValue();
     }
 
-    public boolean hasUnsupportedCriticalExtension() {
+    @Override public boolean hasUnsupportedCriticalExtension() {
         if (extensions == null) {
             return false;
         }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
index 42d7f0e..28aeef5 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
@@ -313,9 +313,6 @@
     /**
      * Returns a string identifier of the crypto tools used in the actual SSL
      * session. For example AES_256_WITH_MD5.
-     *
-     * @return an identifier for all the cryptographic algorithms used in the
-     *         actual SSL session.
      */
     public String getCipherSuite() {
         if (cipherSuite == null) {
@@ -331,10 +328,6 @@
     /**
      * Returns the standard version name of the SSL protocol used in all
      * connections pertaining to this SSL session.
-     *
-     * @return the standard version name of the SSL protocol used in all
-     *         connections pertaining to this SSL session.
-     *
      */
     public String getProtocol() {
         if (protocol == null) {
@@ -346,10 +339,6 @@
     /**
      * Returns the compression method name used in all connections
      * pertaining to this SSL session.
-     *
-     * @return the compresison method used in all connections
-     *         pertaining to this SSL session.
-     *
      */
     public String getCompressionMethod() {
         if (compressionMethod == null) {
diff --git a/luni/src/main/native/JniConstants.cpp b/luni/src/main/native/JniConstants.cpp
index 2681b47..ebdad0c 100644
--- a/luni/src/main/native/JniConstants.cpp
+++ b/luni/src/main/native/JniConstants.cpp
@@ -25,7 +25,6 @@
 jclass JniConstants::byteClass;
 jclass JniConstants::charsetICUClass;
 jclass JniConstants::constructorClass;
-jclass JniConstants::datagramPacketClass;
 jclass JniConstants::deflaterClass;
 jclass JniConstants::doubleClass;
 jclass JniConstants::errnoExceptionClass;
@@ -33,6 +32,7 @@
 jclass JniConstants::fieldPositionIteratorClass;
 jclass JniConstants::fileDescriptorClass;
 jclass JniConstants::gaiExceptionClass;
+jclass JniConstants::inet6AddressClass;
 jclass JniConstants::inetAddressClass;
 jclass JniConstants::inetSocketAddressClass;
 jclass JniConstants::inflaterClass;
@@ -53,6 +53,8 @@
 jclass JniConstants::structFlockClass;
 jclass JniConstants::structGroupReqClass;
 jclass JniConstants::structLingerClass;
+jclass JniConstants::structPasswdClass;
+jclass JniConstants::structPollfdClass;
 jclass JniConstants::structStatClass;
 jclass JniConstants::structStatFsClass;
 jclass JniConstants::structTimevalClass;
@@ -75,7 +77,6 @@
     byteArrayClass = findClass(env, "[B");
     charsetICUClass = findClass(env, "java/nio/charset/CharsetICU");
     constructorClass = findClass(env, "java/lang/reflect/Constructor");
-    datagramPacketClass = findClass(env, "java/net/DatagramPacket");
     deflaterClass = findClass(env, "java/util/zip/Deflater");
     doubleClass = findClass(env, "java/lang/Double");
     errnoExceptionClass = findClass(env, "libcore/io/ErrnoException");
@@ -83,6 +84,7 @@
     fieldPositionIteratorClass = findClass(env, "libcore/icu/NativeDecimalFormat$FieldPositionIterator");
     fileDescriptorClass = findClass(env, "java/io/FileDescriptor");
     gaiExceptionClass = findClass(env, "libcore/io/GaiException");
+    inet6AddressClass = findClass(env, "java/net/Inet6Address");
     inetAddressClass = findClass(env, "java/net/InetAddress");
     inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress");
     inflaterClass = findClass(env, "java/util/zip/Inflater");
@@ -103,6 +105,8 @@
     structFlockClass = findClass(env, "libcore/io/StructFlock");
     structGroupReqClass = findClass(env, "libcore/io/StructGroupReq");
     structLingerClass = findClass(env, "libcore/io/StructLinger");
+    structPasswdClass = findClass(env, "libcore/io/StructPasswd");
+    structPollfdClass = findClass(env, "libcore/io/StructPollfd");
     structStatClass = findClass(env, "libcore/io/StructStat");
     structStatFsClass = findClass(env, "libcore/io/StructStatFs");
     structTimevalClass = findClass(env, "libcore/io/StructTimeval");
diff --git a/luni/src/main/native/JniConstants.h b/luni/src/main/native/JniConstants.h
index af5056b..db52775 100644
--- a/luni/src/main/native/JniConstants.h
+++ b/luni/src/main/native/JniConstants.h
@@ -47,7 +47,6 @@
     static jclass byteClass;
     static jclass charsetICUClass;
     static jclass constructorClass;
-    static jclass datagramPacketClass;
     static jclass deflaterClass;
     static jclass doubleClass;
     static jclass errnoExceptionClass;
@@ -55,6 +54,7 @@
     static jclass fieldPositionIteratorClass;
     static jclass fileDescriptorClass;
     static jclass gaiExceptionClass;
+    static jclass inet6AddressClass;
     static jclass inetAddressClass;
     static jclass inetSocketAddressClass;
     static jclass inflaterClass;
@@ -75,6 +75,8 @@
     static jclass structFlockClass;
     static jclass structGroupReqClass;
     static jclass structLingerClass;
+    static jclass structPasswdClass;
+    static jclass structPollfdClass;
     static jclass structStatClass;
     static jclass structStatFsClass;
     static jclass structTimevalClass;
diff --git a/luni/src/main/native/NetworkUtilities.cpp b/luni/src/main/native/NetworkUtilities.cpp
index 26df02d..1b2c11e 100644
--- a/luni/src/main/native/NetworkUtilities.cpp
+++ b/luni/src/main/native/NetworkUtilities.cpp
@@ -19,6 +19,7 @@
 #include "NetworkUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "ScopedLocalRef.h"
 
 #include <arpa/inet.h>
 #include <fcntl.h>
@@ -26,42 +27,8 @@
 #include <string.h>
 #include <sys/socket.h>
 
-static bool byteArrayToSocketAddress(JNIEnv* env, jbyteArray byteArray, int port, sockaddr_storage* ss) {
-    if (byteArray == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return false;
-    }
-
-    // Convert the IP address bytes to the proper IP address type.
-    size_t addressLength = env->GetArrayLength(byteArray);
-    memset(ss, 0, sizeof(*ss));
-    if (addressLength == 4) {
-        // IPv4 address.
-        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss);
-        sin->sin_family = AF_INET;
-        sin->sin_port = htons(port);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
-        env->GetByteArrayRegion(byteArray, 0, 4, dst);
-    } else if (addressLength == 16) {
-        // IPv6 address.
-        sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss);
-        sin6->sin6_family = AF_INET6;
-        sin6->sin6_port = htons(port);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
-        env->GetByteArrayRegion(byteArray, 0, 16, dst);
-    } else {
-        // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one
-        // really does imply an internal error.
-        // TODO: fix the code (native and Java) so we don't paint ourselves into this corner.
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "byteArrayToSocketAddress bad array length (%i)", addressLength);
-        return false;
-    }
-    return true;
-}
-
-static jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) {
-    // Convert IPv4-mapped addresses to IPv4 addresses.
+jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage* ss, jint* port) {
+    // Convert IPv4-mapped IPv6 addresses to IPv4 addresses.
     // The RI states "Java will never return an IPv4-mapped address".
     sockaddr_storage tmp;
     memset(&tmp, 0, sizeof(tmp));
@@ -80,57 +47,117 @@
 
     const void* rawAddress;
     size_t addressLength;
+    int sin_port;
+    int scope_id = 0;
     if (ss->ss_family == AF_INET) {
         const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(ss);
         rawAddress = &sin->sin_addr.s_addr;
         addressLength = 4;
+        sin_port = ntohs(sin->sin_port);
     } else if (ss->ss_family == AF_INET6) {
         const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(ss);
         rawAddress = &sin6->sin6_addr.s6_addr;
         addressLength = 16;
+        sin_port = ntohs(sin6->sin6_port);
+        scope_id = sin6->sin6_scope_id;
     } else {
         // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one
         // really does imply an internal error.
-        // TODO: fix the code (native and Java) so we don't paint ourselves into this corner.
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "socketAddressToByteArray bad ss_family (%i)", ss->ss_family);
+                "sockaddrToInetAddress bad ss_family: %i", ss->ss_family);
         return NULL;
     }
+    if (port != NULL) {
+        *port = sin_port;
+    }
 
     jbyteArray byteArray = env->NewByteArray(addressLength);
     if (byteArray == NULL) {
         return NULL;
     }
     env->SetByteArrayRegion(byteArray, 0, addressLength, reinterpret_cast<const jbyte*>(rawAddress));
-    return byteArray;
-}
 
-static jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) {
-    if (byteArray == NULL) {
-        return NULL;
-    }
-    jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inetAddressClass,
-            "getByAddress", "([B)Ljava/net/InetAddress;");
+    static jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inetAddressClass,
+            "getByAddress", "(Ljava/lang/String;[BI)Ljava/net/InetAddress;");
     if (getByAddressMethod == NULL) {
         return NULL;
     }
-    return env->CallStaticObjectMethod(JniConstants::inetAddressClass, getByAddressMethod, byteArray);
+    return env->CallStaticObjectMethod(JniConstants::inetAddressClass, getByAddressMethod,
+            NULL, byteArray, scope_id);
 }
 
-jobject socketAddressToInetAddress(JNIEnv* env, const sockaddr_storage* ss) {
-    jbyteArray byteArray = socketAddressToByteArray(env, ss);
-    return byteArrayToInetAddress(env, byteArray);
-}
+static bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss, bool map) {
+    memset(ss, 0, sizeof(*ss));
 
-bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) {
-    // Get the byte array that stores the IP address bytes in the InetAddress.
     if (inetAddress == NULL) {
         jniThrowNullPointerException(env, NULL);
         return false;
     }
-    static jfieldID fid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B");
-    jbyteArray addressBytes = reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, fid));
-    return byteArrayToSocketAddress(env, addressBytes, port, ss);
+
+    // Get the address family.
+    static jfieldID familyFid = env->GetFieldID(JniConstants::inetAddressClass, "family", "I");
+    ss->ss_family = env->GetIntField(inetAddress, familyFid);
+    if (ss->ss_family == AF_UNSPEC) {
+        return true; // Job done!
+    }
+
+    // Check this is an address family we support.
+    if (ss->ss_family != AF_INET && ss->ss_family != AF_INET6) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "inetAddressToSockaddr bad family: %i", ss->ss_family);
+        return false;
+    }
+
+    // Get the byte array that stores the IP address bytes in the InetAddress.
+    static jfieldID bytesFid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B");
+    ScopedLocalRef<jbyteArray> addressBytes(env, reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, bytesFid)));
+    if (addressBytes.get() == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return false;
+    }
+
+    // We use AF_INET6 sockets, so we want an IPv6 address (which may be a IPv4-mapped address).
+    sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss);
+    sin6->sin6_port = htons(port);
+    if (ss->ss_family == AF_INET6) {
+        // IPv6 address. Copy the bytes...
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
+        env->GetByteArrayRegion(addressBytes.get(), 0, 16, dst);
+        // ...and set the scope id...
+        static jfieldID scopeFid = env->GetFieldID(JniConstants::inet6AddressClass, "scope_id", "I");
+        sin6->sin6_scope_id = env->GetIntField(inetAddress, scopeFid);
+        return true;
+    }
+
+    // Deal with Inet4Address instances.
+    if (map) {
+        // We should represent this Inet4Address as an IPv4-mapped IPv6 sockaddr_in6.
+        // Change the family...
+        sin6->sin6_family = AF_INET6;
+        // Copy the bytes...
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr[12]);
+        env->GetByteArrayRegion(addressBytes.get(), 0, 4, dst);
+        // INADDR_ANY and in6addr_any are both all-zeros...
+        if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+            // ...but all other IPv4-mapped addresses are ::ffff:a.b.c.d, so insert the ffff...
+            memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
+        }
+    } else {
+        // We should represent this Inet4Address as an IPv4 sockaddr_in.
+        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss);
+        sin->sin_port = htons(port);
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
+        env->GetByteArrayRegion(addressBytes.get(), 0, 4, dst);
+    }
+    return true;
+}
+
+bool inetAddressToSockaddr_getnameinfo(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) {
+    return inetAddressToSockaddr(env, inetAddress, port, ss, false);
+}
+
+bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) {
+    return inetAddressToSockaddr(env, inetAddress, port, ss, true);
 }
 
 bool setBlocking(int fd, bool blocking) {
diff --git a/luni/src/main/native/NetworkUtilities.h b/luni/src/main/native/NetworkUtilities.h
index 5e5b19e..8b1b6f1 100644
--- a/luni/src/main/native/NetworkUtilities.h
+++ b/luni/src/main/native/NetworkUtilities.h
@@ -17,11 +17,20 @@
 #include "jni.h"
 #include <sys/socket.h>
 
-// Convert from sockaddr_storage to InetAddress.
-jobject socketAddressToInetAddress(JNIEnv* env, const sockaddr_storage* ss);
+// Convert from sockaddr_storage to InetAddress and an optional int port.
+jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage* ss, int* port);
 
-// Convert from InetAddress to sockaddr_storage.
-bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
+// Convert from InetAddress to sockaddr_storage. An Inet4Address will be converted to
+// an IPv4-mapped AF_INET6 sockaddr_in6. This is what you want if you're about to perform an
+// operation on a socket, since all our sockets are AF_INET6.
+bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
+
+// Convert from InetAddress to sockaddr_storage. An Inet6Address will be converted to
+// a sockaddr_in6 while an Inet4Address will be converted to a sockaddr_in. This is
+// probably only useful for getnameinfo(2), where we'll be presenting the result to
+// the user and the user may actually care whether the original address was pure IPv4
+// or an IPv4-mapped IPv6 address.
+bool inetAddressToSockaddr_getnameinfo(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
 
 
 
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index e518944..a637a73 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -29,6 +29,7 @@
 extern int register_java_lang_ProcessManager(JNIEnv* env);
 extern int register_java_lang_RealToString(JNIEnv* env);
 extern int register_java_lang_StrictMath(JNIEnv* env);
+extern int register_java_lang_StringToReal(JNIEnv* env);
 extern int register_java_lang_System(JNIEnv* env);
 extern int register_java_math_NativeBN(JNIEnv* env);
 extern int register_java_nio_ByteOrder(JNIEnv* env);
@@ -49,13 +50,12 @@
 extern int register_libcore_icu_NativeNormalizer(JNIEnv* env);
 extern int register_libcore_icu_NativePluralRules(JNIEnv* env);
 extern int register_libcore_icu_TimeZones(JNIEnv* env);
+extern int register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env);
 extern int register_libcore_io_Memory(JNIEnv* env);
 extern int register_libcore_io_OsConstants(JNIEnv* env);
 extern int register_libcore_io_Posix(JNIEnv* env);
 extern int register_libcore_net_RawSocket(JNIEnv* env);
 extern int register_org_apache_harmony_dalvik_NativeTestTarget(JNIEnv* env);
-extern int register_org_apache_harmony_luni_platform_OSNetworkSystem(JNIEnv* env);
-extern int register_org_apache_harmony_luni_util_fltparse(JNIEnv* env);
 extern int register_org_apache_harmony_xml_ExpatParser(JNIEnv* env);
 extern int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env);
 
@@ -74,6 +74,7 @@
             register_java_lang_ProcessManager(env) != -1 &&
             register_java_lang_RealToString(env) != -1 &&
             register_java_lang_StrictMath(env) != -1 &&
+            register_java_lang_StringToReal(env) != -1 &&
             register_java_lang_System(env) != -1 &&
             register_java_math_NativeBN(env) != -1 &&
             register_java_nio_ByteOrder(env) != -1 &&
@@ -94,13 +95,12 @@
             register_libcore_icu_NativeNormalizer(env) != -1 &&
             register_libcore_icu_NativePluralRules(env) != -1 &&
             register_libcore_icu_TimeZones(env) != -1 &&
+            register_libcore_io_AsynchronousCloseMonitor(env) != -1 &&
             register_libcore_io_Memory(env) != -1 &&
             register_libcore_io_OsConstants(env) != -1 &&
             register_libcore_io_Posix(env) != -1 &&
             register_libcore_net_RawSocket(env) != -1 &&
             register_org_apache_harmony_dalvik_NativeTestTarget(env) != -1 &&
-            register_org_apache_harmony_luni_platform_OSNetworkSystem(env) != -1 &&
-            register_org_apache_harmony_luni_util_fltparse(env) != -1 &&
             register_org_apache_harmony_xml_ExpatParser(env) != -1 &&
             register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(env) != -1 &&
             true;
diff --git a/luni/src/main/native/java_lang_ProcessManager.cpp b/luni/src/main/native/java_lang_ProcessManager.cpp
index 2a1638f..3648a8f 100644
--- a/luni/src/main/native/java_lang_ProcessManager.cpp
+++ b/luni/src/main/native/java_lang_ProcessManager.cpp
@@ -18,10 +18,8 @@
 
 #include <sys/resource.h>
 #include <sys/types.h>
-#include <sys/wait.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <signal.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
@@ -29,90 +27,9 @@
 #include "jni.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "ScopedLocalRef.h"
 #include "utils/Log.h"
 
-/*
- * These are constants shared with the higher level code in
- * ProcessManager.java.
- */
-#define WAIT_STATUS_UNKNOWN (-1)       // unknown child status
-#define WAIT_STATUS_NO_CHILDREN (-2)   // no children to wait for
-#define WAIT_STATUS_STRANGE_ERRNO (-3) // observed an undocumented errno
-
-/**
- * Loops indefinitely and calls ProcessManager.onExit() when children exit.
- */
-static void ProcessManager_watchChildren(JNIEnv* env, jclass processManagerClass, jobject processManager) {
-    static jmethodID onExitMethod = env->GetMethodID(processManagerClass, "onExit", "(II)V");
-    if (onExitMethod == NULL) {
-        return;
-    }
-
-    while (true) {
-        // Wait for children in our process group.
-        int status;
-        pid_t pid = waitpid(0, &status, 0);
-
-        if (pid >= 0) {
-            // Extract real status.
-            if (WIFEXITED(status)) {
-                status = WEXITSTATUS(status);
-            } else if (WIFSIGNALED(status)) {
-                status = WTERMSIG(status);
-            } else if (WIFSTOPPED(status)) {
-                status = WSTOPSIG(status);
-            } else {
-                status = WAIT_STATUS_UNKNOWN;
-            }
-        } else {
-            /*
-             * The pid should be -1 already, but force it here just in case
-             * we somehow end up with some other negative value.
-             */
-            pid = -1;
-
-            switch (errno) {
-                case ECHILD: {
-                    /*
-                     * Expected errno: There are no children to wait()
-                     * for. The callback will sleep until it is
-                     * informed of another child coming to life.
-                     */
-                    status = WAIT_STATUS_NO_CHILDREN;
-                    break;
-                }
-                case EINTR: {
-                    /*
-                     * An unblocked signal came in while waiting; just
-                     * retry the wait().
-                     */
-                    continue;
-                }
-                default: {
-                    /*
-                     * Unexpected errno, so squawk! Note: Per the
-                     * Linux docs, there are no errnos defined for
-                     * wait() other than the two that are handled
-                     * immediately above.
-                     */
-                    LOGE("Error %d calling wait(): %s", errno, strerror(errno));
-                    status = WAIT_STATUS_STRANGE_ERRNO;
-                    break;
-                }
-            }
-        }
-
-        env->CallVoidMethod(processManager, onExitMethod, pid, status);
-        if (env->ExceptionOccurred()) {
-            /*
-             * The callback threw, so break out of the loop and return,
-             * letting the exception percolate up.
-             */
-            break;
-        }
-    }
-}
-
 /** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */
 static void closeNonStandardFds(int skipFd1, int skipFd2) {
     // TODO: rather than close all these non-open files, we could look in /proc/self/fd.
@@ -274,11 +191,11 @@
     jsize length = env->GetArrayLength(javaArray);
     char** array = new char*[length + 1];
     array[length] = 0;
-    for (jsize index = 0; index < length; index++) {
-        jstring javaEntry = (jstring) env->GetObjectArrayElement(javaArray, index);
+    for (jsize i = 0; i < length; ++i) {
+        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
         // We need to pass these strings to const-unfriendly code.
-        char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry, NULL));
-        array[index] = entry;
+        char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry.get(), NULL));
+        array[i] = entry;
     }
 
     return array;
@@ -291,9 +208,9 @@
     }
 
     jsize length = env->GetArrayLength(javaArray);
-    for (jsize index = 0; index < length; index++) {
-        jstring javaEntry = reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, index));
-        env->ReleaseStringUTFChars(javaEntry, array[index]);
+    for (jsize i = 0; i < length; ++i) {
+        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
+        env->ReleaseStringUTFChars(javaEntry.get(), array[i]);
     }
 
     delete[] array;
@@ -346,7 +263,6 @@
 }
 
 static JNINativeMethod methods[] = {
-    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_lang_RealToString.cpp b/luni/src/main/native/java_lang_RealToString.cpp
index f6d50b2..835151e 100644
--- a/luni/src/main/native/java_lang_RealToString.cpp
+++ b/luni/src/main/native/java_lang_RealToString.cpp
@@ -23,6 +23,7 @@
 
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
 #include "cbigint.h"
 
@@ -168,8 +169,8 @@
     }
 
   static jfieldID digitsFid = env->GetFieldID(JniConstants::realToStringClass, "digits", "[I");
-  jintArray javaDigits = reinterpret_cast<jintArray>(env->GetObjectField(obj, digitsFid));
-  ScopedIntArrayRW digits(env, javaDigits);
+  ScopedLocalRef<jintArray> javaDigits(env, reinterpret_cast<jintArray>(env->GetObjectField(obj, digitsFid)));
+  ScopedIntArrayRW digits(env, javaDigits.get());
   if (digits.get() == NULL) {
     return;
   }
diff --git a/luni/src/main/native/org_apache_harmony_luni_util_FloatingPointParser.cpp b/luni/src/main/native/java_lang_StringToReal.cpp
similarity index 97%
rename from luni/src/main/native/org_apache_harmony_luni_util_FloatingPointParser.cpp
rename to luni/src/main/native/java_lang_StringToReal.cpp
index 70a6d4b..06aa076 100644
--- a/luni/src/main/native/org_apache_harmony_luni_util_FloatingPointParser.cpp
+++ b/luni/src/main/native/java_lang_StringToReal.cpp
@@ -989,7 +989,7 @@
   return z;
 }
 
-static jfloat FloatingPointParser_parseFltImpl(JNIEnv* env, jclass, jstring s, jint e) {
+static jfloat StringToReal_parseFltImpl(JNIEnv* env, jclass, jstring s, jint e) {
     ScopedUtfChars str(env, s);
     if (str.c_str() == NULL) {
         return 0.0;
@@ -997,7 +997,7 @@
     return createFloat(env, str.c_str(), e);
 }
 
-static jdouble FloatingPointParser_parseDblImpl(JNIEnv* env, jclass, jstring s, jint e) {
+static jdouble StringToReal_parseDblImpl(JNIEnv* env, jclass, jstring s, jint e) {
     ScopedUtfChars str(env, s);
     if (str.c_str() == NULL) {
         return 0.0;
@@ -1006,10 +1006,9 @@
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(FloatingPointParser, parseFltImpl, "(Ljava/lang/String;I)F"),
-    NATIVE_METHOD(FloatingPointParser, parseDblImpl, "(Ljava/lang/String;I)D"),
+    NATIVE_METHOD(StringToReal, parseFltImpl, "(Ljava/lang/String;I)F"),
+    NATIVE_METHOD(StringToReal, parseDblImpl, "(Ljava/lang/String;I)D"),
 };
-int register_org_apache_harmony_luni_util_fltparse(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "org/apache/harmony/luni/util/FloatingPointParser",
-                gMethods, NELEM(gMethods));
+int register_java_lang_StringToReal(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "java/lang/StringToReal", gMethods, NELEM(gMethods));
 }
diff --git a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
new file mode 100644
index 0000000..d3fdabf
--- /dev/null
+++ b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
@@ -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.
+ */
+
+#define LOG_TAG "AsynchronousCloseMonitor"
+
+#include "AsynchronousSocketCloseMonitor.h"
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "jni.h"
+
+static void AsynchronousCloseMonitor_signalBlockedThreads(JNIEnv* env, jclass, jobject javaFd) {
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    AsynchronousSocketCloseMonitor::signalBlockedThreads(fd);
+}
+
+static JNINativeMethod gMethods[] = {
+    NATIVE_METHOD(AsynchronousCloseMonitor, signalBlockedThreads, "(Ljava/io/FileDescriptor;)V"),
+};
+
+int register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env) {
+    AsynchronousSocketCloseMonitor::init();
+    return jniRegisterNativeMethods(env, "libcore/io/AsynchronousCloseMonitor", gMethods, NELEM(gMethods));
+}
diff --git a/luni/src/main/native/libcore_io_OsConstants.cpp b/luni/src/main/native/libcore_io_OsConstants.cpp
index dd8ebf2..942cfce 100644
--- a/luni/src/main/native/libcore_io_OsConstants.cpp
+++ b/luni/src/main/native/libcore_io_OsConstants.cpp
@@ -18,7 +18,6 @@
 
 #include "JNIHelp.h"
 #include "JniConstants.h"
-#include "ScopedLocalRef.h"
 
 #include <errno.h>
 #include <fcntl.h>
@@ -26,6 +25,7 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
+#include <poll.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
@@ -236,6 +236,16 @@
     initConstant(env, c, "O_SYNC", O_SYNC);
     initConstant(env, c, "O_TRUNC", O_TRUNC);
     initConstant(env, c, "O_WRONLY", O_WRONLY);
+    initConstant(env, c, "POLLERR", POLLERR);
+    initConstant(env, c, "POLLHUP", POLLHUP);
+    initConstant(env, c, "POLLIN", POLLIN);
+    initConstant(env, c, "POLLNVAL", POLLNVAL);
+    initConstant(env, c, "POLLOUT", POLLOUT);
+    initConstant(env, c, "POLLPRI", POLLPRI);
+    initConstant(env, c, "POLLRDBAND", POLLRDBAND);
+    initConstant(env, c, "POLLRDNORM", POLLRDNORM);
+    initConstant(env, c, "POLLWRBAND", POLLWRBAND);
+    initConstant(env, c, "POLLWRNORM", POLLWRNORM);
     initConstant(env, c, "PROT_EXEC", PROT_EXEC);
     initConstant(env, c, "PROT_NONE", PROT_NONE);
     initConstant(env, c, "PROT_READ", PROT_READ);
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index aa69eac..5b02862 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -16,11 +16,13 @@
 
 #define LOG_TAG "Posix"
 
+#include "AsynchronousSocketCloseMonitor.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
 #include "NetworkUtilities.h"
 #include "ScopedBytes.h"
+#include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedUtfChars.h"
 #include "StaticAssert.h"
@@ -34,6 +36,8 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/in.h>
+#include <poll.h>
+#include <pwd.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
@@ -47,8 +51,13 @@
 #include <sys/uio.h>
 #include <sys/utsname.h>
 #include <sys/vfs.h> // Bionic doesn't have <sys/statvfs.h>
+#include <sys/wait.h>
 #include <unistd.h>
 
+#define TO_JAVA_STRING(NAME, EXP) \
+        jstring NAME = env->NewStringUTF(EXP); \
+        if (NAME == NULL) return NULL;
+
 struct addrinfo_deleter {
     void operator()(addrinfo* p) const {
         if (p != NULL) { // bionic's freeaddrinfo(3) crashes when passed NULL.
@@ -57,6 +66,43 @@
     }
 };
 
+/**
+ * Used to retry syscalls that can return EINTR. This differs from TEMP_FAILURE_RETRY in that
+ * it also considers the case where the reason for failure is that another thread called
+ * Socket.close.
+ *
+ * Assumes 'JNIEnv* env' and 'jobject javaFd' (which is a java.io.FileDescriptor) are in scope.
+ *
+ * Returns the result of 'exp', though a Java exception will be pending if the result is -1.
+ *
+ * Correct usage looks like this:
+ *
+ * void Posix_syscall(JNIEnv* env, jobject javaFd, ...) {
+ *     ...
+ *     int fd;
+ *     NET_FAILURE_RETRY("syscall", syscall(fd, ...)); // Throws on error.
+ * }
+ */
+#define NET_FAILURE_RETRY(syscall_name, exp) ({ \
+    typeof (exp) _rc = -1; \
+    do { \
+        { \
+            fd = jniGetFDFromFileDescriptor(env, javaFd); \
+            AsynchronousSocketCloseMonitor monitor(fd); \
+            _rc = (exp); \
+        } \
+        if (_rc == -1) { \
+            if (jniGetFDFromFileDescriptor(env, javaFd) == -1) { \
+                jniThrowException(env, "java/net/SocketException", "Socket closed"); \
+                break; \
+            } else if (errno != EINTR) { \
+                throwErrnoException(env, syscall_name); \
+                break; \
+            } \
+        } \
+    } while (_rc == -1); \
+    _rc; })
+
 static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2,
         const char* functionName, int error) {
     jthrowable cause = NULL;
@@ -132,8 +178,9 @@
             return false;
         }
         // TODO: Linux actually has a 1024 buffer limit. glibc works around this, and we should too.
+        // TODO: you can query the limit at runtime with sysconf(_SC_IOV_MAX).
         for (size_t i = 0; i < mBufferCount; ++i) {
-            jobject buffer = mEnv->GetObjectArrayElement(javaBuffers, i);
+            jobject buffer = mEnv->GetObjectArrayElement(javaBuffers, i); // We keep this local ref.
             mScopedBuffers.push_back(new ScopedT(mEnv, buffer));
             jbyte* ptr = const_cast<jbyte*>(mScopedBuffers.back()->get());
             if (ptr == NULL) {
@@ -169,8 +216,10 @@
     std::vector<ScopedT*> mScopedBuffers;
 };
 
-static jobject makeInetSocketAddress(JNIEnv* env, const sockaddr_storage* ss, int port) {
-    jobject inetAddress = socketAddressToInetAddress(env, ss);
+static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage* ss) {
+    // TODO: support AF_UNIX and AF_UNSPEC (and other families?)
+    jint port;
+    jobject inetAddress = sockaddrToInetAddress(env, ss, &port);
     if (inetAddress == NULL) {
         return NULL;
     }
@@ -179,16 +228,14 @@
     return env->NewObject(JniConstants::inetSocketAddressClass, ctor, inetAddress, port);
 }
 
-static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage* ss) {
-    if (ss->ss_family == AF_INET) {
-        int port = ntohs(reinterpret_cast<const sockaddr_in*>(ss)->sin_port);
-        return makeInetSocketAddress(env, ss, port);
-    } else if (ss->ss_family == AF_INET6) {
-        int port = ntohs(reinterpret_cast<const sockaddr_in6*>(ss)->sin6_port);
-        return makeInetSocketAddress(env, ss, port);
-    }
-    // TODO: support AF_UNIX and AF_UNSPEC, and have some other behavior for other families
-    return NULL;
+static jobject makeStructPasswd(JNIEnv* env, const struct passwd& pw) {
+    TO_JAVA_STRING(pw_name, pw.pw_name);
+    TO_JAVA_STRING(pw_dir, pw.pw_dir);
+    TO_JAVA_STRING(pw_shell, pw.pw_shell);
+    static jmethodID ctor = env->GetMethodID(JniConstants::structPasswdClass, "<init>",
+            "(Ljava/lang/String;IILjava/lang/String;Ljava/lang/String;)V");
+    return env->NewObject(JniConstants::structPasswdClass, ctor,
+            pw_name, static_cast<jint>(pw.pw_uid), static_cast<jint>(pw.pw_gid), pw_dir, pw_shell);
 }
 
 static jobject makeStructStat(JNIEnv* env, const struct stat& sb) {
@@ -230,17 +277,11 @@
 }
 
 static jobject makeStructUtsname(JNIEnv* env, const struct utsname& buf) {
-#define TO_JAVA_STRING(NAME) \
-        jstring NAME = env->NewStringUTF(buf. NAME); \
-        if (NAME == NULL) return NULL;
-
-    TO_JAVA_STRING(sysname);
-    TO_JAVA_STRING(nodename);
-    TO_JAVA_STRING(release);
-    TO_JAVA_STRING(version);
-    TO_JAVA_STRING(machine);
-#undef TO_JAVA_STRING
-
+    TO_JAVA_STRING(sysname, buf.sysname);
+    TO_JAVA_STRING(nodename, buf.nodename);
+    TO_JAVA_STRING(release, buf.release);
+    TO_JAVA_STRING(version, buf.version);
+    TO_JAVA_STRING(machine, buf.machine);
     static jmethodID ctor = env->GetMethodID(JniConstants::structUtsnameClass, "<init>",
             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     return env->NewObject(JniConstants::structUtsnameClass, ctor,
@@ -258,6 +299,23 @@
     return true;
 }
 
+static bool fillInetSocketAddress(JNIEnv* env, jint rc, jobject javaInetSocketAddress, const sockaddr_storage* ss) {
+    if (rc == -1 || javaInetSocketAddress == NULL) {
+        return true;
+    }
+    // Fill out the passed-in InetSocketAddress with the sender's IP address and port number.
+    jint port;
+    jobject sender = sockaddrToInetAddress(env, ss, &port);
+    if (sender == NULL) {
+        return false;
+    }
+    static jfieldID addressFid = env->GetFieldID(JniConstants::inetSocketAddressClass, "addr", "Ljava/net/InetAddress;");
+    static jfieldID portFid = env->GetFieldID(JniConstants::inetSocketAddressClass, "port", "I");
+    env->SetObjectField(javaInetSocketAddress, addressFid, sender);
+    env->SetIntField(javaInetSocketAddress, portFid, port);
+    return true;
+}
+
 static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
     ScopedUtfChars path(env, javaPath);
     if (path.c_str() == NULL) {
@@ -273,6 +331,62 @@
     return makeStructStat(env, sb);
 }
 
+class Passwd {
+public:
+    Passwd(JNIEnv* env) : mEnv(env), mResult(NULL) {
+        mBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX);
+        if (mBufferSize == -1UL) {
+            // We're probably on bionic, where 1KiB should be enough for anyone.
+            // TODO: fix bionic to return 1024 like glibc.
+            mBufferSize = 1024;
+        }
+        mBuffer.reset(new char[mBufferSize]);
+    }
+
+    jobject getpwnam(const char* name) {
+        return process("getpwnam_r", getpwnam_r(name, &mPwd, mBuffer.get(), mBufferSize, &mResult));
+    }
+
+    jobject getpwuid(uid_t uid) {
+        return process("getpwuid_r", getpwuid_r(uid, &mPwd, mBuffer.get(), mBufferSize, &mResult));
+    }
+
+    struct passwd* get() {
+        return mResult;
+    }
+
+private:
+    jobject process(const char* syscall, int error) {
+        if (mResult == NULL) {
+            errno = error;
+            throwErrnoException(mEnv, syscall);
+            return NULL;
+        }
+        return makeStructPasswd(mEnv, *mResult);
+    }
+
+    JNIEnv* mEnv;
+    UniquePtr<char[]> mBuffer;
+    size_t mBufferSize;
+    struct passwd mPwd;
+    struct passwd* mResult;
+};
+
+static jobject Posix_accept(JNIEnv* env, jobject, jobject javaFd, jobject javaInetSocketAddress) {
+    sockaddr_storage ss;
+    socklen_t sl = sizeof(ss);
+    memset(&ss, 0, sizeof(ss));
+    int fd;
+    sockaddr* peer = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
+    socklen_t* peerLength = (javaInetSocketAddress != NULL) ? &sl : 0;
+    jint clientFd = NET_FAILURE_RETRY("accept", accept(fd, peer, peerLength));
+    if (clientFd == -1 || !fillInetSocketAddress(env, clientFd, javaInetSocketAddress, &ss)) {
+        close(clientFd);
+        return NULL;
+    }
+    return (clientFd != -1) ? jniCreateFileDescriptor(env, clientFd) : NULL;
+}
+
 static jboolean Posix_access(JNIEnv* env, jobject, jstring javaPath, jint mode) {
     ScopedUtfChars path(env, javaPath);
     if (path.c_str() == NULL) {
@@ -285,6 +399,16 @@
     return (rc == 0);
 }
 
+static void Posix_bind(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) {
+    sockaddr_storage ss;
+    if (!inetAddressToSockaddr(env, javaAddress, port, &ss)) {
+        return;
+    }
+    int fd;
+    const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss);
+    NET_FAILURE_RETRY("bind", bind(fd, sa, sizeof(sockaddr_storage)));
+}
+
 static void Posix_chmod(JNIEnv* env, jobject, jstring javaPath, jint mode) {
     ScopedUtfChars path(env, javaPath);
     if (path.c_str() == NULL) {
@@ -305,6 +429,28 @@
     throwIfMinusOne(env, "close", close(fd));
 }
 
+static void Posix_connect(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) {
+    sockaddr_storage ss;
+    if (!inetAddressToSockaddr(env, javaAddress, port, &ss)) {
+        return;
+    }
+    int fd;
+    const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss);
+    NET_FAILURE_RETRY("connect", connect(fd, sa, sizeof(sockaddr_storage)));
+}
+
+static jobject Posix_dup(JNIEnv* env, jobject, jobject javaOldFd) {
+    int oldFd = jniGetFDFromFileDescriptor(env, javaOldFd);
+    int newFd = throwIfMinusOne(env, "dup", TEMP_FAILURE_RETRY(dup(oldFd)));
+    return (newFd != -1) ? jniCreateFileDescriptor(env, newFd) : NULL;
+}
+
+static jobject Posix_dup2(JNIEnv* env, jobject, jobject javaOldFd, jint newFd) {
+    int oldFd = jniGetFDFromFileDescriptor(env, javaOldFd);
+    int fd = throwIfMinusOne(env, "dup2", TEMP_FAILURE_RETRY(dup2(oldFd, newFd)));
+    return (fd != -1) ? jniCreateFileDescriptor(env, fd) : NULL;
+}
+
 static jobjectArray Posix_environ(JNIEnv* env, jobject) {
     extern char** environ; // Standard, but not in any header file.
     return toStringArray(env, environ);
@@ -444,7 +590,7 @@
 
         // 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));
+        ScopedLocalRef<jobject> inetAddress(env, sockaddrToInetAddress(env, address, NULL));
         if (inetAddress.get() == NULL) {
             return NULL;
         }
@@ -454,6 +600,18 @@
     return result;
 }
 
+static jint Posix_getegid(JNIEnv*, jobject) {
+    return getegid();
+}
+
+static jint Posix_geteuid(JNIEnv*, jobject) {
+    return geteuid();
+}
+
+static jint Posix_getgid(JNIEnv*, jobject) {
+    return getgid();
+}
+
 static jstring Posix_getenv(JNIEnv* env, jobject, jstring javaName) {
     ScopedUtfChars name(env, javaName);
     if (name.c_str() == NULL) {
@@ -464,7 +622,7 @@
 
 static jstring Posix_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint flags) {
     sockaddr_storage ss;
-    if (!inetAddressToSocketAddress(env, javaAddress, 0, &ss)) {
+    if (!inetAddressToSockaddr_getnameinfo(env, javaAddress, 0, &ss)) {
         return NULL;
     }
     // TODO: bionic's getnameinfo(3) seems to want its length parameter to be exactly
@@ -481,6 +639,26 @@
     return env->NewStringUTF(buf);
 }
 
+static jint Posix_getpid(JNIEnv*, jobject) {
+    return getpid();
+}
+
+static jint Posix_getppid(JNIEnv*, jobject) {
+    return getppid();
+}
+
+static jobject Posix_getpwnam(JNIEnv* env, jobject, jstring javaName) {
+    ScopedUtfChars name(env, javaName);
+    if (name.c_str() == NULL) {
+        return NULL;
+    }
+    return Passwd(env).getpwnam(name.c_str());
+}
+
+static jobject Posix_getpwuid(JNIEnv* env, jobject, jint uid) {
+    return Passwd(env).getpwuid(uid);
+}
+
 static jobject Posix_getsockname(JNIEnv* env, jobject, jobject javaFd) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     sockaddr_storage ss;
@@ -515,7 +693,7 @@
         throwErrnoException(env, "getsockopt");
         return NULL;
     }
-    return socketAddressToInetAddress(env, &ss);
+    return sockaddrToInetAddress(env, &ss, NULL);
 }
 
 static jint Posix_getsockoptInt(JNIEnv* env, jobject, jobject javaFd, jint level, jint option) {
@@ -552,6 +730,10 @@
     return makeStructTimeval(env, tv);
 }
 
+static jint Posix_getuid(JNIEnv*, jobject) {
+    return getuid();
+}
+
 static jstring Posix_if_indextoname(JNIEnv* env, jobject, jint index) {
     char buf[IF_NAMESIZE];
     char* name = if_indextoname(index, buf);
@@ -572,7 +754,7 @@
         return NULL;
     }
     sin->sin_family = AF_INET; // inet_aton only supports IPv4.
-    return socketAddressToInetAddress(env, &ss);
+    return sockaddrToInetAddress(env, &ss, NULL);
 }
 
 static jobject Posix_ioctlInetAddress(JNIEnv* env, jobject, jobject javaFd, jint cmd, jstring javaInterfaceName) {
@@ -585,7 +767,7 @@
     if (rc == -1) {
         return NULL;
     }
-    return socketAddressToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr));
+    return sockaddrToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr), NULL);
 }
 
 static jint Posix_ioctlInt(JNIEnv* env, jobject, jobject javaFd, jint cmd, jobject javaArg) {
@@ -701,6 +883,47 @@
     return result;
 }
 
+static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint timeoutMs) {
+    static jfieldID fdFid = env->GetFieldID(JniConstants::structPollfdClass, "fd", "Ljava/io/FileDescriptor;");
+    static jfieldID eventsFid = env->GetFieldID(JniConstants::structPollfdClass, "events", "S");
+    static jfieldID reventsFid = env->GetFieldID(JniConstants::structPollfdClass, "revents", "S");
+
+    // Turn the Java libcore.io.StructPollfd[] into a C++ struct pollfd[].
+    size_t arrayLength = env->GetArrayLength(javaStructs);
+    UniquePtr<struct pollfd[]> fds(new struct pollfd[arrayLength]);
+    memset(fds.get(), 0, sizeof(struct pollfd) * arrayLength);
+    size_t count = 0; // Some trailing array elements may be irrelevant. (See below.)
+    for (size_t i = 0; i < arrayLength; ++i) {
+        ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i));
+        if (javaStruct.get() == NULL) {
+            break; // We allow trailing nulls in the array for caller convenience.
+        }
+        ScopedLocalRef<jobject> javaFd(env, env->GetObjectField(javaStruct.get(), fdFid));
+        if (javaFd.get() == NULL) {
+            break; // We also allow callers to just clear the fd field (this is what Selector does).
+        }
+        fds[count].fd = jniGetFDFromFileDescriptor(env, javaFd.get());
+        fds[count].events = env->GetShortField(javaStruct.get(), eventsFid);
+        ++count;
+    }
+
+    int rc = TEMP_FAILURE_RETRY(poll(fds.get(), count, timeoutMs));
+    if (rc == -1) {
+        throwErrnoException(env, "poll");
+        return -1;
+    }
+
+    // Update the revents fields in the Java libcore.io.StructPollfd[].
+    for (size_t i = 0; i < count; ++i) {
+        ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i));
+        if (javaStruct.get() == NULL) {
+            return -1;
+        }
+        env->SetShortField(javaStruct.get(), reventsFid, fds[i].revents);
+    }
+    return rc;
+}
+
 static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount) {
     ScopedBytesRW bytes(env, javaBytes);
     if (bytes.get() == NULL) {
@@ -719,6 +942,22 @@
     return throwIfMinusOne(env, "readv", TEMP_FAILURE_RETRY(readv(fd, ioVec.get(), ioVec.size())));
 }
 
+static jint Posix_recvfromBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetSocketAddress) {
+    ScopedBytesRW bytes(env, javaBytes);
+    if (bytes.get() == NULL) {
+        return -1;
+    }
+    sockaddr_storage ss;
+    socklen_t sl = sizeof(ss);
+    memset(&ss, 0, sizeof(ss));
+    int fd;
+    sockaddr* from = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
+    socklen_t* fromLength = (javaInetSocketAddress != NULL) ? &sl : 0;
+    jint recvCount = NET_FAILURE_RETRY("recvfrom", recvfrom(fd, bytes.get() + byteOffset, byteCount, flags, from, fromLength));
+    fillInetSocketAddress(env, recvCount, javaInetSocketAddress, &ss);
+    return recvCount;
+}
+
 static void Posix_remove(JNIEnv* env, jobject, jstring javaPath) {
     ScopedUtfChars path(env, javaPath);
     if (path.c_str() == NULL) {
@@ -757,6 +996,33 @@
     return result;
 }
 
+static jint Posix_sendtoBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetAddress, jint port) {
+    ScopedBytesRO bytes(env, javaBytes);
+    if (bytes.get() == NULL) {
+        return -1;
+    }
+    sockaddr_storage ss;
+    if (javaInetAddress != NULL && !inetAddressToSockaddr(env, javaInetAddress, port, &ss)) {
+        return -1;
+    }
+    int fd;
+    const sockaddr* to = (javaInetAddress != NULL) ? reinterpret_cast<const sockaddr*>(&ss) : NULL;
+    socklen_t toLength = (javaInetAddress != NULL) ? sizeof(ss) : 0;
+    return NET_FAILURE_RETRY("sendto", sendto(fd, bytes.get() + byteOffset, byteCount, flags, to, toLength));
+}
+
+static void Posix_setegid(JNIEnv* env, jobject, jint egid) {
+    throwIfMinusOne(env, "setegid", TEMP_FAILURE_RETRY(setegid(egid)));
+}
+
+static void Posix_seteuid(JNIEnv* env, jobject, jint euid) {
+    throwIfMinusOne(env, "seteuid", TEMP_FAILURE_RETRY(seteuid(euid)));
+}
+
+static void Posix_setgid(JNIEnv* env, jobject, jint gid) {
+    throwIfMinusOne(env, "setgid", TEMP_FAILURE_RETRY(setgid(gid)));
+}
+
 static void Posix_setsockoptByte(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     u_char byte = value;
@@ -792,8 +1058,8 @@
     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)) {
+    ScopedLocalRef<jobject> javaGroup(env, env->GetObjectField(javaGroupReq, grGroupFid));
+    if (!inetAddressToSockaddr(env, javaGroup.get(), 0, &value.gr_group)) {
         return;
     }
 
@@ -835,6 +1101,10 @@
     throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
 }
 
+static void Posix_setuid(JNIEnv* env, jobject, jint uid) {
+    throwIfMinusOne(env, "setuid", TEMP_FAILURE_RETRY(setuid(uid)));
+}
+
 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)));
@@ -899,6 +1169,16 @@
     return makeStructUtsname(env, buf);
 }
 
+static jint Posix_waitpid(JNIEnv* env, jobject, jint pid, jobject javaStatus, jint options) {
+    int status;
+    int rc = throwIfMinusOne(env, "waitpid", TEMP_FAILURE_RETRY(waitpid(pid, &status, options)));
+    if (rc != -1) {
+        static jfieldID valueFid = env->GetFieldID(JniConstants::mutableIntClass, "value", "I");
+        env->SetIntField(javaStatus, valueFid, status);
+    }
+    return rc;
+}
+
 static jint Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
     ScopedBytesRO bytes(env, javaBytes);
     if (bytes.get() == NULL) {
@@ -918,9 +1198,14 @@
 }
 
 static JNINativeMethod gMethods[] = {
+    NATIVE_METHOD(Posix, accept, "(Ljava/io/FileDescriptor;Ljava/net/InetSocketAddress;)Ljava/io/FileDescriptor;"),
     NATIVE_METHOD(Posix, access, "(Ljava/lang/String;I)Z"),
+    NATIVE_METHOD(Posix, bind, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"),
     NATIVE_METHOD(Posix, chmod, "(Ljava/lang/String;I)V"),
     NATIVE_METHOD(Posix, close, "(Ljava/io/FileDescriptor;)V"),
+    NATIVE_METHOD(Posix, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"),
+    NATIVE_METHOD(Posix, dup, "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;"),
+    NATIVE_METHOD(Posix, dup2, "(Ljava/io/FileDescriptor;I)Ljava/io/FileDescriptor;"),
     NATIVE_METHOD(Posix, environ, "()[Ljava/lang/String;"),
     NATIVE_METHOD(Posix, fcntlVoid, "(Ljava/io/FileDescriptor;I)I"),
     NATIVE_METHOD(Posix, fcntlLong, "(Ljava/io/FileDescriptor;IJ)I"),
@@ -932,14 +1217,22 @@
     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, getegid, "()I"),
+    NATIVE_METHOD(Posix, geteuid, "()I"),
+    NATIVE_METHOD(Posix, getgid, "()I"),
     NATIVE_METHOD(Posix, getenv, "(Ljava/lang/String;)Ljava/lang/String;"),
     NATIVE_METHOD(Posix, getnameinfo, "(Ljava/net/InetAddress;I)Ljava/lang/String;"),
+    NATIVE_METHOD(Posix, getpid, "()I"),
+    NATIVE_METHOD(Posix, getppid, "()I"),
+    NATIVE_METHOD(Posix, getpwnam, "(Ljava/lang/String;)Llibcore/io/StructPasswd;"),
+    NATIVE_METHOD(Posix, getpwuid, "(I)Llibcore/io/StructPasswd;"),
     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, getuid, "()I"),
     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;"),
@@ -958,11 +1251,17 @@
     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, poll, "([Llibcore/io/StructPollfd;I)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, recvfromBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/InetSocketAddress;)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, sendtoBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/InetAddress;I)I"),
+    NATIVE_METHOD(Posix, setegid, "(I)V"),
+    NATIVE_METHOD(Posix, seteuid, "(I)V"),
+    NATIVE_METHOD(Posix, setgid, "(I)V"),
     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"),
@@ -970,6 +1269,7 @@
     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, setuid, "(I)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;"),
@@ -978,6 +1278,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, waitpid, "(ILlibcore/util/MutableInt;I)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"),
 };
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
deleted file mode 100644
index d7208b1..0000000
--- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * Copyright (C) 2007 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 "OSNetworkSystem"
-
-#include "AsynchronousSocketCloseMonitor.h"
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "JniException.h"
-#include "NetFd.h"
-#include "NetworkUtilities.h"
-#include "ScopedPrimitiveArray.h"
-#include "jni.h"
-#include "valueOf.h"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <net/if.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-/* 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.
- *
- * @param address the sockaddr_storage structure to get the port from
- *
- * @return the port number, or -1 if the address family is unknown.
- */
-static int getSocketAddressPort(sockaddr_storage* ss) {
-    switch (ss->ss_family) {
-    case AF_INET:
-        return ntohs(reinterpret_cast<sockaddr_in*>(ss)->sin_port);
-    case AF_INET6:
-        return ntohs(reinterpret_cast<sockaddr_in6*>(ss)->sin6_port);
-    default:
-        return -1;
-    }
-}
-
-// Creates mapped addresses.
-// TODO: can this move to inetAddressToSocketAddress?
-class CompatibleSocketAddress {
-public:
-    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);
-            }
-            memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
-            mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
-        }
-    }
-    // Returns a pointer to an IPv6 address.
-    const sockaddr* get() const {
-        return mCompatibleAddress;
-    }
-private:
-    const sockaddr* mCompatibleAddress;
-    sockaddr_storage mTmp;
-};
-
-// Converts a number of milliseconds to a timeval.
-static timeval toTimeval(long ms) {
-    timeval tv;
-    tv.tv_sec = ms / 1000;
-    tv.tv_usec = (ms - tv.tv_sec*1000) * 1000;
-    return tv;
-}
-
-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);
-    }
-}
-
-static jint OSNetworkSystem_writeDirect(JNIEnv* env, jobject,
-        jobject fileDescriptor, jint address, jint offset, jint count) {
-    if (count <= 0) {
-        return 0;
-    }
-
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return 0;
-    }
-
-    jbyte* src = reinterpret_cast<jbyte*>(static_cast<uintptr_t>(address + offset));
-
-    ssize_t bytesSent;
-    {
-        int intFd = fd.get();
-        AsynchronousSocketCloseMonitor monitor(intFd);
-        bytesSent = NET_FAILURE_RETRY(fd, write(intFd, src, count));
-    }
-    if (env->ExceptionOccurred()) {
-        return -1;
-    }
-
-    if (bytesSent == -1) {
-        if (errno == EAGAIN || errno == EWOULDBLOCK) {
-            // We were asked to write to a non-blocking socket, but were told
-            // it would block, so report "no bytes written".
-            return 0;
-        } else {
-            jniThrowSocketException(env, errno);
-            return 0;
-        }
-    }
-    return bytesSent;
-}
-
-static jint OSNetworkSystem_write(JNIEnv* env, jobject,
-        jobject fileDescriptor, jbyteArray byteArray, jint offset, jint count) {
-    ScopedByteArrayRW bytes(env, byteArray);
-    if (bytes.get() == NULL) {
-        return -1;
-    }
-    jint address = static_cast<jint>(reinterpret_cast<uintptr_t>(bytes.get()));
-    int result = OSNetworkSystem_writeDirect(env, NULL, fileDescriptor, address, offset, count);
-    return result;
-}
-
-static jboolean OSNetworkSystem_connect(JNIEnv* env, jobject, jobject fileDescriptor, jobject inetAddr, jint port) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return JNI_FALSE;
-    }
-
-    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 netFd(env, fileDescriptor);
-    if (netFd.isClosed()) {
-        return JNI_FALSE;
-    }
-
-    // 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 {
-            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 ss;
-    if (!inetAddressToSocketAddress(env, inetAddress, port, &ss)) {
-        return;
-    }
-
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return;
-    }
-
-    const CompatibleSocketAddress compatibleAddress(ss, false);
-    int rc = TEMP_FAILURE_RETRY(bind(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage)));
-    if (rc == -1) {
-        jniThrowExceptionWithErrno(env, "java/net/BindException", errno);
-    }
-}
-
-static void OSNetworkSystem_accept(JNIEnv* env, jobject, jobject serverFileDescriptor,
-        jobject newSocket, jobject clientFileDescriptor) {
-
-    if (newSocket == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-
-    NetFd serverFd(env, serverFileDescriptor);
-    if (serverFd.isClosed()) {
-        return;
-    }
-
-    sockaddr_storage ss;
-    socklen_t addrLen = sizeof(ss);
-    sockaddr* sa = reinterpret_cast<sockaddr*>(&ss);
-
-    int clientFd;
-    {
-        int intFd = serverFd.get();
-        AsynchronousSocketCloseMonitor monitor(intFd);
-        clientFd = NET_FAILURE_RETRY(serverFd, accept(intFd, sa, &addrLen));
-    }
-    if (env->ExceptionOccurred()) {
-        return;
-    }
-    if (clientFd == -1) {
-        if (errno == EAGAIN || errno == EWOULDBLOCK) {
-            jniThrowSocketTimeoutException(env, errno);
-        } else {
-            jniThrowSocketException(env, errno);
-        }
-        return;
-    }
-
-    // Reset the inherited read timeout to the Java-specified default of 0.
-    timeval timeout(toTimeval(0));
-    int rc = setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
-    if (rc == -1) {
-        LOGE("couldn't reset SO_RCVTIMEO on accepted socket fd %i: %s", clientFd, strerror(errno));
-        jniThrowSocketException(env, errno);
-    }
-
-    /*
-     * For network sockets, put the peer address and port in instance variables.
-     * We don't bother to do this for UNIX domain sockets, since most peers are
-     * anonymous anyway.
-     */
-    if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
-        // Remote address and port.
-        jobject remoteAddress = socketAddressToInetAddress(env, &ss);
-        if (remoteAddress == NULL) {
-            close(clientFd);
-            return;
-        }
-        int remotePort = getSocketAddressPort(&ss);
-
-        // Local port.
-        memset(&ss, 0, addrLen);
-        int rc = getsockname(clientFd, sa, &addrLen);
-        if (rc == -1) {
-            close(clientFd);
-            jniThrowSocketException(env, errno);
-            return;
-        }
-        int localPort = getSocketAddressPort(&ss);
-
-        static jfieldID addressFid = env->GetFieldID(JniConstants::socketImplClass, "address", "Ljava/net/InetAddress;");
-        static jfieldID localPortFid = env->GetFieldID(JniConstants::socketImplClass, "localport", "I");
-        static jfieldID portFid = env->GetFieldID(JniConstants::socketImplClass, "port", "I");
-        env->SetObjectField(newSocket, addressFid, remoteAddress);
-        env->SetIntField(newSocket, portFid, remotePort);
-        env->SetIntField(newSocket, localPortFid, localPort);
-    }
-
-    jniSetFileDescriptorOfFD(env, clientFileDescriptor, clientFd);
-}
-
-static void OSNetworkSystem_sendUrgentData(JNIEnv* env, jobject,
-        jobject fileDescriptor, jbyte value) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return;
-    }
-
-    int rc = send(fd.get(), &value, 1, MSG_OOB);
-    if (rc == -1) {
-        jniThrowSocketException(env, errno);
-    }
-}
-
-static void OSNetworkSystem_disconnectDatagram(JNIEnv* env, jobject, jobject fileDescriptor) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return;
-    }
-
-    // To disconnect a datagram socket, we connect to a bogus address with
-    // the family AF_UNSPEC.
-    sockaddr_storage ss;
-    memset(&ss, 0, sizeof(ss));
-    ss.ss_family = AF_UNSPEC;
-    const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss);
-    int rc = TEMP_FAILURE_RETRY(connect(fd.get(), sa, sizeof(ss)));
-    if (rc == -1) {
-        jniThrowSocketException(env, errno);
-    }
-}
-
-// TODO: can we merge this with recvDirect?
-static jint OSNetworkSystem_readDirect(JNIEnv* env, jobject, jobject fileDescriptor,
-        jint address, jint count) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return 0;
-    }
-
-    jbyte* dst = reinterpret_cast<jbyte*>(static_cast<uintptr_t>(address));
-    ssize_t bytesReceived;
-    {
-        int intFd = fd.get();
-        AsynchronousSocketCloseMonitor monitor(intFd);
-        bytesReceived = NET_FAILURE_RETRY(fd, read(intFd, dst, count));
-    }
-    if (env->ExceptionOccurred()) {
-        return -1;
-    }
-    if (bytesReceived == 0) {
-        return -1;
-    } else if (bytesReceived == -1) {
-        if (errno == EAGAIN || errno == EWOULDBLOCK) {
-            // We were asked to read a non-blocking socket with no data
-            // available, so report "no bytes read".
-            return 0;
-        } else {
-            jniThrowSocketException(env, errno);
-            return 0;
-        }
-    } else {
-        return bytesReceived;
-    }
-}
-
-static jint OSNetworkSystem_read(JNIEnv* env, jclass, jobject fileDescriptor,
-        jbyteArray byteArray, jint offset, jint count) {
-    ScopedByteArrayRW bytes(env, byteArray);
-    if (bytes.get() == NULL) {
-        return -1;
-    }
-    jint address = static_cast<jint>(reinterpret_cast<uintptr_t>(bytes.get() + offset));
-    return OSNetworkSystem_readDirect(env, NULL, fileDescriptor, address, count);
-}
-
-// TODO: can we merge this with readDirect?
-static jint OSNetworkSystem_recvDirect(JNIEnv* env, jobject, jobject fileDescriptor, jobject packet,
-        jint address, jint offset, jint length, jboolean peek, jboolean connected) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return 0;
-    }
-
-    char* buf = reinterpret_cast<char*>(static_cast<uintptr_t>(address + offset));
-    const int flags = peek ? MSG_PEEK : 0;
-    sockaddr_storage ss;
-    memset(&ss, 0, sizeof(ss));
-    socklen_t sockAddrLen = sizeof(ss);
-    sockaddr* from = connected ? NULL : reinterpret_cast<sockaddr*>(&ss);
-    socklen_t* fromLength = connected ? NULL : &sockAddrLen;
-
-    ssize_t bytesReceived;
-    {
-        int intFd = fd.get();
-        AsynchronousSocketCloseMonitor monitor(intFd);
-        bytesReceived = NET_FAILURE_RETRY(fd, recvfrom(intFd, buf, length, flags, from, fromLength));
-    }
-    if (env->ExceptionOccurred()) {
-        return -1;
-    }
-    if (bytesReceived == -1) {
-        if (connected && errno == ECONNREFUSED) {
-            jniThrowException(env, "java/net/PortUnreachableException", "");
-        } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
-            jniThrowSocketTimeoutException(env, errno);
-        } else {
-            jniThrowSocketException(env, errno);
-        }
-        return 0;
-    }
-
-    static jfieldID addressFid = env->GetFieldID(JniConstants::datagramPacketClass, "address", "Ljava/net/InetAddress;");
-    static jfieldID lengthFid = env->GetFieldID(JniConstants::datagramPacketClass, "length", "I");
-    static jfieldID portFid = env->GetFieldID(JniConstants::datagramPacketClass, "port", "I");
-    if (packet != NULL) {
-        env->SetIntField(packet, lengthFid, bytesReceived);
-        if (!connected) {
-            jobject sender = socketAddressToInetAddress(env, &ss);
-            if (sender == NULL) {
-                return 0;
-            }
-            int port = getSocketAddressPort(&ss);
-            env->SetObjectField(packet, addressFid, sender);
-            env->SetIntField(packet, portFid, port);
-        }
-    }
-    return bytesReceived;
-}
-
-static jint OSNetworkSystem_recv(JNIEnv* env, jobject, jobject fd, jobject packet,
-        jbyteArray javaBytes, jint offset, jint length, jboolean peek, jboolean connected) {
-    ScopedByteArrayRW bytes(env, javaBytes);
-    if (bytes.get() == NULL) {
-        return -1;
-    }
-    uintptr_t address = reinterpret_cast<uintptr_t>(bytes.get());
-    return OSNetworkSystem_recvDirect(env, NULL, fd, packet, address, offset, length, peek,
-            connected);
-}
-
-
-
-
-
-
-
-
-static jint OSNetworkSystem_sendDirect(JNIEnv* env, jobject, jobject fileDescriptor, jint address, jint offset, jint length, jint port, jobject inetAddress) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        return -1;
-    }
-
-    sockaddr_storage receiver;
-    if (inetAddress != NULL && !inetAddressToSocketAddress(env, inetAddress, port, &receiver)) {
-        return -1;
-    }
-
-    int flags = 0;
-    char* buf = reinterpret_cast<char*>(static_cast<uintptr_t>(address + offset));
-    sockaddr* to = inetAddress ? reinterpret_cast<sockaddr*>(&receiver) : NULL;
-    socklen_t toLength = inetAddress ? sizeof(receiver) : 0;
-
-    ssize_t bytesSent;
-    {
-        int intFd = fd.get();
-        AsynchronousSocketCloseMonitor monitor(intFd);
-        bytesSent = NET_FAILURE_RETRY(fd, sendto(intFd, buf, length, flags, to, toLength));
-    }
-    if (env->ExceptionOccurred()) {
-        return -1;
-    }
-    if (bytesSent == -1) {
-        if (errno == ECONNRESET || errno == ECONNREFUSED) {
-            return 0;
-        } else {
-            jniThrowSocketException(env, errno);
-        }
-    }
-    return bytesSent;
-}
-
-static jint OSNetworkSystem_send(JNIEnv* env, jobject, jobject fd,
-        jbyteArray data, jint offset, jint length,
-        jint port, jobject inetAddress) {
-    ScopedByteArrayRO bytes(env, data);
-    if (bytes.get() == NULL) {
-        return -1;
-    }
-    return OSNetworkSystem_sendDirect(env, NULL, fd,
-            reinterpret_cast<uintptr_t>(bytes.get()), offset, length, port, inetAddress);
-}
-
-
-
-
-
-
-
-
-static bool isValidFd(int fd) {
-    return fd >= 0 && fd < FD_SETSIZE;
-}
-
-static bool initFdSet(JNIEnv* env, jobjectArray fdArray, jint count, fd_set* fdSet, int* maxFd) {
-    for (int i = 0; i < count; ++i) {
-        jobject fileDescriptor = env->GetObjectArrayElement(fdArray, i);
-        if (fileDescriptor == NULL) {
-            return false;
-        }
-
-        const int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-        if (!isValidFd(fd)) {
-            LOGE("selectImpl: ignoring invalid fd %i", fd);
-            continue;
-        }
-
-        FD_SET(fd, fdSet);
-
-        if (fd > *maxFd) {
-            *maxFd = fd;
-        }
-    }
-    return true;
-}
-
-/*
- * Note: fdSet has to be non-const because although on Linux FD_ISSET() is sane
- * and takes a const fd_set*, it takes fd_set* on Mac OS. POSIX is not on our
- * side here:
- *   http://www.opengroup.org/onlinepubs/000095399/functions/select.html
- */
-static bool translateFdSet(JNIEnv* env, jobjectArray fdArray, jint count, fd_set& fdSet, jint* flagArray, size_t offset, jint op) {
-    for (int i = 0; i < count; ++i) {
-        jobject fileDescriptor = env->GetObjectArrayElement(fdArray, i);
-        if (fileDescriptor == NULL) {
-            return false;
-        }
-
-        const int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-        if (isValidFd(fd) && FD_ISSET(fd, &fdSet)) {
-            flagArray[i + offset] = op;
-        } else {
-            flagArray[i + offset] = SOCKET_OP_NONE;
-        }
-    }
-    return true;
-}
-
-static jboolean OSNetworkSystem_selectImpl(JNIEnv* env, jclass,
-        jobjectArray readFDArray, jobjectArray writeFDArray, jint countReadC,
-        jint countWriteC, jintArray outFlags, jlong timeoutMs) {
-
-    // Initialize the fd_sets.
-    int maxFd = -1;
-    fd_set readFds;
-    fd_set writeFds;
-    FD_ZERO(&readFds);
-    FD_ZERO(&writeFds);
-    bool initialized = initFdSet(env, readFDArray, countReadC, &readFds, &maxFd) &&
-                       initFdSet(env, writeFDArray, countWriteC, &writeFds, &maxFd);
-    if (!initialized) {
-        return -1;
-    }
-
-    // Initialize the timeout, if any.
-    timeval tv;
-    timeval* tvp = NULL;
-    if (timeoutMs >= 0) {
-        tv = toTimeval(timeoutMs);
-        tvp = &tv;
-    }
-
-    // Perform the select.
-    int result = select(maxFd + 1, &readFds, &writeFds, NULL, tvp);
-    if (result == 0) {
-        // Timeout.
-        return JNI_FALSE;
-    } else if (result == -1) {
-        // Error.
-        if (errno == EINTR) {
-            return JNI_FALSE;
-        } else {
-            jniThrowSocketException(env, errno);
-            return JNI_FALSE;
-        }
-    }
-
-    // Translate the result into the int[] we're supposed to fill in.
-    ScopedIntArrayRW flagArray(env, outFlags);
-    if (flagArray.get() == NULL) {
-        return JNI_FALSE;
-    }
-    return translateFdSet(env, readFDArray, countReadC, readFds, flagArray.get(), 0, SOCKET_OP_READ) &&
-            translateFdSet(env, writeFDArray, countWriteC, writeFds, flagArray.get(), countReadC, SOCKET_OP_WRITE);
-}
-
-static void OSNetworkSystem_close(JNIEnv* env, jobject, jobject fileDescriptor) {
-    NetFd fd(env, fileDescriptor);
-    if (fd.isClosed()) {
-        // Socket.close doesn't throw if you try to close an already-closed socket.
-        env->ExceptionClear();
-        return;
-    }
-
-    int oldFd = fd.get();
-    jniSetFileDescriptorOfFD(env, fileDescriptor, -1);
-    AsynchronousSocketCloseMonitor::signalBlockedThreads(oldFd);
-    close(oldFd);
-}
-
-static JNINativeMethod gMethods[] = {
-    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, 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"),
-    NATIVE_METHOD(OSNetworkSystem, readDirect, "(Ljava/io/FileDescriptor;II)I"),
-    NATIVE_METHOD(OSNetworkSystem, recv, "(Ljava/io/FileDescriptor;Ljava/net/DatagramPacket;[BIIZZ)I"),
-    NATIVE_METHOD(OSNetworkSystem, recvDirect, "(Ljava/io/FileDescriptor;Ljava/net/DatagramPacket;IIIZZ)I"),
-    NATIVE_METHOD(OSNetworkSystem, selectImpl, "([Ljava/io/FileDescriptor;[Ljava/io/FileDescriptor;II[IJ)Z"),
-    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, write, "(Ljava/io/FileDescriptor;[BII)I"),
-    NATIVE_METHOD(OSNetworkSystem, writeDirect, "(Ljava/io/FileDescriptor;III)I"),
-};
-
-int register_org_apache_harmony_luni_platform_OSNetworkSystem(JNIEnv* env) {
-    AsynchronousSocketCloseMonitor::init();
-    return jniRegisterNativeMethods(env, "org/apache/harmony/luni/platform/OSNetworkSystem", gMethods, NELEM(gMethods));
-}
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index c7620b3..9535ce6 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -18,6 +18,7 @@
 	java_lang_ProcessManager.cpp \
 	java_lang_RealToString.cpp \
 	java_lang_StrictMath.cpp \
+	java_lang_StringToReal.cpp \
 	java_lang_System.cpp \
 	java_math_NativeBN.cpp \
 	java_nio_ByteOrder.cpp \
@@ -38,12 +39,11 @@
 	libcore_icu_NativeNormalizer.cpp \
 	libcore_icu_NativePluralRules.cpp \
 	libcore_icu_TimeZones.cpp \
+	libcore_io_AsynchronousCloseMonitor.cpp \
 	libcore_io_Memory.cpp \
 	libcore_io_OsConstants.cpp \
 	libcore_io_Posix.cpp \
 	libcore_net_RawSocket.cpp \
-	org_apache_harmony_luni_platform_OSNetworkSystem.cpp \
-	org_apache_harmony_luni_util_FloatingPointParser.cpp \
 	org_apache_harmony_xml_ExpatParser.cpp \
 	org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp \
 	readlink.cpp \
diff --git a/luni/src/test/java/libcore/io/Base64Test.java b/luni/src/test/java/libcore/io/Base64Test.java
new file mode 100644
index 0000000..edf24d4
--- /dev/null
+++ b/luni/src/test/java/libcore/io/Base64Test.java
@@ -0,0 +1,55 @@
+/*
+ *  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.io;
+
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+public final class Base64Test extends TestCase {
+
+    public void testDecodeEmpty() throws Exception {
+        assertEquals("[]", Arrays.toString(Base64.decode(new byte[0])));
+    }
+
+    public void testEncode() throws Exception {
+        assertEncoded("");
+        assertEncoded("Eg==", 0x12);
+        assertEncoded("EjQ=", 0x12, 0x34 );
+        assertEncoded("EjRW", 0x12, 0x34, 0x56);
+        assertEncoded("EjRWeA==", 0x12, 0x34, 0x56, 0x78);
+        assertEncoded("EjRWeJo=", 0x12, 0x34, 0x56, 0x78, 0x9A);
+        assertEncoded("EjRWeJq8", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc);
+    }
+
+    public void testEncodeDoesNotWrap() {
+        int[] data = new int[61];
+        Arrays.fill(data, 0xff);
+        String expected = "///////////////////////////////////////////////////////////////////////"
+                + "//////////w=="; // 84 chars
+        assertEncoded(expected, data);
+    }
+
+    public void assertEncoded(String expected , int... data) {
+        byte[] dataBytes = new byte[data.length];
+        for (int i = 0; i < data.length; i++) {
+            dataBytes[i] = (byte) data[i];
+        }
+        assertEquals(expected, Base64.encode(dataBytes));
+    }
+}
+
diff --git a/luni/src/test/java/libcore/io/DiskLruCacheTest.java b/luni/src/test/java/libcore/io/DiskLruCacheTest.java
new file mode 100644
index 0000000..808918d
--- /dev/null
+++ b/luni/src/test/java/libcore/io/DiskLruCacheTest.java
@@ -0,0 +1,755 @@
+/*
+ * 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.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+import static libcore.io.DiskLruCache.JOURNAL_FILE;
+import static libcore.io.DiskLruCache.MAGIC;
+import static libcore.io.DiskLruCache.VERSION_1;
+import tests.io.MockOs;
+
+public final class DiskLruCacheTest extends TestCase {
+    private final int appVersion = 100;
+    private String javaTmpDir;
+    private File cacheDir;
+    private File journalFile;
+    private DiskLruCache cache;
+    private final MockOs mockOs = new MockOs();
+
+    @Override public void setUp() throws Exception {
+        super.setUp();
+        javaTmpDir = System.getProperty("java.io.tmpdir");
+        cacheDir = new File(javaTmpDir, "DiskLruCacheTest");
+        cacheDir.mkdir();
+        journalFile = new File(cacheDir, JOURNAL_FILE);
+        for (File file : cacheDir.listFiles()) {
+            file.delete();
+        }
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        mockOs.install();
+    }
+
+    @Override protected void tearDown() throws Exception {
+        mockOs.uninstall();
+        cache.close();
+        super.tearDown();
+    }
+
+    public void testEmptyCache() throws Exception {
+        cache.close();
+        assertJournalEquals();
+    }
+
+    public void testWriteAndReadEntry() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        creator.set(0, "ABC");
+        creator.set(1, "DE");
+        assertNull(creator.getString(0));
+        assertNull(creator.newInputStream(0));
+        assertNull(creator.getString(1));
+        assertNull(creator.newInputStream(1));
+        creator.commit();
+
+        DiskLruCache.Snapshot snapshot = cache.get("k1");
+        assertEquals("ABC", snapshot.getString(0));
+        assertEquals("DE", snapshot.getString(1));
+    }
+
+    public void testReadAndWriteEntryAcrossCacheOpenAndClose() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        creator.set(0, "A");
+        creator.set(1, "B");
+        creator.commit();
+        cache.close();
+
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        DiskLruCache.Snapshot snapshot = cache.get("k1");
+        assertEquals("A", snapshot.getString(0));
+        assertEquals("B", snapshot.getString(1));
+        snapshot.close();
+    }
+
+    public void testJournalWithEditAndPublish() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed
+        creator.set(0, "AB");
+        creator.set(1, "C");
+        creator.commit();
+        cache.close();
+        assertJournalEquals("DIRTY k1", "CLEAN k1 2 1");
+    }
+
+    public void testRevertedNewFileIsRemoveInJournal() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed
+        creator.set(0, "AB");
+        creator.set(1, "C");
+        creator.abort();
+        cache.close();
+        assertJournalEquals("DIRTY k1", "REMOVE k1");
+    }
+
+    public void testUnterminatedEditIsRevertedOnClose() throws Exception {
+        cache.edit("k1");
+        cache.close();
+        assertJournalEquals("DIRTY k1", "REMOVE k1");
+    }
+
+    public void testJournalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        assertNull(cache.get("k1"));
+        creator.set(0, "A");
+        creator.set(1, "BC");
+        creator.commit();
+        cache.close();
+        assertJournalEquals("DIRTY k1", "CLEAN k1 1 2");
+    }
+
+    public void testJournalWithEditAndPublishAndRead() throws Exception {
+        DiskLruCache.Editor k1Creator = cache.edit("k1");
+        k1Creator.set(0, "AB");
+        k1Creator.set(1, "C");
+        k1Creator.commit();
+        DiskLruCache.Editor k2Creator = cache.edit("k2");
+        k2Creator.set(0, "DEF");
+        k2Creator.set(1, "G");
+        k2Creator.commit();
+        DiskLruCache.Snapshot k1Snapshot = cache.get("k1");
+        k1Snapshot.close();
+        cache.close();
+        assertJournalEquals("DIRTY k1", "CLEAN k1 2 1",
+                "DIRTY k2", "CLEAN k2 3 1",
+                "READ k1");
+    }
+
+    public void testCannotOperateOnEditAfterPublish() throws Exception {
+        DiskLruCache.Editor editor = cache.edit("k1");
+        editor.set(0, "A");
+        editor.set(1, "B");
+        editor.commit();
+        assertInoperable(editor);
+    }
+
+    public void testCannotOperateOnEditAfterRevert() throws Exception {
+        DiskLruCache.Editor editor = cache.edit("k1");
+        editor.set(0, "A");
+        editor.set(1, "B");
+        editor.abort();
+        assertInoperable(editor);
+    }
+
+    public void testExplicitRemoveAppliedToDiskImmediately() throws Exception {
+        DiskLruCache.Editor editor = cache.edit("k1");
+        editor.set(0, "ABC");
+        editor.set(1, "B");
+        editor.commit();
+        File k1 = getCleanFile("k1", 0);
+        assertEquals("ABC", readFile(k1));
+        cache.remove("k1");
+        assertFalse(k1.exists());
+    }
+
+    /**
+     * Each read sees a snapshot of the file at the time read was called.
+     * This means that two reads of the same key can see different data.
+     */
+    public void testReadAndWriteOverlapsMaintainConsistency() throws Exception {
+        DiskLruCache.Editor v1Creator = cache.edit("k1");
+        v1Creator.set(0, "AAaa");
+        v1Creator.set(1, "BBbb");
+        v1Creator.commit();
+
+        DiskLruCache.Snapshot snapshot1 = cache.get("k1");
+        InputStream inV1 = snapshot1.getInputStream(0);
+        assertEquals('A', inV1.read());
+        assertEquals('A', inV1.read());
+
+        DiskLruCache.Editor v1Updater = cache.edit("k1");
+        v1Updater.set(0, "CCcc");
+        v1Updater.set(1, "DDdd");
+        v1Updater.commit();
+
+        DiskLruCache.Snapshot snapshot2 = cache.get("k1");
+        assertEquals("CCcc", snapshot2.getString(0));
+        assertEquals("DDdd", snapshot2.getString(1));
+        snapshot2.close();
+
+        assertEquals('a', inV1.read());
+        assertEquals('a', inV1.read());
+        assertEquals("BBbb", snapshot1.getString(1));
+        snapshot1.close();
+    }
+
+    public void testOpenWithDirtyKeyDeletesAllFilesForThatKey() throws Exception {
+        cache.close();
+        File cleanFile0 = getCleanFile("k1", 0);
+        File cleanFile1 = getCleanFile("k1", 1);
+        File dirtyFile0 = getDirtyFile("k1", 0);
+        File dirtyFile1 = getDirtyFile("k1", 1);
+        writeFile(cleanFile0, "A");
+        writeFile(cleanFile1, "B");
+        writeFile(dirtyFile0, "C");
+        writeFile(dirtyFile1, "D");
+        createJournal("CLEAN k1 1 1", "DIRTY   k1");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertFalse(cleanFile0.exists());
+        assertFalse(cleanFile1.exists());
+        assertFalse(dirtyFile0.exists());
+        assertFalse(dirtyFile1.exists());
+        assertNull(cache.get("k1"));
+    }
+
+    public void testOpenWithInvalidVersionClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournalWithHeader(MAGIC, "0", "100", "2", "");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+    }
+
+    public void testOpenWithInvalidAppVersionClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournalWithHeader(MAGIC, "1", "101", "2", "");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+    }
+
+    public void testOpenWithInvalidValueCountClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournalWithHeader(MAGIC, "1", "100", "1", "");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+    }
+
+    public void testOpenWithInvalidBlankLineClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournalWithHeader(MAGIC, "1", "100", "2", "x");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+    }
+
+    public void testOpenWithInvalidJournalLineClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournal("CLEAN k1 1 1", "BOGUS");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+        assertNull(cache.get("k1"));
+    }
+
+    public void testOpenWithInvalidFileSizeClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournal("CLEAN k1 0000x001 1");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+        assertNull(cache.get("k1"));
+    }
+
+    public void testOpenWithTruncatedLineDiscardsThatLine() throws Exception {
+        cache.close();
+        writeFile(getCleanFile("k1", 0), "A");
+        writeFile(getCleanFile("k1", 1), "B");
+        Writer writer = new FileWriter(journalFile);
+        writer.write(MAGIC + "\n" + VERSION_1 + "\n100\n2\n\nCLEAN k1 1 1"); // no trailing newline
+        writer.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertNull(cache.get("k1"));
+    }
+
+    public void testOpenWithTooManyFileSizesClearsDirectory() throws Exception {
+        cache.close();
+        generateSomeGarbageFiles();
+        createJournal("CLEAN k1 1 1 1");
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+        assertGarbageFilesAllDeleted();
+        assertNull(cache.get("k1"));
+    }
+
+    public void testKeyWithSpaceNotPermitted() throws Exception {
+        try {
+            cache.edit("my key");
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testKeyWithNewlineNotPermitted() throws Exception {
+        try {
+            cache.edit("my\nkey");
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testKeyWithCarriageReturnNotPermitted() throws Exception {
+        try {
+            cache.edit("my\rkey");
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testNullKeyThrows() throws Exception {
+        try {
+            cache.edit(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testCreateNewEntryWithTooFewValuesFails() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        creator.set(1, "A");
+        try {
+            creator.commit();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        assertFalse(getCleanFile("k1", 0).exists());
+        assertFalse(getCleanFile("k1", 1).exists());
+        assertFalse(getDirtyFile("k1", 0).exists());
+        assertFalse(getDirtyFile("k1", 1).exists());
+        assertNull(cache.get("k1"));
+
+        DiskLruCache.Editor creator2 = cache.edit("k1");
+        creator2.set(0, "B");
+        creator2.set(1, "C");
+        creator2.commit();
+    }
+
+    public void testRevertWithTooFewValues() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        creator.set(1, "A");
+        creator.abort();
+        assertFalse(getCleanFile("k1", 0).exists());
+        assertFalse(getCleanFile("k1", 1).exists());
+        assertFalse(getDirtyFile("k1", 0).exists());
+        assertFalse(getDirtyFile("k1", 1).exists());
+        assertNull(cache.get("k1"));
+    }
+
+    public void testUpdateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception {
+        DiskLruCache.Editor creator = cache.edit("k1");
+        creator.set(0, "A");
+        creator.set(1, "B");
+        creator.commit();
+
+        DiskLruCache.Editor updater = cache.edit("k1");
+        updater.set(0, "C");
+        updater.commit();
+
+        DiskLruCache.Snapshot snapshot = cache.get("k1");
+        assertEquals("C", snapshot.getString(0));
+        assertEquals("B", snapshot.getString(1));
+        snapshot.close();
+    }
+
+    public void testEvictOnInsert() throws Exception {
+        cache.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
+
+        set("A", "a", "aaa"); // size 4
+        set("B", "bb", "bbbb"); // size 6
+        assertEquals(10, cache.size());
+
+        // cause the size to grow to 12 should evict 'A'
+        set("C", "c", "c");
+        cache.flush();
+        assertEquals(8, cache.size());
+        assertAbsent("A");
+        assertValue("B", "bb", "bbbb");
+        assertValue("C", "c", "c");
+
+        // causing the size to grow to 10 should evict nothing
+        set("D", "d", "d");
+        cache.flush();
+        assertEquals(10, cache.size());
+        assertAbsent("A");
+        assertValue("B", "bb", "bbbb");
+        assertValue("C", "c", "c");
+        assertValue("D", "d", "d");
+
+        // causing the size to grow to 18 should evict 'B' and 'C'
+        set("E", "eeee", "eeee");
+        cache.flush();
+        assertEquals(10, cache.size());
+        assertAbsent("A");
+        assertAbsent("B");
+        assertAbsent("C");
+        assertValue("D", "d", "d");
+        assertValue("E", "eeee", "eeee");
+    }
+
+    public void testEvictOnUpdate() throws Exception {
+        cache.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
+
+        set("A", "a", "aa"); // size 3
+        set("B", "b", "bb"); // size 3
+        set("C", "c", "cc"); // size 3
+        assertEquals(9, cache.size());
+
+        // causing the size to grow to 11 should evict 'A'
+        set("B", "b", "bbbb");
+        cache.flush();
+        assertEquals(8, cache.size());
+        assertAbsent("A");
+        assertValue("B", "b", "bbbb");
+        assertValue("C", "c", "cc");
+    }
+
+    public void testEvictionHonorsLruFromCurrentSession() throws Exception {
+        cache.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
+        set("A", "a", "a");
+        set("B", "b", "b");
+        set("C", "c", "c");
+        set("D", "d", "d");
+        set("E", "e", "e");
+        cache.get("B").close(); // 'B' is now least recently used
+
+        // causing the size to grow to 12 should evict 'A'
+        set("F", "f", "f");
+        // causing the size to grow to 12 should evict 'C'
+        set("G", "g", "g");
+        cache.flush();
+        assertEquals(10, cache.size());
+        assertAbsent("A");
+        assertValue("B", "b", "b");
+        assertAbsent("C");
+        assertValue("D", "d", "d");
+        assertValue("E", "e", "e");
+        assertValue("F", "f", "f");
+    }
+
+    public void testEvictionHonorsLruFromPreviousSession() throws Exception {
+        set("A", "a", "a");
+        set("B", "b", "b");
+        set("C", "c", "c");
+        set("D", "d", "d");
+        set("E", "e", "e");
+        set("F", "f", "f");
+        cache.get("B").close(); // 'B' is now least recently used
+        assertEquals(12, cache.size());
+        cache.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
+
+        set("G", "g", "g");
+        cache.flush();
+        assertEquals(10, cache.size());
+        assertAbsent("A");
+        assertValue("B", "b", "b");
+        assertAbsent("C");
+        assertValue("D", "d", "d");
+        assertValue("E", "e", "e");
+        assertValue("F", "f", "f");
+        assertValue("G", "g", "g");
+    }
+
+    public void testCacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception {
+        cache.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
+        set("A", "aaaaa", "aaaaaa"); // size=11
+        cache.flush();
+        assertAbsent("A");
+    }
+
+    public void testCacheSingleValueOfSizeGreaterThanMaxSize() throws Exception {
+        cache.close();
+        cache = DiskLruCache.open(cacheDir, appVersion, 2, 10);
+        set("A", "aaaaaaaaaaa", "a"); // size=12
+        cache.flush();
+        assertAbsent("A");
+    }
+
+    public void testConstructorDoesNotAllowZeroCacheSize() throws Exception {
+        try {
+            DiskLruCache.open(cacheDir, appVersion, 2, 0);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testConstructorDoesNotAllowZeroValuesPerEntry() throws Exception {
+        try {
+            DiskLruCache.open(cacheDir, appVersion, 0, 10);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testRemoveAbsentElement() throws Exception {
+        cache.remove("A");
+    }
+
+    public void testReadingTheSameStreamMultipleTimes() throws Exception {
+        set("A", "a", "b");
+        DiskLruCache.Snapshot snapshot = cache.get("A");
+        assertSame(snapshot.getInputStream(0), snapshot.getInputStream(0));
+        snapshot.close();
+    }
+
+    public void testRebuildJournalOnRepeatedReads() throws Exception {
+        set("A", "a", "a");
+        set("B", "b", "b");
+        long lastJournalLength = 0;
+        while (true) {
+            long journalLength = journalFile.length();
+            assertValue("A", "a", "a");
+            assertValue("B", "b", "b");
+            if (journalLength < lastJournalLength) {
+                System.out.printf("Journal compacted from %s bytes to %s bytes\n",
+                        lastJournalLength, journalLength);
+                break; // test passed!
+            }
+            lastJournalLength = journalLength;
+        }
+    }
+
+    public void testRebuildJournalOnRepeatedEdits() throws Exception {
+        long lastJournalLength = 0;
+        while (true) {
+            long journalLength = journalFile.length();
+            set("A", "a", "a");
+            set("B", "b", "b");
+            if (journalLength < lastJournalLength) {
+                System.out.printf("Journal compacted from %s bytes to %s bytes\n",
+                        lastJournalLength, journalLength);
+                break;
+            }
+            lastJournalLength = journalLength;
+        }
+
+        // sanity check that a rebuilt journal behaves normally
+        assertValue("A", "a", "a");
+        assertValue("B", "b", "b");
+    }
+
+    public void testOpenCreatesDirectoryIfNecessary() throws Exception {
+        cache.close();
+        File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary");
+        cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE);
+        set("A", "a", "a");
+        assertTrue(new File(dir, "A.0").exists());
+        assertTrue(new File(dir, "A.1").exists());
+        assertTrue(new File(dir, "journal").exists());
+    }
+
+    public void testFileDeletedExternally() throws Exception {
+        set("A", "a", "a");
+        getCleanFile("A", 1).delete();
+        assertNull(cache.get("A"));
+    }
+
+    public void testFileBecomesInaccessibleDuringReadResultsInIoException() throws Exception {
+        set("A", "aaaaa", "a");
+        DiskLruCache.Snapshot snapshot = cache.get("A");
+        InputStream in = snapshot.getInputStream(0);
+        assertEquals('a', in.read());
+        mockOs.enqueueFault("read");
+        try {
+            in.read();
+            fail();
+        } catch (IOException expected) {
+        }
+        snapshot.close();
+    }
+
+    public void testFileBecomesInaccessibleDuringWriteIsSilentlyDiscarded() throws Exception {
+        set("A", "a", "a");
+        DiskLruCache.Editor editor = cache.edit("A");
+        OutputStream out0 = editor.newOutputStream(0);
+        out0.write('b');
+        out0.close();
+        OutputStream out1 = editor.newOutputStream(1);
+        out1.write('c');
+        mockOs.enqueueFault("write");
+        out1.write('c'); // this doesn't throw...
+        out1.close();
+        editor.commit(); // ... but this will abort
+        assertAbsent("A");
+    }
+
+    private void assertJournalEquals(String... expectedBodyLines) throws Exception {
+        List<String> expectedLines = new ArrayList<String>();
+        expectedLines.add(MAGIC);
+        expectedLines.add(VERSION_1);
+        expectedLines.add("100");
+        expectedLines.add("2");
+        expectedLines.add("");
+        expectedLines.addAll(Arrays.asList(expectedBodyLines));
+        assertEquals(expectedLines, readJournalLines());
+    }
+
+    private void createJournal(String... bodyLines) throws Exception {
+        createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines);
+    }
+
+    private void createJournalWithHeader(String magic, String version, String appVersion,
+            String valueCount, String blank, String... bodyLines) throws Exception {
+        Writer writer = new FileWriter(journalFile);
+        writer.write(magic + "\n");
+        writer.write(version + "\n");
+        writer.write(appVersion + "\n");
+        writer.write(valueCount + "\n");
+        writer.write(blank + "\n");
+        for (String line : bodyLines) {
+            writer.write(line);
+            writer.write('\n');
+        }
+        writer.close();
+    }
+
+    private List<String> readJournalLines() throws Exception {
+        List<String> result = new ArrayList<String>();
+        BufferedReader reader = new BufferedReader(new FileReader(journalFile));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            result.add(line);
+        }
+        reader.close();
+        return result;
+    }
+
+    private File getCleanFile(String key, int index) {
+        return new File(cacheDir, key + "." + index);
+    }
+
+    private File getDirtyFile(String key, int index) {
+        return new File(cacheDir, key + "." + index + ".tmp");
+    }
+
+    private String readFile(File file) throws Exception {
+        Reader reader = new FileReader(file);
+        StringWriter writer = new StringWriter();
+        char[] buffer = new char[1024];
+        int count;
+        while ((count = reader.read(buffer)) != -1) {
+            writer.write(buffer, 0, count);
+        }
+        reader.close();
+        return writer.toString();
+    }
+
+    public void writeFile(File file, String content) throws Exception {
+        FileWriter writer = new FileWriter(file);
+        writer.write(content);
+        writer.close();
+    }
+
+    private void assertInoperable(DiskLruCache.Editor editor) throws Exception {
+        try {
+            editor.getString(0);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            editor.set(0, "A");
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            editor.newInputStream(0);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            editor.newOutputStream(0);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            editor.commit();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            editor.abort();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    private void generateSomeGarbageFiles() throws Exception {
+        File dir1 = new File(cacheDir, "dir1");
+        File dir2 = new File(dir1, "dir2");
+        writeFile(getCleanFile("g1", 0), "A");
+        writeFile(getCleanFile("g1", 1), "B");
+        writeFile(getCleanFile("g2", 0), "C");
+        writeFile(getCleanFile("g2", 1), "D");
+        writeFile(getCleanFile("g2", 1), "D");
+        writeFile(new File(cacheDir, "otherFile0"), "E");
+        dir1.mkdir();
+        dir2.mkdir();
+        writeFile(new File(dir2, "otherFile1"), "F");
+    }
+
+    private void assertGarbageFilesAllDeleted() throws Exception {
+        assertFalse(getCleanFile("g1", 0).exists());
+        assertFalse(getCleanFile("g1", 1).exists());
+        assertFalse(getCleanFile("g2", 0).exists());
+        assertFalse(getCleanFile("g2", 1).exists());
+        assertFalse(new File(cacheDir, "otherFile0").exists());
+        assertFalse(new File(cacheDir, "dir1").exists());
+    }
+
+    private void set(String key, String value0, String value1) throws Exception {
+        DiskLruCache.Editor editor = cache.edit(key);
+        editor.set(0, value0);
+        editor.set(1, value1);
+        editor.commit();
+    }
+
+    private void assertAbsent(String key) throws Exception {
+        DiskLruCache.Snapshot snapshot = cache.get(key);
+        if (snapshot != null) {
+            snapshot.close();
+            fail();
+        }
+        assertFalse(getCleanFile(key, 0).exists());
+        assertFalse(getCleanFile(key, 1).exists());
+        assertFalse(getDirtyFile(key, 0).exists());
+        assertFalse(getDirtyFile(key, 1).exists());
+    }
+
+    private void assertValue(String key, String value0, String value1) throws Exception {
+        DiskLruCache.Snapshot snapshot = cache.get(key);
+        assertEquals(value0, snapshot.getString(0));
+        assertEquals(value1, snapshot.getString(1));
+        assertTrue(getCleanFile(key, 0).exists());
+        assertTrue(getCleanFile(key, 1).exists());
+        snapshot.close();
+    }
+}
\ No newline at end of file
diff --git a/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java b/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java
index 8b706cd..a1b9920 100644
--- a/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java
+++ b/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java
@@ -40,4 +40,31 @@
         } catch (IOException expected) {
         }
     }
+
+    public void testClose() throws Exception {
+        FileOutputStream fos = new FileOutputStream(File.createTempFile("FileOutputStreamTest", "tmp"));
+
+        // Closing an already-closed stream is a no-op...
+        fos.close();
+        // ...as is flushing...
+        fos.flush();
+
+        // ...but any explicit write is an error.
+        byte[] bytes = "hello".getBytes();
+        try {
+            fos.write(bytes);
+            fail();
+        } catch (IOException expected) {
+        }
+        try {
+            fos.write(bytes, 0, 2);
+            fail();
+        } catch (IOException expected) {
+        }
+        try {
+            fos.write(42);
+            fail();
+        } catch (IOException expected) {
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/io/OldFileOutputStreamTest.java b/luni/src/test/java/libcore/java/io/OldFileOutputStreamTest.java
deleted file mode 100644
index 3661b3e..0000000
--- a/luni/src/test/java/libcore/java/io/OldFileOutputStreamTest.java
+++ /dev/null
@@ -1,204 +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.java.io;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-public class OldFileOutputStreamTest extends junit.framework.TestCase {
-
-    public String fileName;
-
-    FileOutputStream fos;
-
-    FileInputStream fis;
-
-    File f;
-
-    String tmpDirName = System.getProperty("java.io.tmpdir");
-
-    File tmpDir = new File(tmpDirName);
-
-    public String fileString = "Test_All_Tests\nTest_java_io_BufferedInputStream\nTest_java_io_BufferedOutputStream\nTest_java_io_ByteArrayInputStream\nTest_java_io_ByteArrayOutputStream\nTest_java_io_DataInputStream\nTest_java_io_File\nTest_java_io_FileDescriptor\nTest_java_io_FileInputStream\nTest_java_io_FileNotFoundException\nTest_FileOutputStream\nTest_java_io_FilterInputStream\nTest_java_io_FilterOutputStream\nTest_java_io_InputStream\nTest_java_io_IOException\nTest_java_io_OutputStream\nTest_java_io_PrintStream\nTest_java_io_RandomAccessFile\nTest_java_io_SyncFailedException\nTest_java_lang_AbstractMethodError\nTest_java_lang_ArithmeticException\nTest_java_lang_ArrayIndexOutOfBoundsException\nTest_java_lang_ArrayStoreException\nTest_java_lang_Boolean\nTest_java_lang_Byte\nTest_java_lang_Character\nTest_java_lang_Class\nTest_java_lang_ClassCastException\nTest_java_lang_ClassCircularityError\nTest_java_lang_ClassFormatError\nTest_java_lang_ClassLoader\nTest_java_lang_ClassNotFoundException\nTest_java_lang_CloneNotSupportedException\nTest_java_lang_Double\nTest_java_lang_Error\nTest_java_lang_Exception\nTest_java_lang_ExceptionInInitializerError\nTest_java_lang_Float\nTest_java_lang_IllegalAccessError\nTest_java_lang_IllegalAccessException\nTest_java_lang_IllegalArgumentException\nTest_java_lang_IllegalMonitorStateException\nTest_java_lang_IllegalThreadStateException\nTest_java_lang_IncompatibleClassChangeError\nTest_java_lang_IndexOutOfBoundsException\nTest_java_lang_InstantiationError\nTest_java_lang_InstantiationException\nTest_java_lang_Integer\nTest_java_lang_InternalError\nTest_java_lang_InterruptedException\nTest_java_lang_LinkageError\nTest_java_lang_Long\nTest_java_lang_Math\nTest_java_lang_NegativeArraySizeException\nTest_java_lang_NoClassDefFoundError\nTest_java_lang_NoSuchFieldError\nTest_java_lang_NoSuchMethodError\nTest_java_lang_NullPointerException\nTest_java_lang_Number\nTest_java_lang_NumberFormatException\nTest_java_lang_Object\nTest_java_lang_OutOfMemoryError\nTest_java_lang_RuntimeException\nTest_java_lang_SecurityManager\nTest_java_lang_Short\nTest_java_lang_StackOverflowError\nTest_java_lang_String\nTest_java_lang_StringBuffer\nTest_java_lang_StringIndexOutOfBoundsException\nTest_java_lang_System\nTest_java_lang_Thread\nTest_java_lang_ThreadDeath\nTest_java_lang_ThreadGroup\nTest_java_lang_Throwable\nTest_java_lang_UnknownError\nTest_java_lang_UnsatisfiedLinkError\nTest_java_lang_VerifyError\nTest_java_lang_VirtualMachineError\nTest_java_lang_vm_Image\nTest_java_lang_vm_MemorySegment\nTest_java_lang_vm_ROMStoreException\nTest_java_lang_vm_VM\nTest_java_lang_Void\nTest_java_net_BindException\nTest_java_net_ConnectException\nTest_java_net_DatagramPacket\nTest_java_net_DatagramSocket\nTest_java_net_DatagramSocketImpl\nTest_java_net_InetAddress\nTest_java_net_NoRouteToHostException\nTest_java_net_PlainDatagramSocketImpl\nTest_java_net_PlainSocketImpl\nTest_java_net_Socket\nTest_java_net_SocketException\nTest_java_net_SocketImpl\nTest_java_net_SocketInputStream\nTest_java_net_SocketOutputStream\nTest_java_net_UnknownHostException\nTest_java_util_ArrayEnumerator\nTest_java_util_Date\nTest_java_util_EventObject\nTest_java_util_HashEnumerator\nTest_java_util_Hashtable\nTest_java_util_Properties\nTest_java_util_ResourceBundle\nTest_java_util_tm\nTest_java_util_Vector\n";
-
-    public void test_ConstructorLjava_io_File() throws Exception {
-        try {
-            fos = new FileOutputStream(tmpDir);
-            fail("Test 1: FileNotFoundException expected.");
-        } catch (FileNotFoundException e) {
-            // Expected.
-        }
-
-        f = new File(fileName = System.getProperty("java.io.tmpdir"), "fos.tst");
-        fos = new FileOutputStream(f);
-    }
-
-    public void test_ConstructorLjava_io_FileZ() throws Exception {
-        try {
-            fos = new FileOutputStream(tmpDir, false);
-            fail("Test 1: FileNotFoundException expected.");
-        } catch (FileNotFoundException e) {
-            // Expected.
-        }
-
-        f = new File(tmpDirName, "fos.tst");
-        fos = new FileOutputStream(f, false);
-        fos.write("FZ1".getBytes(), 0, 3);
-        fos.close();
-        // Append data to existing file
-        fos = new FileOutputStream(f, true);
-        fos.write(fileString.getBytes());
-        fos.close();
-        byte[] buf = new byte[fileString.length() + 3];
-        fis = new FileInputStream(f);
-        fis.read(buf, 0, buf.length);
-        assertTrue("Test 2: Failed to create appending stream.", new String(buf, 0,
-                buf.length).equals("FZ1" + fileString));
-        fis.close();
-
-        // Check that the existing file is overwritten
-        fos = new FileOutputStream(f, false);
-        fos.write("FZ2".getBytes(), 0, 3);
-        fos.close();
-        fis = new FileInputStream(f);
-        int bytesRead = fis.read(buf, 0, buf.length);
-        assertTrue("Test 3: Failed to overwrite stream.", new String(buf, 0,
-                bytesRead).equals("FZ2"));
-    }
-
-    public void test_ConstructorLjava_lang_String() throws Exception {
-        try {
-            fos = new FileOutputStream(tmpDirName);
-            fail("Test 1: FileNotFoundException expected.");
-        } catch (FileNotFoundException e) {
-            // Expected.
-        }
-    }
-
-    public void test_ConstructorLjava_lang_StringZ() throws Exception {
-        try {
-            fos = new FileOutputStream(tmpDirName, true);
-            fail("Test 1: FileNotFoundException expected.");
-        } catch (FileNotFoundException e) {
-            // Expected.
-        }
-
-        f = new File(tmpDirName, "fos.tst");
-        fos = new FileOutputStream(f.getPath(), false);
-        fos.write("HI".getBytes(), 0, 2);
-        fos.close();
-        // Append data to existing file
-        fos = new FileOutputStream(f.getPath(), true);
-        fos.write(fileString.getBytes());
-        fos.close();
-        byte[] buf = new byte[fileString.length() + 2];
-        fis = new FileInputStream(f.getPath());
-        fis.read(buf, 0, buf.length);
-        assertTrue("Failed to create appending stream", new String(buf, 0,
-                buf.length).equals("HI" + fileString));
-        fis.close();
-
-        // Check that the existing file is overwritten
-        fos = new FileOutputStream(f.getPath(), false);
-        fos.write("HI".getBytes(), 0, 2);
-        fos.close();
-        fis = new FileInputStream(f.getPath());
-        int bytesRead = fis.read(buf, 0, buf.length);
-        assertTrue("Failed to overwrite stream", new String(buf, 0,
-                bytesRead).equals("HI"));
-    }
-
-    public void test_write$B() throws Exception {
-        // Test for method void java.io.FileOutputStream.write(byte [])
-        f = new File(System.getProperty("java.io.tmpdir"), "output.tst");
-        fos = new FileOutputStream(f.getPath());
-        fos.write(fileString.getBytes());
-        fos.close();
-        try {
-            fos.write(fileString.getBytes());
-            fail("Test 1: IOException expected.");
-        } catch (IOException e) {
-            // Expected.
-        }
-
-        fis = new FileInputStream(f.getPath());
-        byte rbytes[] = new byte[4000];
-        fis.read(rbytes, 0, fileString.length());
-        assertTrue("Test 2: Incorrect string written or read.",
-                new String(rbytes, 0, fileString.length()).equals(fileString));
-    }
-
-    public void test_writeI() throws IOException {
-        // Test for method void java.io.FileOutputStream.write(int)
-        f = new File(System.getProperty("java.io.tmpdir"), "output.tst");
-        fos = new FileOutputStream(f.getPath());
-        fos.write('t');
-        fos.close();
-        try {
-            fos.write(42);
-            fail("Test: IOException expected.");
-        } catch (IOException e) {
-            // Expected.
-        }
-
-        fis = new FileInputStream(f.getPath());
-        assertEquals("Test 1: Incorrect char written or read.",
-                't', fis.read());
-    }
-
-    public void test_write$BII3() {
-        try {
-            new FileOutputStream(new FileDescriptor()).write(new byte[1], 0, 0);
-        } catch (Exception e) {
-            fail("Unexpected exception: " + e);
-        }
-    }
-
-    public void test_getChannel() throws Exception {
-        // Make sure that system properties are set correctly
-        if (tmpDir == null) {
-            throw new Exception("System property java.io.tmpdir not defined.");
-        }
-        File tmpfile = File.createTempFile("FileOutputStream", "tmp");
-        tmpfile.deleteOnExit();
-        FileOutputStream fos = new FileOutputStream(tmpfile);
-        byte[] b = new byte[10];
-        for (int i = 10; i < b.length; i++) {
-            b[i] = (byte) i;
-        }
-        fos.write(b);
-        fos.flush();
-        fos.close();
-        FileOutputStream f = new FileOutputStream(tmpfile, true);
-        assertEquals(10, f.getChannel().position());
-    }
-
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (f != null)
-            f.delete();
-        if (fis != null)
-            fis.close();
-        if (fos != null)
-            fos.close();
-    }
-}
diff --git a/luni/src/test/java/libcore/java/io/SerializationTest.java b/luni/src/test/java/libcore/java/io/SerializationTest.java
new file mode 100644
index 0000000..74becfe
--- /dev/null
+++ b/luni/src/test/java/libcore/java/io/SerializationTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.Serializable;
+import junit.framework.TestCase;
+import libcore.java.util.SerializableTester;
+
+public final class SerializationTest extends TestCase {
+
+    // http://b/4471249
+    public void testSerializeFieldMadeTransient() throws Exception {
+        // this was created by serializing a FieldMadeTransient with a non-0 transientInt
+        String s = "aced0005737200346c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657"
+                + "374244669656c644d6164655472616e7369656e74000000000000000002000149000c7472616e736"
+                + "9656e74496e747870abababab";
+        FieldMadeTransient deserialized = (FieldMadeTransient) SerializableTester.deserializeHex(s);
+        assertEquals(0, deserialized.transientInt);
+    }
+
+    static class FieldMadeTransient implements Serializable {
+        private static final long serialVersionUID = 0L;
+        private transient int transientInt;
+    }
+}
diff --git a/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java b/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java
index 470c2e4..d2b1b0a 100644
--- a/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java
+++ b/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java
@@ -31,4 +31,11 @@
 
         assertEquals("ffffffff", IntegralToString.intToHexString(-1, false, 0));
     }
+
+    public void testBytesToHexString() {
+        assertEquals("abcdef", IntegralToString.bytesToHexString(
+                new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, false));
+        assertEquals("ABCDEF", IntegralToString.bytesToHexString(
+                new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/net/CookiesTest.java b/luni/src/test/java/libcore/java/net/CookiesTest.java
index 69dea61..87aa571 100644
--- a/luni/src/test/java/libcore/java/net/CookiesTest.java
+++ b/luni/src/test/java/libcore/java/net/CookiesTest.java
@@ -268,6 +268,39 @@
                 + "b=\"banana\";$Path=\"/\";$Domain=\".local\"");
     }
 
+    public void testRedirectsDoNotIncludeTooManyCookies() throws Exception {
+        MockWebServer redirectTarget = new MockWebServer();
+        redirectTarget.enqueue(new MockResponse().setBody("A"));
+        redirectTarget.play();
+
+        MockWebServer redirectSource = new MockWebServer();
+        redirectSource.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+                .addHeader("Location: " + redirectTarget.getUrl("/")));
+        redirectSource.play();
+
+        CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
+        HttpCookie cookie = new HttpCookie("c", "cookie");
+        cookie.setDomain(".local");
+        cookie.setPath("/");
+        String portList = Integer.toString(redirectSource.getPort());
+        cookie.setPortlist(portList);
+        cookieManager.getCookieStore().add(redirectSource.getUrl("/").toURI(), cookie);
+        CookieHandler.setDefault(cookieManager);
+
+        get(redirectSource, "/");
+        RecordedRequest request = redirectSource.takeRequest();
+
+        assertContains(request.getHeaders(), "Cookie: $Version=\"1\"; "
+                + "c=\"cookie\";$Path=\"/\";$Domain=\".local\";$Port=\"" + portList + "\"");
+
+        for (String header : redirectTarget.takeRequest().getHeaders()) {
+            if (header.startsWith("Cookie")) {
+                fail(header);
+            }
+        }
+    }
+
     /**
      * Test which headers show up where. The cookie manager should be notified of both
      * user-specified and derived headers like {@code Content-Length}. Headers named {@code Cookie}
diff --git a/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java b/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java
index 07e0897..0b4009c 100644
--- a/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java
+++ b/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java
@@ -267,7 +267,6 @@
             fail("Exception during test : " + e.getMessage());
         }
 
-        if ("true".equals(System.getProperty("run.ipv6tests"))) {
             System.out
                     .println("Running test_connectLjava_net_InetAddressI" +
                             "(DatagramSocketTest) with IPv6GlobalAddressJcl4: "
@@ -285,7 +284,6 @@
             } catch (Exception e) {
                 fail("Exception during test : " + e.getMessage());
             }
-        }
 
         try {
             // Create a connected datagram socket to test
@@ -627,7 +625,6 @@
                             + e.toString());
         }
 
-        if ("true".equals(System.getProperty("run.ipv6tests"))) {
             System.out
                     .println("Running test_connectLjava_net_InetAddressI(DatagramSocketTest) with IPv6 address");
             try {
@@ -643,7 +640,6 @@
                         "Unexcpected exception when trying to connect at native level with bad IPv6 address for signature with no exception to be returned: "
                                 + e.toString());
             }
-        }
     }
 
     public void test_disconnect() {
@@ -659,7 +655,6 @@
             fail("Exception during test : " + e.getMessage());
         }
 
-        if ("true".equals(System.getProperty("run.ipv6tests"))) {
             System.out
                     .println("Running test_disconnect(DatagramSocketTest) with IPv6GlobalAddressJcl4: "
                             + Support_Configuration.IPv6GlobalAddressJcl4);
@@ -675,8 +670,6 @@
             } catch (Exception e) {
                 fail("Exception during test : " + e.getMessage());
             }
-        }
-
     }
 
     public void test_getInetAddress() {
@@ -719,49 +712,6 @@
 
     }
 
-    public void test_getLocalAddress() {
-        // Test for method java.net.InetAddress
-        // java.net.DatagramSocket.getLocalAddress()
-        InetAddress local = null;
-        try {
-            int portNumber = Support_PortManager.getNextPortForUDP();
-            local = InetAddress.getLocalHost();
-            ds = new java.net.DatagramSocket(portNumber, local);
-            assertTrue("Returned incorrect address. Got:"
-                    + ds.getLocalAddress()
-                    + " wanted: "
-                    + InetAddress.getByName(InetAddress.getLocalHost()
-                            .getHostName()), InetAddress.getByName(
-                    InetAddress.getLocalHost().getHostName()).equals(
-                    ds.getLocalAddress()));
-
-            // now validate thet behaviour when the any address is returned
-            String preferIPv4StackValue = System
-                    .getProperty("java.net.preferIPv4Stack");
-            String preferIPv6AddressesValue = System
-                    .getProperty("java.net.preferIPv6Addresses");
-            DatagramSocket s = new DatagramSocket(0);
-            if (((preferIPv4StackValue == null) || preferIPv4StackValue
-                    .equalsIgnoreCase("false"))
-                    && (preferIPv6AddressesValue != null)
-                    && (preferIPv6AddressesValue.equals("true"))) {
-                assertTrue(
-                        "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=false "
-                                + s.getLocalSocketAddress(), s
-                                .getLocalAddress() instanceof Inet6Address);
-            } else {
-                assertTrue(
-                        "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=true "
-                                + s.getLocalSocketAddress(), s
-                                .getLocalAddress() instanceof Inet4Address);
-            }
-            s.close();
-        } catch (Exception e) {
-            fail(
-                    "Exception during getLocalAddress: " + local + " - " + e);
-        }
-    }
-
     public void test_getLocalPort() {
         // Test for method int java.net.DatagramSocket.getLocalPort()
         try {
@@ -1314,150 +1264,94 @@
     }
 
     public void test_bindLjava_net_SocketAddress() throws Exception {
-        class mySocketAddress extends SocketAddress {
+        int[] ports = Support_PortManager.getNextPortsForUDP(3);
+        int serverPortNumber = ports[1];
 
-            public mySocketAddress() {
-            }
-        }
+        // now create a socket that is not bound and then bind it
+        InetAddress localHost = InetAddress.getLocalHost();
+        InetSocketAddress localAddress1 = new InetSocketAddress(localHost, ports[0]);
+        DatagramSocket theSocket = new DatagramSocket(localAddress1);
 
-        DatagramServer server = null;
-        try {
-            // now create a socket that is not bound and then bind it
-            int[] ports = Support_PortManager.getNextPortsForUDP(3);
-            int portNumber = ports[0];
-            int serverPortNumber = ports[1];
-            DatagramSocket theSocket = new DatagramSocket(
-                    new InetSocketAddress(InetAddress.getLocalHost(),
-                            portNumber));
+        // validate that the localSocketAddress reflects the address we bound to
+        assertEquals(localAddress1, theSocket.getLocalSocketAddress());
 
-            // validate that the localSocketAddress reflects the address we
-            // bound to
-            assertTrue("Local address not correct after bind:"
-                    + theSocket.getLocalSocketAddress().toString()
-                    + "Expected: "
-                    + (new InetSocketAddress(InetAddress.getLocalHost(),
-                            portNumber)).toString(), theSocket
-                    .getLocalSocketAddress().equals(
-                            new InetSocketAddress(InetAddress.getLocalHost(),
-                                    portNumber)));
+        // now make sure that datagrams sent from this socket appear to come
+        // from the address we bound to
+        InetSocketAddress localAddress2 = new InetSocketAddress(localHost, ports[2]);
+        DatagramSocket ds = new DatagramSocket((SocketAddress) null);
+        ds.bind(localAddress2);
 
-            // now make sure that datagrams sent from this socket appear to come
-            // from the address we bound to
-            InetAddress localHost = InetAddress.getLocalHost();
-            portNumber = ports[2];
-            DatagramSocket ds = new DatagramSocket((SocketAddress) null);
-            ds.bind(new InetSocketAddress(localHost, portNumber));
+        DatagramServer server = new DatagramServer(serverPortNumber, localHost);
+        server.start();
+        Thread.sleep(1000);
 
-            try {
-                server = new DatagramServer(serverPortNumber, localHost);
-                server.start();
-                Thread.sleep(1000);
-            } catch (Exception e) {
-                fail(
-                        "Failed to set up datagram server for bin datagram socket test ");
-            }
+        ds.connect(new InetSocketAddress(localHost, serverPortNumber));
 
-            ds.connect(new InetSocketAddress(localHost, serverPortNumber));
-
-            byte[] sendBytes = { 'T', 'e', 's', 't', 0 };
-            DatagramPacket send = new DatagramPacket(sendBytes,
-                    sendBytes.length);
-            ds.send(send);
-            Thread.sleep(1000);
-            ds.close();
-            assertTrue(
-                    "Address in packet sent does not match address bound to:"
-                            + server.rdp.getAddress() + ":"
-                            + server.rdp.getPort() + ":" + localHost + ":"
-                            + portNumber, (server.rdp.getAddress()
-                            .equals(localHost))
-                            && (server.rdp.getPort() == portNumber));
-
-            // validate if we pass in null that it picks an address for us and
-            // all is ok
-            theSocket = new DatagramSocket((SocketAddress) null);
-            theSocket.bind(null);
-            assertNotNull("Bind with null did not work", theSocket
-                    .getLocalSocketAddress());
-            theSocket.close();
-
-            // now check the error conditions
-
-            // Address we cannot bind to
-            theSocket = new DatagramSocket((SocketAddress) null);
-            try {
-                theSocket
-                        .bind(new InetSocketAddress(
-                                InetAddress
-                                        .getByAddress(Support_Configuration.nonLocalAddressBytes),
-                                Support_PortManager.getNextPortForUDP()));
-                fail("No exception when binding to bad address");
-            } catch (SocketException ex) {
-            }
-            theSocket.close();
-
-            // Address that we have allready bound to
-            ports = Support_PortManager.getNextPortsForUDP(2);
-            theSocket = new DatagramSocket((SocketAddress) null);
-            DatagramSocket theSocket2 = new DatagramSocket(ports[0]);
-            try {
-                InetSocketAddress theAddress = new InetSocketAddress(
-                        InetAddress.getLocalHost(), ports[1]);
-                theSocket.bind(theAddress);
-                theSocket2.bind(theAddress);
-                fail("No exception binding to address that is not available");
-            } catch (SocketException ex) {
-                //expected
-            }
-            theSocket.close();
-            theSocket2.close();
-
-            /*
-            SecurityManager sm = new SecurityManager() {
-
-                public void checkPermission(Permission perm) {
-                }
-
-                public void checkListen(int port) {
-                    throw new SecurityException();
-                }
-            };
-
-            ports = Support_PortManager.getNextPortsForUDP(2);
-            ds = new DatagramSocket(null);
-            SecurityManager oldSm = System.getSecurityManager();
-            System.setSecurityManager(sm);
-            try {
-
-                ds.bind(new InetSocketAddress(localHost, ports[0]));
-                fail("SecurityException should be thrown.");
-            } catch (SecurityException e) {
-                // expected
-            } catch (SocketException e) {
-                fail("SocketException was thrown.");
-            } finally {
-                System.setSecurityManager(oldSm);
-            }
-            */
-
-            // unsupported SocketAddress subclass
-            theSocket = new DatagramSocket((SocketAddress) null);
-            try {
-                theSocket.bind(new mySocketAddress());
-                fail("No exception when binding using unsupported SocketAddress subclass");
-            } catch (IllegalArgumentException ex) {
-            }
-            theSocket.close();
-
-        } catch (Exception e) {
-            fail("Unexpected exception during bind test : " + e.getMessage());
-        }
+        byte[] sendBytes = { 'T', 'e', 's', 't', 0 };
+        DatagramPacket send = new DatagramPacket(sendBytes, sendBytes.length);
+        ds.send(send);
+        Thread.sleep(1000);
+        ds.close();
+        // Check that the address in the packet matches the bound address.
+        assertEquals(localAddress2, server.rdp.getSocketAddress());
 
         if (server != null) {
             server.stopServer();
         }
     }
 
+    public void test_bindLjava_net_SocketAddress_null() throws Exception {
+        // validate if we pass in null that it picks an address for us.
+        DatagramSocket theSocket = new DatagramSocket((SocketAddress) null);
+        theSocket.bind(null);
+        assertNotNull(theSocket.getLocalSocketAddress());
+        theSocket.close();
+    }
+
+    public void test_bindLjava_net_SocketAddress_bad_address() throws Exception {
+        // Address we cannot bind to
+        DatagramSocket theSocket = new DatagramSocket((SocketAddress) null);
+        try {
+            InetAddress badAddress = InetAddress.getByAddress(Support_Configuration.nonLocalAddressBytes);
+            theSocket.bind(new InetSocketAddress(badAddress, Support_PortManager.getNextPortForUDP()));
+            fail("No exception when binding to bad address");
+        } catch (SocketException expected) {
+        }
+        theSocket.close();
+    }
+
+    public void test_bindLjava_net_SocketAddress_address_in_use() throws Exception {
+        // Address that we have already bound to
+        int[] ports = Support_PortManager.getNextPortsForUDP(2);
+        DatagramSocket theSocket1 = new DatagramSocket((SocketAddress) null);
+        DatagramSocket theSocket2 = new DatagramSocket(ports[0]);
+        try {
+            InetSocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), ports[1]);
+            theSocket1.bind(theAddress);
+            theSocket2.bind(theAddress);
+            fail("No exception binding to address that is not available");
+        } catch (SocketException expected) {
+        }
+        theSocket1.close();
+        theSocket2.close();
+    }
+
+    public void test_bindLjava_net_SocketAddress_unsupported_address_type() throws Exception {
+        class mySocketAddress extends SocketAddress {
+            public mySocketAddress() {
+            }
+        }
+
+        // unsupported SocketAddress subclass
+        DatagramSocket theSocket = new DatagramSocket((SocketAddress) null);
+        try {
+            theSocket.bind(new mySocketAddress());
+            fail("No exception when binding using unsupported SocketAddress subclass");
+        } catch (IllegalArgumentException expected) {
+        }
+        theSocket.close();
+    }
+
     public void test_connectLjava_net_SocketAddress() {
 
         // validate that we get the PortUnreachable exception if we try to
@@ -1884,70 +1778,6 @@
         }
     }
 
-    public void test_getLocalSocketAddress() throws Exception {
-        int portNumber = Support_PortManager.getNextPortForUDP();
-        DatagramSocket s = new DatagramSocket(new InetSocketAddress(
-                InetAddress.getLocalHost(), portNumber));
-        assertTrue("Returned incorrect InetSocketAddress(1):"
-                + s.getLocalSocketAddress().toString()
-                + "Expected: "
-                + (new InetSocketAddress(InetAddress.getLocalHost(),
-                        portNumber)).toString(), s.getLocalSocketAddress()
-                .equals(
-                        new InetSocketAddress(InetAddress.getLocalHost(),
-                                portNumber)));
-        s.close();
-
-        InetSocketAddress remoteAddress = (InetSocketAddress) s
-                .getRemoteSocketAddress();
-
-        // now create a socket that is not bound and validate we get the
-        // right answer
-        DatagramSocket theSocket = new DatagramSocket((SocketAddress) null);
-        assertNull(
-                "Returned incorrect InetSocketAddress -unbound socket- Expected null",
-                theSocket.getLocalSocketAddress());
-
-        // now bind the socket and make sure we get the right answer
-        portNumber = Support_PortManager.getNextPortForUDP();
-        theSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(),
-                portNumber));
-        assertTrue("Returned incorrect InetSocketAddress(2):"
-                + theSocket.getLocalSocketAddress().toString()
-                + "Expected: "
-                + (new InetSocketAddress(InetAddress.getLocalHost(),
-                        portNumber)).toString(), theSocket
-                .getLocalSocketAddress().equals(
-                        new InetSocketAddress(InetAddress.getLocalHost(),
-                                portNumber)));
-        theSocket.close();
-
-        // now validate thet behaviour when the any address is returned
-        s = new DatagramSocket(0);
-
-        String preferIPv4StackValue = System
-                .getProperty("java.net.preferIPv4Stack");
-        String preferIPv6AddressesValue = System
-                .getProperty("java.net.preferIPv6Addresses");
-        if (((preferIPv4StackValue == null) || preferIPv4StackValue
-                .equalsIgnoreCase("false"))
-                && (preferIPv6AddressesValue != null)
-                && (preferIPv6AddressesValue.equals("true"))) {
-            assertTrue(
-                    "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=false "
-                            + s.getLocalSocketAddress(),
-                    ((InetSocketAddress) s.getLocalSocketAddress())
-                            .getAddress() instanceof Inet6Address);
-        } else {
-            assertTrue(
-                    "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=true "
-                            + s.getLocalSocketAddress(),
-                    ((InetSocketAddress) s.getLocalSocketAddress())
-                            .getAddress() instanceof Inet4Address);
-        }
-        s.close();
-    }
-
     public void test_setReuseAddressZ() throws Exception {
         // test case were we set it to false
         DatagramSocket theSocket1 = null;
@@ -2198,6 +2028,7 @@
         DatagramChannel channel = DatagramChannel.open();
         DatagramSocket socket = channel.socket();
         assertEquals(channel, socket.getChannel());
+        socket.close();
     }
 
     class TestDatagramSocketImplFactory implements DatagramSocketImplFactory {
diff --git a/luni/src/test/java/libcore/java/net/OldResponseCacheTest.java b/luni/src/test/java/libcore/java/net/OldResponseCacheTest.java
deleted file mode 100644
index d292414..0000000
--- a/luni/src/test/java/libcore/java/net/OldResponseCacheTest.java
+++ /dev/null
@@ -1,231 +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.java.net;
-
-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.HttpURLConnection;
-import java.net.NetPermission;
-import java.net.ResponseCache;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.security.Permission;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import junit.framework.TestCase;
-import tests.support.Support_PortManager;
-import tests.support.Support_TestWebData;
-import tests.support.Support_TestWebServer;
-
-public class OldResponseCacheTest extends TestCase {
-
-
-
-    public void test_GetDefault() throws Exception {
-        assertNull(ResponseCache.getDefault());
-    }
-
-    public void test_SetDefaultLjava_net_ResponseCache_Normal() throws Exception {
-        ResponseCache rc1 = new MockResponseCache();
-        ResponseCache rc2 = new MockResponseCache();
-        ResponseCache.setDefault(rc1);
-        assertSame(ResponseCache.getDefault(), rc1);
-        ResponseCache.setDefault(rc2);
-        assertSame(ResponseCache.getDefault(), rc2);
-        ResponseCache.setDefault(null);
-        assertNull(ResponseCache.getDefault());
-    }
-
-    public void test_get() throws Exception {
-        String uri = "http://localhost/";
-        URL url  = new URL(uri);
-        TestResponseCache cache = new TestResponseCache(uri, true);
-        ResponseCache.setDefault(cache);
-        HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
-        httpCon.setUseCaches(true);
-        httpCon.connect();
-        try {
-            Thread.sleep(5000);
-        } catch(Exception e) {}
-
-        InputStream is = httpCon.getInputStream();
-        byte[] array = new byte [10];
-        is.read(array);
-        assertEquals(url.toURI(), cache.getWasCalled);
-        assertEquals("Cache test", new String(array));
-        is.close();
-        httpCon.disconnect();
-
-    }
-
-    public void test_put() throws Exception {
-        // Create test ResponseCache
-        TestResponseCache cache = new TestResponseCache(
-                "http://localhost/not_cached", false);
-        ResponseCache.setDefault(cache);
-
-        // Start Server
-        int port = Support_PortManager.getNextPort();
-        Support_TestWebServer s = new Support_TestWebServer();
-        try {
-            s.initServer(port, 10000, false);
-            Thread.currentThread().sleep(2500);
-
-            // Create connection to server
-            URL url  = new URL("http://localhost:" + port + "/test1");
-            HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
-            httpCon.setUseCaches(true);
-            httpCon.connect();
-            Thread.currentThread().sleep(2500);
-
-            // Check that a call to the cache was made.
-            assertEquals(url.toURI(), cache.getWasCalled);
-            // Make the HttpConnection get the content. It should try to
-            // put it into the cache.
-            httpCon.getContent();
-            // Check if put was called
-            assertEquals(url.toURI(), cache.putWasCalled);
-
-            // get the
-            InputStream is = httpCon.getInputStream();
-
-            byte[] array = new byte[Support_TestWebData.test1.length];
-            is.read(array);
-            assertTrue(Arrays.equals(Support_TestWebData.tests[0], array));
-            is.close();
-            httpCon.disconnect();
-        } finally {
-            s.close();
-        }
-    }
-
-    /*
-     * MockResponseCache for testSetDefault(ResponseCache)
-     */
-    class MockResponseCache extends ResponseCache {
-
-        public CacheResponse get(URI arg0, String arg1, Map arg2)
-                throws IOException {
-            return null;
-        }
-
-        public CacheRequest put(URI arg0, URLConnection arg1)
-                throws IOException {
-            return null;
-        }
-    }
-
-    /*
-     * MockSecurityMaanger. It denies NetPermission("getResponseCache") and
-     * NetPermission("setResponseCache").
-     */
-    class MockSM extends SecurityManager {
-        public void checkPermission(Permission permission) {
-            if (permission instanceof NetPermission) {
-                if ("setResponseCache".equals(permission.getName())) {
-                    throw new SecurityException();
-                }
-            }
-
-            if (permission instanceof NetPermission) {
-                if ("getResponseCache".equals(permission.getName())) {
-
-                    throw new SecurityException();
-                }
-            }
-
-            if (permission instanceof RuntimePermission) {
-                if ("setSecurityManager".equals(permission.getName())) {
-                    return;
-                }
-            }
-        }
-    }
-
-    class TestCacheResponse extends CacheResponse {
-        InputStream is = null;
-
-        public TestCacheResponse(String body) {
-            is = new ByteArrayInputStream(body.getBytes());
-        }
-
-        @Override
-        public InputStream getBody() {
-           return is;
-        }
-
-        @Override
-        public Map<String, List<String>> getHeaders() throws IOException {
-            return new HashMap<String, List<String>>();
-        }
-    }
-
-    class TestCacheRequest extends CacheRequest {
-
-        @Override
-        public OutputStream getBody() {
-            return null;
-        }
-
-        @Override
-        public void abort() {
-        }
-    }
-
-    class TestResponseCache extends ResponseCache {
-
-        URI uri1 = null;
-        boolean testGet = false;
-
-        public URI getWasCalled = null;
-        public URI putWasCalled = null;
-
-        TestResponseCache(String uri, boolean testGet) {
-            try {
-                uri1  = new URI(uri);
-            } catch (URISyntaxException e) {
-            }
-            this.testGet = testGet;
-        }
-
-        @Override
-        public CacheResponse get(URI uri, String rqstMethod, Map rqstHeaders) {
-            getWasCalled = uri;
-            if (testGet && uri.equals(uri1)) {
-                return new TestCacheResponse("Cache test");
-            }
-            return null;
-        }
-
-        @Override
-        public CacheRequest put(URI uri, URLConnection conn) {
-            putWasCalled = uri;
-            if (!testGet && uri.equals(uri1)) {
-                return new TestCacheRequest();
-            }
-            return null;
-        }
-    }
-}
diff --git a/luni/src/test/java/libcore/java/net/OldSocketTest.java b/luni/src/test/java/libcore/java/net/OldSocketTest.java
index 6247719..42f7b78 100644
--- a/luni/src/test/java/libcore/java/net/OldSocketTest.java
+++ b/luni/src/test/java/libcore/java/net/OldSocketTest.java
@@ -20,6 +20,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.BindException;
 import java.net.ConnectException;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -104,13 +105,7 @@
         try {
             socket = new Socket(InetAddress.getByName(null), sport);
             InetAddress address = socket.getLocalAddress();
-            if (Boolean.getBoolean("java.net.preferIPv6Addresses")) {
-                assertTrue(
-                    address.equals(InetAddress.getByName("::1")) ||
-                    address.equals(InetAddress.getByName("0:0:0:0:0:0:0:1")));
-            } else {
-                assertEquals(address, InetAddress.getByName("127.0.0.1"));
-            }
+            assertTrue(address.isLoopbackAddress());
         } finally {
             try {
                 socket.close();
@@ -118,111 +113,31 @@
         }
     }
 
-    public void test_ConstructorLjava_lang_StringILjava_net_InetAddressI()
-            throws IOException {
-        // Test for method java.net.Socket(java.lang.String, int,
-        // java.net.InetAddress, int)
+    public void test_ConstructorLjava_lang_StringILjava_net_InetAddressI1() throws IOException {
         int sport = startServer("Cons String,I,InetAddress,I");
         int portNumber = Support_PortManager.getNextPort();
         s = new Socket(InetAddress.getLocalHost().getHostName(), sport,
                 InetAddress.getLocalHost(), portNumber);
         assertTrue("Failed to create socket", s.getPort() == sport);
+    }
 
-        if (("true".equals(System.getProperty("java.net.preferIPv6Addresses")))
-                && !("true".equals(System
-                        .getProperty("java.net.preferIPv4Stack")))) {
-
-            // ALTERNATE IPv6 TEST
-            if ("true".equals(System.getProperty("run.ipv6tests"))) {
-                System.out
-                        .println("Running testConstructorLjava_lang_StringILjava_net_InetAddressI(OldSocketTest) with IPv6GlobalAddressJcl4: "
-                                + Support_Configuration.IPv6GlobalAddressJcl4);
-                int testPort = Support_PortManager.getNextPort();
-                Socket s1 = null, s2 = null;
-                try {
-                    s1 = new Socket(
-                            Support_Configuration.IPv6GlobalAddressJcl4, 80,
-                            InetAddress.getLocalHost(), testPort);
-                } catch (IOException e) {
-                    // check here if InetAddress.getLocalHost() is returning the
-                    // loopback address.
-                    // if so that is likely the cause of the failure
-                    String warning = "";
-                    try {
-                        InetAddress returnedLocalHost = InetAddress
-                                .getLocalHost();
-                        // don't use isLoopbackAddress for some configurations
-                        // as they do not have it
-                        if (returnedLocalHost.isLoopbackAddress()) {
-                            warning = " - WARNING RETURNED LOCAL HOST IS THE LOOPBACK ADDRESS - MACHINE IS LIKELY NOT CONFIGURED CORRECTLY - THIS LIKELY CAUSED THE FAILURE";
-
-                        }
-                    } catch (Exception ex) {
-                        warning = " - WARNING COULD NOT GET LOCAL HOST - " + ex;
-                    }
-
-                    fail("Exception creating 1st socket" + warning + ": " + e);
-                }
-                boolean exception = false;
-                try {
-                    s2 = new Socket(
-                            Support_Configuration.IPv6GlobalAddressJcl4, 80,
-                            InetAddress.getLocalHost(), testPort);
-                } catch (IOException e) {
-                    exception = true;
-                }
-                try {
-                    s1.close();
-                    if (!exception)
-                        s2.close();
-                } catch (IOException e) {
-                }
-                assertTrue("Was able to create two sockets on same port",
-                        exception);
-            }
-
-        } else {
-            int testPort = Support_PortManager.getNextPort();
-            Socket s1 = null, s2 = null;
-            int serverPort = ss.getLocalPort();
+    public void test_ConstructorLjava_lang_StringILjava_net_InetAddressI2() throws IOException {
+        int testPort = Support_PortManager.getNextPort();
+        Socket s1 = new Socket("www.google.com", 80, null, testPort);
+        try {
+            Socket s2 = new Socket("www.google.com", 80, null, testPort);
             try {
-                s1 = new Socket("127.0.0.1", serverPort, InetAddress
-                        .getLocalHost(), testPort);
-            } catch (IOException e) {
-                e.printStackTrace();
-
-                // check here if InetAddress.getLocalHost() is returning the
-                // loopback address.
-                // if so that is likely the cause of the failure
-                String warning = "";
-                try {
-                    InetAddress returnedLocalHost = InetAddress.getLocalHost();
-                    // don't use isLoopbackAddress for some configurations as
-                    // they do not have it
-                    if (returnedLocalHost.isLoopbackAddress()) {
-                        warning = " - WARNING RETURNED LOCAL HOST IS THE LOOPBACK ADDRESS - MACHINE IS LIKELY NOT CONFIGURED CORRECTLY - THIS LIKELY CAUSED THE FAILURE";
-
-                    }
-                } catch (Exception ex) {
-                    warning = " - WARNING COULD NOT GET LOCAL HOST - " + ex;
-                }
-
-                fail("Exception creating 1st socket" + warning + ": " + e);
+                s2.close();
+            } catch (IOException ignored) {
             }
-            boolean exception = false;
-            try {
-                s2 = new Socket("127.0.0.1", serverPort, InetAddress
-                        .getLocalHost(), testPort);
-            } catch (IOException e) {
-                exception = true;
-            }
+            fail("second connect should have failed with EADDRINUSE");
+        } catch (BindException expected) {
+            // success!
+        } finally {
             try {
                 s1.close();
-                if (!exception)
-                    s2.close();
-            } catch (IOException e) {
+            } catch (IOException ignored) {
             }
-            assertTrue("Was able to create two sockets on same port", exception);
         }
     }
 
@@ -344,31 +259,13 @@
         assertEquals("Returned incorrect InetAddress",
                 InetAddress.getLocalHost(), s.getLocalAddress());
 
-        // now validate that behaviour when the any address is returned
-        String preferIPv4StackValue = System
-                .getProperty("java.net.preferIPv4Stack");
-        String preferIPv6AddressesValue = System
-                .getProperty("java.net.preferIPv6Addresses");
-
+        // now check behavior when the ANY address is returned
         s = new Socket();
         s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0));
 
-        if (((preferIPv4StackValue == null) || preferIPv4StackValue
-                .equalsIgnoreCase("false"))
-                && (preferIPv6AddressesValue != null)
-                && (preferIPv6AddressesValue.equals("true"))) {
-            assertTrue(
-                    "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=false "
-                            + s.getLocalSocketAddress(),
+        assertTrue("ANY address not IPv6: " + s.getLocalSocketAddress(),
                     s.getLocalAddress() instanceof Inet6Address);
-        } else {
-            assertTrue(
-                    "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=true "
-                            + s.getLocalSocketAddress(),
-                    s.getLocalAddress() instanceof Inet4Address);
-        }
         s.close();
-
     }
 
     public void test_getLocalPort() throws IOException {
@@ -865,47 +762,15 @@
         s = new Socket();
         s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0));
 
-        String preferIPv4StackValue = System
-                .getProperty("java.net.preferIPv4Stack");
-        String preferIPv6AddressesValue = System
-                .getProperty("java.net.preferIPv6Addresses");
-        if (((preferIPv4StackValue == null) || preferIPv4StackValue
-                .equalsIgnoreCase("false"))
-                && (preferIPv6AddressesValue != null)
-                && (preferIPv6AddressesValue.equals("true"))) {
-            assertTrue(
-                    "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=false "
-                            + s.getLocalSocketAddress(),
-                    ((InetSocketAddress) s.getLocalSocketAddress())
-                            .getAddress() instanceof Inet6Address);
-        } else {
-            assertTrue(
-                    "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=true "
-                            + s.getLocalSocketAddress(),
-                    ((InetSocketAddress) s.getLocalSocketAddress())
-                            .getAddress() instanceof Inet4Address);
-        }
+        assertTrue("ANY address not IPv6: " + s.getLocalSocketAddress(),
+                ((InetSocketAddress) s.getLocalSocketAddress()).getAddress() instanceof Inet6Address);
         s.close();
 
         // now validate the same for getLocalAddress
         s = new Socket();
         s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0));
-        if (((preferIPv4StackValue == null) || preferIPv4StackValue
-                .equalsIgnoreCase("false"))
-                && (preferIPv6AddressesValue != null)
-                && (preferIPv6AddressesValue.equals("true"))) {
-            assertTrue(
-                    "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=false "
-                            + s.getLocalSocketAddress(),
-                    ((InetSocketAddress) s.getLocalSocketAddress())
-                            .getAddress() instanceof Inet6Address);
-        } else {
-            assertTrue(
-                    "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=true "
-                            + s.getLocalSocketAddress(),
-                    ((InetSocketAddress) s.getLocalSocketAddress())
-                            .getAddress() instanceof Inet4Address);
-        }
+        assertTrue("ANY address not IPv6: " + s.getLocalSocketAddress(),
+                ((InetSocketAddress) s.getLocalSocketAddress()).getAddress() instanceof Inet6Address);
         s.close();
     }
 
diff --git a/luni/src/test/java/libcore/java/net/ServerSocketTest.java b/luni/src/test/java/libcore/java/net/ServerSocketTest.java
new file mode 100644
index 0000000..fe9d423
--- /dev/null
+++ b/luni/src/test/java/libcore/java/net/ServerSocketTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class ServerSocketTest extends junit.framework.TestCase {
+    public void testTimeoutAfterAccept() throws Exception {
+        final ServerSocket ss = new ServerSocket(0);
+        ss.setReuseAddress(true);
+        // On Unix, the receive timeout is inherited by the result of accept(2).
+        // Java specifies that it should always be 0 instead.
+        ss.setSoTimeout(1234);
+        final Socket[] result = new Socket[1];
+        Thread t = new Thread(new Runnable() {
+            public void run() {
+                try {
+                    result[0] = ss.accept();
+                } catch (IOException ex) {
+                    ex.printStackTrace();
+                    fail();
+                }
+            }
+        });
+        t.start();
+        new Socket(ss.getInetAddress(), ss.getLocalPort());
+        t.join();
+        assertEquals(0, result[0].getSoTimeout());
+    }
+}
diff --git a/luni/src/test/java/libcore/java/net/SocketTest.java b/luni/src/test/java/libcore/java/net/SocketTest.java
index 9fa09b7..e796e1e 100644
--- a/luni/src/test/java/libcore/java/net/SocketTest.java
+++ b/luni/src/test/java/libcore/java/net/SocketTest.java
@@ -53,7 +53,7 @@
         try {
             // Bind to an ephemeral local port.
             s.bind(new InetSocketAddress("localhost", 0));
-            assertTrue(s.getLocalAddress().isLoopbackAddress());
+            assertTrue(s.getLocalAddress().toString(), s.getLocalAddress().isLoopbackAddress());
             // What local port did we get?
             int localPort = s.getLocalPort();
             assertTrue(localPort > 0);
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 68cff88..6393e02 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -57,6 +57,7 @@
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
+import junit.framework.TestCase;
 import libcore.java.security.TestKeyStore;
 import libcore.javax.net.ssl.TestSSLContext;
 import tests.http.MockResponse;
@@ -69,7 +70,7 @@
 import static tests.http.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
 import tests.net.StuckServer;
 
-public class URLConnectionTest extends junit.framework.TestCase {
+public final class URLConnectionTest extends TestCase {
 
     private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() {
         protected PasswordAuthentication getPasswordAuthentication() {
@@ -77,6 +78,9 @@
         }
     };
 
+    /** base64("username:password") */
+    private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ=";
+
     private MockWebServer server = new MockWebServer();
     private String hostName;
 
@@ -105,8 +109,11 @@
         HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
         urlConnection.addRequestProperty("D", "e");
         urlConnection.addRequestProperty("D", "f");
+        assertEquals("f", urlConnection.getRequestProperty("D"));
+        assertEquals("f", urlConnection.getRequestProperty("d"));
         Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties();
         assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D")));
+        assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d")));
         try {
             requestHeaders.put("G", Arrays.asList("h"));
             fail("Modified an unmodifiable view.");
@@ -128,7 +135,9 @@
         } catch (NullPointerException expected) {
         }
         urlConnection.setRequestProperty("NullValue", null); // should fail silently!
+        assertNull(urlConnection.getRequestProperty("NullValue"));
         urlConnection.addRequestProperty("AnotherNullValue", null);  // should fail silently!
+        assertNull(urlConnection.getRequestProperty("AnotherNullValue"));
 
         urlConnection.getResponseCode();
         RecordedRequest request = server.takeRequest();
@@ -149,13 +158,27 @@
             fail("Set header after connect");
         } catch (IllegalStateException expected) {
         }
+        try {
+            urlConnection.getRequestProperties();
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testGetRequestPropertyReturnsLastValue() throws Exception {
+        server.play();
+        HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
+        urlConnection.addRequestProperty("A", "value1");
+        urlConnection.addRequestProperty("A", "value2");
+        assertEquals("value2", urlConnection.getRequestProperty("A"));
     }
 
     public void testResponseHeaders() throws IOException, InterruptedException {
         server.enqueue(new MockResponse()
                 .setStatus("HTTP/1.0 200 Fantastic")
-                .addHeader("A: b")
                 .addHeader("A: c")
+                .addHeader("B: d")
+                .addHeader("A: e")
                 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
         server.play();
 
@@ -165,17 +188,38 @@
         assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null));
         Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields();
         assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null));
-        assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A")));
+        assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A")));
+        assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a")));
         try {
             responseHeaders.put("N", Arrays.asList("o"));
             fail("Modified an unmodifiable view.");
         } catch (UnsupportedOperationException expected) {
         }
         try {
-            responseHeaders.get("A").add("d");
+            responseHeaders.get("A").add("f");
             fail("Modified an unmodifiable view.");
         } catch (UnsupportedOperationException expected) {
         }
+        assertEquals("A", urlConnection.getHeaderFieldKey(0));
+        assertEquals("c", urlConnection.getHeaderField(0));
+        assertEquals("B", urlConnection.getHeaderFieldKey(1));
+        assertEquals("d", urlConnection.getHeaderField(1));
+        assertEquals("A", urlConnection.getHeaderFieldKey(2));
+        assertEquals("e", urlConnection.getHeaderField(2));
+    }
+
+    public void testGetErrorStreamOnSuccessfulRequest() throws Exception {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertNull(connection.getErrorStream());
+    }
+
+    public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception {
+        server.enqueue(new MockResponse().setResponseCode(404).setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE));
     }
 
     // Check that if we don't read to the end of a response, the next request on the
@@ -541,7 +585,9 @@
         RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
 
         server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
-        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
+        server.enqueue(new MockResponse()
+                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
+                .clearHeaders());
         server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
         server.play();
 
@@ -572,7 +618,9 @@
         TestSSLContext testSSLContext = TestSSLContext.create();
 
         server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
-        server.enqueue(new MockResponse().clearHeaders()); // for CONNECT
+        server.enqueue(new MockResponse()
+                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
+                .clearHeaders());
         server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
         server.play();
 
@@ -598,6 +646,39 @@
         assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
     }
 
+    public void testProxyAuthenticateOnConnect() throws Exception {
+        Authenticator.setDefault(SIMPLE_AUTHENTICATOR);
+        TestSSLContext testSSLContext = TestSSLContext.create();
+        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
+        server.enqueue(new MockResponse()
+                .setResponseCode(407)
+                .addHeader("Proxy-Authenticate: Basic realm=\"localhost\""));
+        server.enqueue(new MockResponse()
+                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
+                .clearHeaders());
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+
+        URL url = new URL("https://android.com/foo");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
+                server.toProxyAddress());
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+        assertContent("A", connection);
+
+        RecordedRequest connect1 = server.takeRequest();
+        assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine());
+        assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*");
+
+        RecordedRequest connect2 = server.takeRequest();
+        assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine());
+        assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic " + BASE_64_CREDENTIALS);
+
+        RecordedRequest get = server.takeRequest();
+        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
+        assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*");
+    }
+
     public void testDisconnectedConnection() throws IOException {
         server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"));
         server.play();
@@ -613,6 +694,22 @@
         }
     }
 
+    public void testDisconnectBeforeConnect() throws IOException {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.disconnect();
+
+        assertContent("A", connection);
+        assertEquals(200, connection.getResponseCode());
+    }
+
+    public void testDefaultRequestProperty() throws Exception {
+        URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
+        assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
+    }
+
     /**
      * Reads {@code count} characters from the stream. If the stream is
      * exhausted before {@code count} characters can be read, the remaining
@@ -863,6 +960,104 @@
         assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
     }
 
+    public void testSetValidRequestMethod() throws Exception {
+        server.play();
+        assertValidRequestMethod("GET");
+        assertValidRequestMethod("DELETE");
+        assertValidRequestMethod("HEAD");
+        assertValidRequestMethod("OPTIONS");
+        assertValidRequestMethod("POST");
+        assertValidRequestMethod("PUT");
+        assertValidRequestMethod("TRACE");
+    }
+
+    private void assertValidRequestMethod(String requestMethod) throws Exception {
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.setRequestMethod(requestMethod);
+        assertEquals(requestMethod, connection.getRequestMethod());
+    }
+
+    public void testSetInvalidRequestMethodLowercase() throws Exception {
+        server.play();
+        assertInvalidRequestMethod("get");
+    }
+
+    public void testSetInvalidRequestMethodConnect() throws Exception {
+        server.play();
+        assertInvalidRequestMethod("CONNECT");
+    }
+
+    private void assertInvalidRequestMethod(String requestMethod) throws Exception {
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        try {
+            connection.setRequestMethod(requestMethod);
+            fail();
+        } catch (ProtocolException expected) {
+        }
+    }
+
+    public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception {
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        try {
+            connection.setFixedLengthStreamingMode(-2);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testCanSetNegativeChunkedStreamingMode() throws Exception {
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.setChunkedStreamingMode(-2);
+    }
+
+    public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+        try {
+            connection.setFixedLengthStreamingMode(1);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+        try {
+            connection.setChunkedStreamingMode(1);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.setChunkedStreamingMode(1);
+        try {
+            connection.setFixedLengthStreamingMode(1);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.setFixedLengthStreamingMode(1);
+        try {
+            connection.setChunkedStreamingMode(1);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
     public void testSecureFixedLengthStreaming() throws Exception {
         testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
     }
@@ -939,8 +1134,7 @@
         for (int i = 0; i < 3; i++) {
             request = server.takeRequest();
             assertEquals("POST / HTTP/1.1", request.getRequestLine());
-            assertContains(request.getHeaders(), "Authorization: Basic "
-                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
+            assertContains(request.getHeaders(), "Authorization: Basic " + BASE_64_CREDENTIALS);
             assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody()));
         }
     }
@@ -970,8 +1164,7 @@
         for (int i = 0; i < 3; i++) {
             request = server.takeRequest();
             assertEquals("GET / HTTP/1.1", request.getRequestLine());
-            assertContains(request.getHeaders(), "Authorization: Basic "
-                    + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password")
+            assertContains(request.getHeaders(), "Authorization: Basic " + BASE_64_CREDENTIALS);
         }
     }
 
@@ -1089,6 +1282,66 @@
         server2.shutdown();
     }
 
+    public void testResponse300MultipleChoiceWithPost() throws Exception {
+        // Chrome doesn't follow the redirect, but Firefox and the RI both do
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE);
+    }
+
+    public void testResponse301MovedPermanentlyWithPost() throws Exception {
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM);
+    }
+
+    public void testResponse302MovedTemporarilyWithPost() throws Exception {
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP);
+    }
+
+    public void testResponse303SeeOtherWithPost() throws Exception {
+        testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER);
+    }
+
+    private void testResponseRedirectedWithPost(int redirectCode) throws Exception {
+        server.enqueue(new MockResponse()
+                .setResponseCode(redirectCode)
+                .addHeader("Location: /page2")
+                .setBody("This page has moved!"));
+        server.enqueue(new MockResponse().setBody("Page 2"));
+        server.play();
+
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection();
+        connection.setDoOutput(true);
+        byte[] requestBody = { 'A', 'B', 'C', 'D' };
+        OutputStream outputStream = connection.getOutputStream();
+        outputStream.write(requestBody);
+        outputStream.close();
+        assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+        assertTrue(connection.getDoOutput());
+
+        RecordedRequest page1 = server.takeRequest();
+        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
+        assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody()));
+
+        RecordedRequest page2 = server.takeRequest();
+        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
+    }
+
+    public void testResponse305UseProxy() throws Exception {
+        server.play();
+        server.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
+                .addHeader("Location: " + server.getUrl("/"))
+                .setBody("This page has moved!"));
+        server.enqueue(new MockResponse().setBody("Proxy Response"));
+
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection();
+        // Fails on the RI, which gets "Proxy Response"
+        assertEquals("This page has moved!",
+                readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+
+        RecordedRequest page1 = server.takeRequest();
+        assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
+        assertEquals(1, server.getRequestCount());
+    }
+
     public void testHttpsWithCustomTrustManager() throws Exception {
         RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
         RecordingTrustManager trustManager = new RecordingTrustManager();
@@ -1494,6 +1747,115 @@
         assertEquals(-1, in.read()); // throws IOException in Gingerbread
     }
 
+    public void testGetContent() throws Exception {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        InputStream in = (InputStream) connection.getContent();
+        assertEquals("A", readAscii(in, Integer.MAX_VALUE));
+    }
+
+    public void testGetContentOfType() throws Exception {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        try {
+            connection.getContent(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+        try {
+            connection.getContent(new Class[] { null });
+            fail();
+        } catch (NullPointerException expected) {
+        }
+        assertNull(connection.getContent(new Class[] { getClass() }));
+        connection.disconnect();
+    }
+
+    public void testGetOutputStreamOnGetFails() throws Exception {
+        server.enqueue(new MockResponse());
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        try {
+            connection.getOutputStream();
+            fail();
+        } catch (ProtocolException expected) {
+        }
+    }
+
+    public void testGetOutputAfterGetInputStreamFails() throws Exception {
+        server.enqueue(new MockResponse());
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.setDoOutput(true);
+        try {
+            connection.getInputStream();
+            connection.getOutputStream();
+            fail();
+        } catch (ProtocolException expected) {
+        }
+    }
+
+    public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception {
+        server.enqueue(new MockResponse());
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.connect();
+        try {
+            connection.setDoOutput(true);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        try {
+            connection.setDoInput(true);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+        connection.disconnect();
+    }
+
+    public void testClientSendsContentLength() throws Exception {
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        connection.setDoOutput(true);
+        OutputStream out = connection.getOutputStream();
+        out.write(new byte[] { 'A', 'B', 'C' });
+        out.close();
+        assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+        RecordedRequest request = server.takeRequest();
+        assertContains(request.getHeaders(), "Content-Length: 3");
+    }
+
+    public void testGetContentLengthConnects() throws Exception {
+        server.enqueue(new MockResponse().setBody("ABC"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertEquals(3, connection.getContentLength());
+        connection.disconnect();
+    }
+
+    public void testGetContentTypeConnects() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Content-Type: text/plain")
+                .setBody("ABC"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertEquals("text/plain", connection.getContentType());
+        connection.disconnect();
+    }
+
+    public void testGetContentEncodingConnects() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Content-Encoding: identity")
+                .setBody("ABC"));
+        server.play();
+        HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+        assertEquals("identity", connection.getContentEncoding());
+        connection.disconnect();
+    }
+
     /**
      * Returns a gzipped copy of {@code bytes}.
      */
diff --git a/luni/src/test/java/libcore/java/net/URLTest.java b/luni/src/test/java/libcore/java/net/URLTest.java
index 1765630..f00cb25 100644
--- a/luni/src/test/java/libcore/java/net/URLTest.java
+++ b/luni/src/test/java/libcore/java/net/URLTest.java
@@ -18,10 +18,28 @@
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.MalformedURLException;
 import java.net.URL;
 import junit.framework.TestCase;
+import libcore.java.util.SerializableTester;
 
 public class URLTest extends TestCase {
+
+    public void testUrlParts() throws Exception {
+        URL url = new URL("http://username:password@host:8080/directory/file?query#ref");
+        assertEquals("http", url.getProtocol());
+        assertEquals("username:password@host:8080", url.getAuthority());
+        assertEquals("/directory/file", url.getPath());
+        assertEquals("ref", url.getRef());
+
+        assertEquals("username:password", url.getUserInfo());
+        assertEquals("host", url.getHost());
+        assertEquals(8080, url.getPort());
+        assertEquals("/directory/file?query", url.getFile());
+        assertEquals("query", url.getQuery());
+
+        assertEquals(80, url.getDefaultPort());
+    }
     // http://code.google.com/p/android/issues/detail?id=12724
     public void testExplicitPort() throws Exception {
         URL url = new URL("http://www.google.com:80/example?language[id]=2");
@@ -78,4 +96,44 @@
         assertFalse(new URL("file", null, -1, "/a/").equals(new URL("file:/a/")));
         assertFalse(new URL("http", null, 80, "/a/").equals(new URL("http:/a/")));
     }
+
+    public void testUrlSerialization() throws Exception {
+        String s = "aced00057372000c6a6176612e6e65742e55524c962537361afce472030006490004706f72744c0"
+                + "009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c65710"
+                + "07e00014c0004686f737471007e00014c000870726f746f636f6c71007e00014c000372656671007"
+                + "e00017870ffffffff74000e757365723a7061737340686f73747400102f706174682f66696c653f7"
+                + "175657279740004686f7374740004687474707400046861736878";
+        URL url = new URL("http://user:pass@host/path/file?query#hash");
+        new SerializableTester<URL>(url, s).test();
+    }
+
+    /**
+     * The serialized form of a URL includes its hash code. But the hash code
+     * is not documented. Check that we don't return a deserialized hash code
+     * from a deserialized value.
+     */
+    public void testUrlSerializationWithHashCode() throws Exception {
+        String s = "aced00057372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436"
+                + "f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696"
+                + "e673b4c000466696c6571007e00014c0004686f737471007e00014c000870726f746f636f6c71007"
+                + "e00014c000372656671007e00017870cdf0efacffffffff74000e757365723a7061737340686f737"
+                + "47400102f706174682f66696c653f7175657279740004686f7374740004687474707400046861736"
+                + "878";
+        final URL url = new URL("http://user:pass@host/path/file?query#hash");
+        new SerializableTester<URL>(url, s) {
+            @Override protected void verify(URL deserialized) {
+                assertEquals(url.hashCode(), deserialized.hashCode());
+            }
+        }.test();
+    }
+
+    public void testOnlySupportedProtocols() {
+        try {
+            new URL("abcd://host");
+            fail();
+        } catch (MalformedURLException expected) {
+        }
+    }
+
+    // TODO: test resolve relative URL
 }
diff --git a/luni/src/test/java/libcore/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java
index 6f3f948..255db36 100644
--- a/luni/src/test/java/libcore/java/nio/BufferTest.java
+++ b/luni/src/test/java/libcore/java/nio/BufferTest.java
@@ -638,4 +638,20 @@
         } catch (BufferOverflowException expected) {
         }
     }
+
+    public void testCharBufferSubSequence() throws Exception {
+        ByteBuffer b = ByteBuffer.allocateDirect(10).order(ByteOrder.nativeOrder());
+        b.putChar('H');
+        b.putChar('e');
+        b.putChar('l');
+        b.putChar('l');
+        b.putChar('o');
+        b.flip();
+
+        assertEquals("Hello", b.asCharBuffer().toString());
+
+        CharBuffer cb = b.asCharBuffer();
+        CharSequence cs = cb.subSequence(0, cb.length());
+        assertEquals("Hello", cs.toString());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/nio/SelectorTest.java b/luni/src/test/java/libcore/java/nio/SelectorTest.java
deleted file mode 100644
index 9bc1b04..0000000
--- a/luni/src/test/java/libcore/java/nio/SelectorTest.java
+++ /dev/null
@@ -1,143 +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 libcore.java.nio;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.nio.channels.NoConnectionPendingException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import junit.framework.TestCase;
-import tests.support.Support_PortManager;
-
-public class SelectorTest extends TestCase {
-    private static final int WAIT_TIME = 100;
-    private static final int PORT = Support_PortManager.getNextPort();
-    private static final InetSocketAddress LOCAL_ADDRESS = new InetSocketAddress(
-            "127.0.0.1", PORT);
-
-    Selector selector;
-    ServerSocketChannel ssc;
-
-    protected void setUp() throws Exception {
-        super.setUp();
-        ssc = ServerSocketChannel.open();
-        ssc.configureBlocking(false);
-        ServerSocket ss = ssc.socket();
-        InetSocketAddress address = new InetSocketAddress(PORT);
-        ss.bind(address);
-        selector = Selector.open();
-    }
-
-    protected void tearDown() throws Exception {
-        try {
-            ssc.close();
-        } catch (Exception e) {
-            // do nothing
-        }
-        try {
-            selector.close();
-        } catch (Exception e) {
-            // do nothing
-        }
-        super.tearDown();
-    }
-
-    // http://code.google.com/p/android/issues/detail?id=4237
-    public void test_connectFinish_fails() throws Exception {
-        final SocketChannel channel = SocketChannel.open();
-        channel.configureBlocking(false);
-        channel.register(selector, SelectionKey.OP_CONNECT);
-        final Boolean[] fail = new Boolean[1];
-        new Thread() {
-            public void run() {
-                try {
-                    while (selector.isOpen()) {
-                        if (selector.select() != 0) {
-                            for (SelectionKey key : selector.selectedKeys()) {
-                                if (key.isValid() && key.isConnectable()) {
-                                    try {
-                                        channel.finishConnect();
-                                        synchronized (fail) {
-                                            fail[0] = Boolean.FALSE;
-                                            fail.notify();
-                                        }
-                                    }
-                                    catch (NoConnectionPendingException _) {
-                                        synchronized (fail) {
-                                            fail[0] = Boolean.TRUE;
-                                            fail.notify();
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } catch (Exception _) {}
-            }
-        }.start();
-        Thread.sleep(WAIT_TIME);
-        channel.connect(LOCAL_ADDRESS);
-        long time = System.currentTimeMillis();
-        synchronized (fail) {
-            while (System.currentTimeMillis() - time < WAIT_TIME || fail[0] == null) {
-                fail.wait(WAIT_TIME);
-            }
-        }
-        if (fail[0] == null) {
-            fail("test does not work");
-        } else if (fail[0].booleanValue()) {
-            fail();
-        }
-    }
-
-    // http://code.google.com/p/android/issues/detail?id=15388
-    public void testInterrupted() throws IOException {
-        Thread.currentThread().interrupt();
-        int count = selector.select();
-        assertEquals(0, count);
-    }
-
-    public void testManyWakeupCallsTriggerOnlyOneWakeup() throws Exception {
-        selector.wakeup();
-        selector.wakeup();
-        selector.wakeup();
-        selector.select();
-
-        // create a latch that will reach 0 when select returns
-        final CountDownLatch selectReturned = new CountDownLatch(1);
-        Thread thread = new Thread(new Runnable() {
-            @Override public void run() {
-                try {
-                    selector.select();
-                    selectReturned.countDown();
-                } catch (IOException ignored) {
-                }
-            }
-        });
-        thread.start();
-
-        // select doesn't ever return, so await() times out and returns false
-        assertFalse(selectReturned.await(500, TimeUnit.MILLISECONDS));
-    }
-}
-
diff --git a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java
index d982785..13757d2 100644
--- a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java
@@ -42,4 +42,17 @@
         } catch (IllegalArgumentException expected) {
         }
     }
+
+    // http://code.google.com/p/android/issues/detail?id=16579
+    public void testNonBlockingRecv() throws Exception {
+        DatagramChannel dc = DatagramChannel.open();
+        try {
+            dc.configureBlocking(false);
+            dc.socket().bind(null);
+            // Should return immediately, since we're non-blocking.
+            assertNull(dc.receive(ByteBuffer.allocate(2048)));
+        } finally {
+            dc.close();
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java
index e221675..415ae0a 100644
--- a/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java
@@ -92,6 +92,22 @@
         assertEquals(8, fc.write(buffers));
         fc.close();
         assertEquals(8, tmp.length());
-        assertEquals("abcdABCD", new String(IoUtils.readFileAsByteArray(tmp.getPath()), "US-ASCII"));
+        assertEquals("abcdABCD", new String(IoUtils.readFileAsString(tmp.getPath())));
+    }
+
+    public void test_append() throws Exception {
+        File tmp = File.createTempFile("FileChannelTest", "tmp");
+        FileOutputStream fos = new FileOutputStream(tmp, true);
+        FileChannel fc = fos.getChannel();
+
+        fc.write(ByteBuffer.wrap("hello".getBytes("US-ASCII")));
+        fc.position(0);
+        // The RI reports whatever position you set...
+        assertEquals(0, fc.position());
+        // ...but writes to the end of the file.
+        fc.write(ByteBuffer.wrap(" world".getBytes("US-ASCII")));
+        fos.close();
+
+        assertEquals("hello world", new String(IoUtils.readFileAsString(tmp.getPath())));
     }
 }
diff --git a/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java
index 9388888..7e2310b 100644
--- a/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java
@@ -156,11 +156,13 @@
 
         writeOnlyFileChannel.write(writeBuffer);
         writeOnlyFileChannel.force(false);
+        fis.close();
 
         readBuffer = new byte[CONTENT_AS_BYTES_LENGTH];
         fis = new FileInputStream(fileOfWriteOnlyFileChannel);
         fis.read(readBuffer);
         assertTrue(Arrays.equals(CONTENT_AS_BYTES, readBuffer));
+        fis.close();
     }
 
 
diff --git a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java
index 96cec03..aa958ad 100644
--- a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java
@@ -16,10 +16,98 @@
 package libcore.java.nio.channels;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.channels.NoConnectionPendingException;
+import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import junit.framework.TestCase;
+import tests.net.StuckServer;
 
 public class SelectorTest extends TestCase {
+    public void testNonBlockingConnect_immediate() throws Exception {
+        // Test the case where we [probably] connect immediately.
+        Selector selector = Selector.open();
+        ServerSocketChannel ssc = ServerSocketChannel.open();
+        try {
+            ssc.configureBlocking(false);
+            ssc.socket().bind(null);
+
+            SocketChannel sc = SocketChannel.open();
+            sc.configureBlocking(false);
+            sc.connect(ssc.socket().getLocalSocketAddress());
+            SelectionKey key = sc.register(selector, SelectionKey.OP_CONNECT);
+            assertEquals(1, selector.select());
+            assertEquals(SelectionKey.OP_CONNECT, key.readyOps());
+            sc.finishConnect();
+        } finally {
+            selector.close();
+            ssc.close();
+        }
+    }
+
+    public void testNonBlockingConnect_slow() throws Exception {
+        // Test the case where we have to wait for the connection.
+        Selector selector = Selector.open();
+        StuckServer ss = new StuckServer();
+        try {
+            SocketChannel sc = SocketChannel.open();
+            sc.configureBlocking(false);
+            ss.unblockAfterMs(2000);
+            sc.connect(ss.getLocalSocketAddress());
+            SelectionKey key = sc.register(selector, SelectionKey.OP_CONNECT);
+            assertEquals(1, selector.select());
+            assertEquals(SelectionKey.OP_CONNECT, key.readyOps());
+            sc.finishConnect();
+        } finally {
+            selector.close();
+            ss.close();
+        }
+    }
+
+    // http://code.google.com/p/android/issues/detail?id=15388
+    public void testInterrupted() throws IOException {
+        Selector selector = Selector.open();
+        try {
+            Thread.currentThread().interrupt();
+            int count = selector.select();
+            assertEquals(0, count);
+        } finally {
+            selector.close();
+        }
+    }
+
+    public void testManyWakeupCallsTriggerOnlyOneWakeup() throws Exception {
+        final Selector selector = Selector.open();
+        try {
+            selector.wakeup();
+            selector.wakeup();
+            selector.wakeup();
+            selector.select();
+
+            // create a latch that will reach 0 when select returns
+            final CountDownLatch selectReturned = new CountDownLatch(1);
+            Thread thread = new Thread(new Runnable() {
+                @Override public void run() {
+                    try {
+                        selector.select();
+                        selectReturned.countDown();
+                    } catch (IOException ignored) {
+                    }
+                }
+            });
+            thread.start();
+
+            // select doesn't ever return, so await() times out and returns false
+            assertFalse(selectReturned.await(500, TimeUnit.MILLISECONDS));
+        } finally {
+            selector.close();
+        }
+    }
 
     /**
      * We previously leaked a file descriptor for each selector instance created.
diff --git a/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java
new file mode 100644
index 0000000..e66096c
--- /dev/null
+++ b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.nio.channels;
+
+import java.nio.channels.ServerSocketChannel;
+
+public class ServerSocketChannelTest extends junit.framework.TestCase {
+    // http://code.google.com/p/android/issues/detail?id=16579
+    public void testNonBlockingAccept() throws Exception {
+        ServerSocketChannel ssc = ServerSocketChannel.open();
+        try {
+            ssc.configureBlocking(false);
+            ssc.socket().bind(null);
+            // Should return immediately, since we're non-blocking.
+            assertNull(ssc.accept());
+        } finally {
+            ssc.close();
+        }
+    }
+}
diff --git a/luni/src/test/java/libcore/java/util/EventObjectTest.java b/luni/src/test/java/libcore/java/util/EventObjectTest.java
new file mode 100644
index 0000000..a80e8c7
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/EventObjectTest.java
@@ -0,0 +1,55 @@
+/*
+ *  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.java.util;
+
+import java.util.EventObject;
+import junit.framework.TestCase;
+
+public final class EventObjectTest extends TestCase {
+
+    public void testConstructor() {
+        try {
+            new EventObject(null);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testGetSource() {
+        Object source = new Object();
+        assertSame(source, new EventObject(source).getSource());
+    }
+
+    public void testToString() {
+        assertEquals("java.util.EventObject[source=x]", new EventObject("x").toString());
+    }
+
+    public void testSerializationNullsOutSource() {
+        String s = "aced0005737200156a6176612e7574696c2e4576656e744f626a6563744"
+                + "c8d094e186d7da80200007870";
+        Object source = new Object();
+        EventObject eventObject = new EventObject(source);
+        new SerializableTester<EventObject>(eventObject, s) {
+            @Override protected boolean equals(EventObject a, EventObject b) {
+                return a.getSource() == null
+                        || b.getSource() == null
+                        || a.getSource() == b.getSource();
+            }
+        }.test();
+    }
+}
diff --git a/luni/src/test/java/libcore/java/util/SerializableTester.java b/luni/src/test/java/libcore/java/util/SerializableTester.java
index 20318d7..50b6435 100644
--- a/luni/src/test/java/libcore/java/util/SerializableTester.java
+++ b/luni/src/test/java/libcore/java/util/SerializableTester.java
@@ -78,20 +78,20 @@
         }
     }
 
-    private byte[] serialize(Object object) throws IOException {
+    private static byte[] serialize(Object object) throws IOException {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         new ObjectOutputStream(out).writeObject(object);
         return out.toByteArray();
     }
 
-    private Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
+    private static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
         ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
         Object result = in.readObject();
         assertEquals(-1, in.read());
         return result;
     }
 
-    private String hexEncode(byte[] bytes) {
+    private static String hexEncode(byte[] bytes) {
         StringBuilder result = new StringBuilder(bytes.length * 2);
         for (byte b : bytes) {
             result.append(String.format("%02x", b));
@@ -99,12 +99,19 @@
         return result.toString();
     }
 
-    private byte[] hexDecode(String s) {
+    private static byte[] hexDecode(String s) {
         byte[] result = new byte[s.length() / 2];
         for (int i = 0; i < result.length; i++) {
             result[i] = (byte) Integer.parseInt(s.substring(i*2, i*2 + 2), 16);
         }
         return result;
     }
-}
 
+    public static String serializeHex(Object object) throws IOException {
+        return hexEncode(serialize(object));
+    }
+
+    public static Object deserializeHex(String hex) throws IOException, ClassNotFoundException {
+        return deserialize(hexDecode(hex));
+    }
+}
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index dde2ec8..39ac19d 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -132,6 +132,16 @@
         }
     }
 
+    // http://code.google.com/p/android/issues/detail?id=16608
+    public void testHelsinkiOverflow() throws Exception {
+        long time = 2147483648000L;// Tue, 19 Jan 2038 03:14:08 GMT
+        TimeZone timeZone = TimeZone.getTimeZone("Europe/Helsinki");
+        long offset = timeZone.getOffset(time);
+        // This might cause us trouble if Europe/Helsinki changes its rules. See the bug for
+        // details of the intent of this test.
+        assertEquals(7200000, offset);
+    }
+
     // http://code.google.com/p/android/issues/detail?id=11918
     public void testHasSameRules() throws Exception {
         TimeZone denver = TimeZone.getTimeZone ("America/Denver") ;
diff --git a/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java b/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java
similarity index 67%
rename from luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java
rename to luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java
index ca8c790..5981b0b 100644
--- a/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java
+++ b/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java
@@ -14,17 +14,22 @@
  * limitations under the License.
  */
 
-package libcore.java.net;
+package libcore.net.http;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.lang.reflect.InvocationHandler;
 import java.net.CacheRequest;
 import java.net.CacheResponse;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.HttpCookie;
 import java.net.HttpURLConnection;
 import java.net.ResponseCache;
 import java.net.SecureCacheResponse;
@@ -40,12 +45,13 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Deque;
 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.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -53,24 +59,35 @@
 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;
+import tests.io.MockOs;
 
 public final class HttpResponseCacheTest extends TestCase {
     private MockWebServer server = new MockWebServer();
-    private HttpResponseCache cache = new HttpResponseCache();
+    private HttpResponseCache cache;
+    private final MockOs mockOs = new MockOs();
+    private final CookieManager cookieManager = new CookieManager();
 
     @Override protected void setUp() throws Exception {
         super.setUp();
+
+        String tmp = System.getProperty("java.io.tmpdir");
+        File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
+        cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
         ResponseCache.setDefault(cache);
+        mockOs.install();
+        CookieHandler.setDefault(cookieManager);
     }
 
     @Override protected void tearDown() throws Exception {
-        ResponseCache.setDefault(null);
+        mockOs.uninstall();
         server.shutdown();
+        ResponseCache.setDefault(null);
+        cache.getCache().delete();
+        CookieHandler.setDefault(null);
         super.tearDown();
     }
 
@@ -153,11 +170,14 @@
         // 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());
+        CacheResponse cached = cache.get(url.toURI(), "GET",
+                Collections.<String, List<String>>emptyMap());
+        if (shouldPut) {
+            assertNotNull(Integer.toString(responseCode), cached);
+            cached.getBody().close();
+        } else {
+            assertNull(Integer.toString(responseCode), cached);
+        }
         server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
     }
 
@@ -249,8 +269,8 @@
         assertEquals("spiders", readAscii(urlConnection, "spiders".length()));
         assertEquals(-1, in.read());
         in.close();
-        assertEquals(1, cache.getSuccessCount());
-        assertEquals(0, cache.getAbortCount());
+        assertEquals(1, cache.getWriteSuccessCount());
+        assertEquals(0, cache.getWriteAbortCount());
 
         urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
         in = urlConnection.getInputStream();
@@ -260,10 +280,11 @@
         assertEquals("Fantastic", urlConnection.getResponseMessage());
 
         assertEquals(-1, in.read());
-        assertEquals(1, cache.getMissCount());
+        in.close();
+        assertEquals(1, cache.getWriteSuccessCount());
+        assertEquals(0, cache.getWriteAbortCount());
+        assertEquals(2, cache.getRequestCount());
         assertEquals(1, cache.getHitCount());
-        assertEquals(1, cache.getSuccessCount());
-        assertEquals(0, cache.getAbortCount());
     }
 
     public void testSecureResponseCaching() throws IOException {
@@ -290,7 +311,8 @@
         connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
         assertEquals("ABC", readAscii(connection));
 
-        assertEquals(1, cache.getMissCount());
+        assertEquals(2, cache.getRequestCount());
+        assertEquals(1, cache.getNetworkCount());
         assertEquals(1, cache.getHitCount());
 
         assertEquals(suite, connection.getCipherSuite());
@@ -318,7 +340,7 @@
         assertEquals("DEF", readAscii(connection));
     }
 
-    public void testResponseCachingAndRedirects() throws IOException {
+    public void testResponseCachingAndRedirects() throws Exception {
         server.enqueue(new MockResponse()
                 .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
                 .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
@@ -337,10 +359,38 @@
         connection = server.getUrl("/").openConnection(); // cached!
         assertEquals("ABC", readAscii(connection));
 
-        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
+        assertEquals(4, cache.getRequestCount()); // 2 requests + 2 redirects
+        assertEquals(2, cache.getNetworkCount());
         assertEquals(2, cache.getHitCount());
     }
 
+    public void testRedirectToCachedResult() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("ABC"));
+        server.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
+                .addHeader("Location: /foo"));
+        server.enqueue(new MockResponse().setBody("DEF"));
+        server.play();
+
+        assertEquals("ABC", readAscii(server.getUrl("/foo").openConnection()));
+        RecordedRequest request1 = server.takeRequest();
+        assertEquals("GET /foo HTTP/1.1", request1.getRequestLine());
+        assertEquals(0, request1.getSequenceNumber());
+
+        assertEquals("ABC", readAscii(server.getUrl("/bar").openConnection()));
+        RecordedRequest request2 = server.takeRequest();
+        assertEquals("GET /bar HTTP/1.1", request2.getRequestLine());
+        assertEquals(1, request2.getSequenceNumber());
+
+        // an unrelated request should reuse the pooled connection
+        assertEquals("DEF", readAscii(server.getUrl("/baz").openConnection()));
+        RecordedRequest request3 = server.takeRequest();
+        assertEquals("GET /baz HTTP/1.1", request3.getRequestLine());
+        assertEquals(2, request3.getSequenceNumber());
+    }
+
     public void testSecureResponseCachingAndRedirects() throws IOException {
         TestSSLContext testSSLContext = TestSSLContext.create();
         server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
@@ -364,7 +414,7 @@
         connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
         assertEquals("ABC", readAscii(connection));
 
-        assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
+        assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4
         assertEquals(2, cache.getHitCount());
     }
 
@@ -423,14 +473,16 @@
             reader.readLine();
             fail("This implementation silently ignored a truncated HTTP body.");
         } catch (IOException expected) {
+        } finally {
+            reader.close();
         }
 
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(0, cache.getSuccessCount());
+        assertEquals(1, cache.getWriteAbortCount());
+        assertEquals(0, cache.getWriteSuccessCount());
         URLConnection connection = server.getUrl("/").openConnection();
         assertEquals("Request #2", readAscii(connection));
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(1, cache.getSuccessCount());
+        assertEquals(1, cache.getWriteAbortCount());
+        assertEquals(1, cache.getWriteSuccessCount());
     }
 
     public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
@@ -462,12 +514,12 @@
         } catch (IOException expected) {
         }
 
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(0, cache.getSuccessCount());
+        assertEquals(1, cache.getWriteAbortCount());
+        assertEquals(0, cache.getWriteSuccessCount());
         connection = server.getUrl("/").openConnection();
         assertEquals("Request #2", readAscii(connection));
-        assertEquals(1, cache.getAbortCount());
-        assertEquals(1, cache.getSuccessCount());
+        assertEquals(1, cache.getWriteAbortCount());
+        assertEquals(1, cache.getWriteSuccessCount());
     }
 
     public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
@@ -881,6 +933,20 @@
                 connection.getHeaderField("Warning"));
     }
 
+    public void testRequestMaxStaleNotHonoredWithMustRevalidate() throws IOException {
+        server.enqueue(new MockResponse().setBody("A")
+                .addHeader("Cache-Control: max-age=120, must-revalidate")
+                .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("B", readAscii(connection));
+    }
+
     public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
         // (no responses enqueued)
         server.play();
@@ -999,6 +1065,19 @@
         return server.takeRequest();
     }
 
+    public void testSetIfModifiedSince() throws Exception {
+        Date since = new Date();
+        server.enqueue(new MockResponse().setBody("A"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        URLConnection connection = url.openConnection();
+        connection.setIfModifiedSince(since.getTime());
+        assertEquals("A", readAscii(connection));
+        RecordedRequest request = server.takeRequest();
+        assertTrue(request.getHeaders().contains("If-Modified-Since: " + formatDate(since)));
+    }
+
     public void testClientSuppliedConditionWithoutCachedResult() throws Exception {
         server.enqueue(new MockResponse()
                 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
@@ -1055,28 +1134,6 @@
         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")
@@ -1089,13 +1146,483 @@
         assertEquals("B", readAscii(server.getUrl("/bar").openConnection()));
     }
 
+    public void testUseCachesFalseDoesNotWriteToCache() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A").setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URLConnection connection = server.getUrl("/").openConnection();
+        connection.setUseCaches(false);
+        assertEquals("A", readAscii(connection));
+        assertEquals("B", readAscii(server.getUrl("/").openConnection()));
+    }
+
+    public void testUseCachesFalseDoesNotReadFromCache() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A").setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        URLConnection connection = server.getUrl("/").openConnection();
+        connection.setUseCaches(false);
+        assertEquals("B", readAscii(connection));
+    }
+
+    public void testDefaultUseCachesSetsInitialValueOnly() throws Exception {
+        URL url = new URL("http://localhost/");
+        URLConnection c1 = url.openConnection();
+        URLConnection c2 = url.openConnection();
+        assertTrue(c1.getDefaultUseCaches());
+        c1.setDefaultUseCaches(false);
+        try {
+            assertTrue(c1.getUseCaches());
+            assertTrue(c2.getUseCaches());
+            URLConnection c3 = url.openConnection();
+            assertFalse(c3.getUseCaches());
+        } finally {
+            c1.setDefaultUseCaches(true);
+        }
+    }
+
+    public void testConnectionIsReturnedToPoolAfterConditionalSuccess() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/a").openConnection()));
+        assertEquals("A", readAscii(server.getUrl("/a").openConnection()));
+        assertEquals("B", readAscii(server.getUrl("/b").openConnection()));
+
+        assertEquals(0, server.takeRequest().getSequenceNumber());
+        assertEquals(1, server.takeRequest().getSequenceNumber());
+        assertEquals(2, server.takeRequest().getSequenceNumber());
+    }
+
+    public void testStatisticsConditionalCacheMiss() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.enqueue(new MockResponse().setBody("C"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals(1, cache.getRequestCount());
+        assertEquals(1, cache.getNetworkCount());
+        assertEquals(0, cache.getHitCount());
+        assertEquals("B", readAscii(server.getUrl("/").openConnection()));
+        assertEquals("C", readAscii(server.getUrl("/").openConnection()));
+        assertEquals(3, cache.getRequestCount());
+        assertEquals(3, cache.getNetworkCount());
+        assertEquals(0, cache.getHitCount());
+    }
+
+    public void testStatisticsConditionalCacheHit() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals(1, cache.getRequestCount());
+        assertEquals(1, cache.getNetworkCount());
+        assertEquals(0, cache.getHitCount());
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals(3, cache.getRequestCount());
+        assertEquals(3, cache.getNetworkCount());
+        assertEquals(2, cache.getHitCount());
+    }
+
+    public void testStatisticsFullCacheHit() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals(1, cache.getRequestCount());
+        assertEquals(1, cache.getNetworkCount());
+        assertEquals(0, cache.getHitCount());
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals(3, cache.getRequestCount());
+        assertEquals(1, cache.getNetworkCount());
+        assertEquals(2, cache.getHitCount());
+    }
+
+    public void testVaryMatchesChangedRequestHeaderField() 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("/");
+        HttpURLConnection frConnection = (HttpURLConnection) url.openConnection();
+        frConnection.addRequestProperty("Accept-Language", "fr-CA");
+        assertEquals("A", readAscii(frConnection));
+
+        HttpURLConnection enConnection = (HttpURLConnection) url.openConnection();
+        enConnection.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("B", readAscii(enConnection));
+    }
+
+    public void testVaryMatchesUnchangedRequestHeaderField() 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("A", readAscii(connection2));
+    }
+
+    public void testVaryMatchesAbsentRequestHeaderField() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Foo")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+    }
+
+    public void testVaryMatchesAddedRequestHeaderField() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Foo")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        URLConnection fooConnection = server.getUrl("/").openConnection();
+        fooConnection.addRequestProperty("Foo", "bar");
+        assertEquals("B", readAscii(fooConnection));
+    }
+
+    public void testVaryMatchesRemovedRequestHeaderField() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Foo")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URLConnection fooConnection = server.getUrl("/").openConnection();
+        fooConnection.addRequestProperty("Foo", "bar");
+        assertEquals("A", readAscii(fooConnection));
+        assertEquals("B", readAscii(server.getUrl("/").openConnection()));
+    }
+
+    public void testVaryFieldsAreCaseInsensitive() 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("A", readAscii(connection2));
+    }
+
+    public void testVaryMultipleFieldsWithMatch() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Accept-Language, Accept-Charset")
+                .addHeader("Vary: Accept-Encoding")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        URLConnection connection1 = url.openConnection();
+        connection1.addRequestProperty("Accept-Language", "fr-CA");
+        connection1.addRequestProperty("Accept-Charset", "UTF-8");
+        connection1.addRequestProperty("Accept-Encoding", "identity");
+        assertEquals("A", readAscii(connection1));
+        URLConnection connection2 = url.openConnection();
+        connection2.addRequestProperty("Accept-Language", "fr-CA");
+        connection2.addRequestProperty("Accept-Charset", "UTF-8");
+        connection2.addRequestProperty("Accept-Encoding", "identity");
+        assertEquals("A", readAscii(connection2));
+    }
+
+    public void testVaryMultipleFieldsWithNoMatch() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: Accept-Language, Accept-Charset")
+                .addHeader("Vary: Accept-Encoding")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        URLConnection frConnection = url.openConnection();
+        frConnection.addRequestProperty("Accept-Language", "fr-CA");
+        frConnection.addRequestProperty("Accept-Charset", "UTF-8");
+        frConnection.addRequestProperty("Accept-Encoding", "identity");
+        assertEquals("A", readAscii(frConnection));
+        URLConnection enConnection = url.openConnection();
+        enConnection.addRequestProperty("Accept-Language", "en-CA");
+        enConnection.addRequestProperty("Accept-Charset", "UTF-8");
+        enConnection.addRequestProperty("Accept-Encoding", "identity");
+        assertEquals("B", readAscii(enConnection));
+    }
+
+    public void testVaryMultipleFieldValuesWithMatch() 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, fr-FR");
+        connection1.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection1));
+
+        URLConnection connection2 = url.openConnection();
+        connection2.addRequestProperty("Accept-Language", "fr-CA, fr-FR");
+        connection2.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection2));
+    }
+
+    public void testVaryMultipleFieldValuesWithNoMatch() 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, fr-FR");
+        connection1.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection1));
+
+        URLConnection connection2 = url.openConnection();
+        connection2.addRequestProperty("Accept-Language", "fr-CA");
+        connection2.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("B", readAscii(connection2));
+    }
+
+    public void testVaryAsterisk() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .addHeader("Vary: *")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        assertEquals("A", readAscii(server.getUrl("/").openConnection()));
+        assertEquals("B", readAscii(server.getUrl("/").openConnection()));
+    }
+
+    public void testVaryAndHttps() throws Exception {
+        TestSSLContext testSSLContext = TestSSLContext.create();
+        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
+        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("/");
+        HttpsURLConnection connection1 = (HttpsURLConnection) url.openConnection();
+        connection1.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        connection1.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection1));
+
+        HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection();
+        connection2.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        connection2.addRequestProperty("Accept-Language", "en-US");
+        assertEquals("A", readAscii(connection2));
+    }
+
+    public void testDiskWriteFailureCacheDegradation() throws Exception {
+        Deque<InvocationHandler> writeHandlers = mockOs.getHandlers("write");
+        int i = 0;
+        boolean hasMoreScenarios = true;
+        while (hasMoreScenarios) {
+            mockOs.enqueueNormal("write", i++);
+            mockOs.enqueueFault("write");
+            exercisePossiblyFaultyCache(false);
+            hasMoreScenarios = writeHandlers.isEmpty();
+            writeHandlers.clear();
+        }
+        System.out.println("Exercising the cache performs " + (i - 1) + " writes.");
+    }
+
+    public void testDiskReadFailureCacheDegradation() throws Exception {
+        Deque<InvocationHandler> readHandlers = mockOs.getHandlers("read");
+        int i = 0;
+        boolean hasMoreScenarios = true;
+        while (hasMoreScenarios) {
+            mockOs.enqueueNormal("read", i++);
+            mockOs.enqueueFault("read");
+            exercisePossiblyFaultyCache(true);
+            hasMoreScenarios = readHandlers.isEmpty();
+            readHandlers.clear();
+        }
+        System.out.println("Exercising the cache performs " + (i - 1) + " reads.");
+    }
+
+    public void testCachePlusCookies() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Set-Cookie: a=FIRST; domain=.local;")
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse()
+                .addHeader("Set-Cookie: a=SECOND; domain=.local;")
+                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        URL url = server.getUrl("/");
+        assertEquals("A", readAscii(url.openConnection()));
+        assertCookies(url, "a=FIRST");
+        assertEquals("A", readAscii(url.openConnection()));
+        assertCookies(url, "a=SECOND");
+    }
+
+    public void testGetHeadersReturnsNetworkEndToEndHeaders() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Allow: GET, HEAD")
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse()
+                .addHeader("Allow: GET, HEAD, PUT")
+                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        URLConnection connection1 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection1));
+        assertEquals("GET, HEAD", connection1.getHeaderField("Allow"));
+
+        URLConnection connection2 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection2));
+        assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow"));
+    }
+
+    public void testGetHeadersReturnsCachedHopByHopHeaders() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Transfer-Encoding: identity")
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse()
+                .addHeader("Transfer-Encoding: none")
+                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        URLConnection connection1 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection1));
+        assertEquals("identity", connection1.getHeaderField("Transfer-Encoding"));
+
+        URLConnection connection2 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection2));
+        assertEquals("identity", connection2.getHeaderField("Transfer-Encoding"));
+    }
+
+    public void testGetHeadersDeletesCached100LevelWarnings() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Warning: 199 test danger")
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        URLConnection connection1 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection1));
+        assertEquals("199 test danger", connection1.getHeaderField("Warning"));
+
+        URLConnection connection2 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection2));
+        assertEquals(null, connection2.getHeaderField("Warning"));
+    }
+
+    public void testGetHeadersRetainsCached200LevelWarnings() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("Warning: 299 test danger")
+                .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+                .addHeader("Cache-Control: max-age=0")
+                .setBody("A"));
+        server.enqueue(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+        server.play();
+
+        URLConnection connection1 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection1));
+        assertEquals("299 test danger", connection1.getHeaderField("Warning"));
+
+        URLConnection connection2 = server.getUrl("/").openConnection();
+        assertEquals("A", readAscii(connection2));
+        assertEquals("299 test danger", connection2.getHeaderField("Warning"));
+    }
+
+    public void assertCookies(URL url, String... expectedCookies) throws Exception {
+        List<String> actualCookies = new ArrayList<String>();
+        for (HttpCookie cookie : cookieManager.getCookieStore().get(url.toURI())) {
+            actualCookies.add(cookie.toString());
+        }
+        assertEquals(Arrays.asList(expectedCookies), actualCookies);
+    }
+
+    public void testCachePlusRange() throws Exception {
+        assertNotCached(new MockResponse()
+                .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
+                .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
+                .addHeader("Content-Range: bytes 100-100/200")
+                .addHeader("Cache-Control: max-age=60"));
+    }
+
     /**
      * @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));
+        return formatDate(new Date(System.currentTimeMillis() + timeUnit.toMillis(delta)));
+    }
+
+    private String formatDate(Date date) {
         DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
         rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
         return rfc1123.format(date);
@@ -1121,6 +1648,31 @@
         assertEquals("B", readAscii(url.openConnection()));
     }
 
+    private void exercisePossiblyFaultyCache(boolean permitReadBodyFailures) throws Exception {
+        server.shutdown();
+        server = new MockWebServer();
+        server.enqueue(new MockResponse()
+                .addHeader("Cache-Control: max-age=60")
+                .setBody("A"));
+        server.enqueue(new MockResponse().setBody("B"));
+        server.play();
+
+        URL url = server.getUrl("/" + UUID.randomUUID());
+        assertEquals("A", readAscii(url.openConnection()));
+
+        URLConnection connection = url.openConnection();
+        InputStream in = connection.getInputStream();
+        try {
+            int bodyChar = in.read();
+            assertTrue(bodyChar == 'A' || bodyChar == 'B');
+            assertEquals(-1, in.read());
+        } catch (IOException e) {
+            if (!permitReadBodyFailures) {
+                throw e;
+            }
+        }
+    }
+
     /**
      * @return the request with the conditional get headers.
      */
@@ -1261,16 +1813,14 @@
         return bytesOut.toByteArray();
     }
 
-    private static class InsecureResponseCache extends ResponseCache {
-        private final HttpResponseCache delegate = new HttpResponseCache();
-
+    private class InsecureResponseCache extends ResponseCache {
         @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
-            return delegate.put(uri, connection);
+            return cache.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);
+            final CacheResponse response = cache.get(uri, requestMethod, requestHeaders);
             if (response instanceof SecureCacheResponse) {
                 return new CacheResponse() {
                     @Override public InputStream getBody() throws IOException {
diff --git a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
index 0f38a5d..ae684c3 100644
--- a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
+++ b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
@@ -57,16 +57,15 @@
 
     public void testQuotedFieldName() {
         RawHeaders headers = new RawHeaders();
-        headers.add("Cache-Control", "private=\"Set-Cookie\"");
+        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 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);
     }
 
@@ -74,7 +73,6 @@
         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);
     }
@@ -83,7 +81,6 @@
         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);
     }
 
@@ -92,14 +89,12 @@
         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() {
@@ -145,7 +140,6 @@
         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);
@@ -153,7 +147,6 @@
         assertEquals(60, parsedHeaders.maxAgeSeconds);
         assertEquals(70, parsedHeaders.sMaxAgeSeconds);
         assertTrue(parsedHeaders.isPublic);
-        assertEquals("a", parsedHeaders.privateField);
         assertTrue(parsedHeaders.mustRevalidate);
     }
 
diff --git a/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java b/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java
index 2db8e82..84b7942 100644
--- a/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java
+++ b/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java
@@ -16,6 +16,16 @@
 
 package libcore.xml;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import junit.framework.Assert;
 import junit.framework.TestCase;
 import org.apache.harmony.xml.ExpatReader;
@@ -27,20 +37,8 @@
 import org.xml.sax.XMLReader;
 import org.xml.sax.ext.DefaultHandler2;
 import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.net.ServerSocket;
-import java.net.Socket;
+import tests.http.MockResponse;
+import tests.http.MockWebServer;
 
 public class ExpatSaxParserTest extends TestCase {
 
@@ -586,98 +584,44 @@
     }
 
     public void testExternalEntityDownload() throws IOException, SAXException {
-        class Server implements Runnable {
-
-            private final ServerSocket serverSocket;
-
-            Server() throws IOException {
-                serverSocket = new ServerSocket(8080);
-            }
-
-            public void run() {
-                try {
-                    Socket socket = serverSocket.accept();
-
-                    final InputStream in = socket.getInputStream();
-                    Thread inputThread = new Thread() {
-                        public void run() {
-                            try {
-                                byte[] buffer = new byte[1024];
-                                while (in.read(buffer) > -1) { /* ignore */ }
-                            } catch (IOException e) {
-                                e.printStackTrace();
-                            }
-                        }
-                    };
-                    inputThread.setDaemon(true);
-                    inputThread.start();
-
-                    OutputStream out = socket.getOutputStream();
-
-                    String body = "<bar></bar>";
-                    String response = "HTTP/1.0 200 OK\n"
-                        + "Content-Length: " + body.length() + "\n"
-                        + "\n"
-                        + body;
-
-                    out.write(response.getBytes("UTF-8"));
-                    out.close();
-                    serverSocket.close();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
-        }
+        final MockWebServer server = new MockWebServer();
+        server.enqueue(new MockResponse().setBody("<bar></bar>"));
+        server.play();
 
         class Handler extends DefaultHandler {
+            final List<String> elementNames = new ArrayList<String>();
 
-            List<String> elementNames = new ArrayList<String>();
-
-            public InputSource resolveEntity(String publicId, String systemId)
-                    throws IOException, SAXException {
+            @Override public InputSource resolveEntity(String publicId, String systemId)
+                    throws IOException {
                 // The parser should have resolved the systemId.
-                assertEquals("http://localhost:8080/systemBar", systemId);
+                assertEquals(server.getUrl("/systemBar").toString(), systemId);
                 return new InputSource(systemId);
             }
 
-            @Override
-            public void startElement(String uri, String localName, String qName,
-                    Attributes attributes) throws SAXException {
+            @Override public void startElement(String uri, String localName, String qName,
+                    Attributes attributes) {
                 elementNames.add(localName);
             }
 
-            @Override
-            public void endElement(String uri, String localName, String qName)
-                    throws SAXException {
+            @Override public void endElement(String uri, String localName, String qName) {
                 elementNames.add("/" + localName);
             }
         }
 
-        // Start server to serve up the XML for 'systemBar'.
-        Thread serverThread = new Thread(new Server());
-        serverThread.setDaemon(true);
-        serverThread.start();
-
         // 'systemBar', the external entity, is relative to 'systemFoo':
         Reader in = new StringReader("<?xml version=\"1.0\"?>\n"
             + "<!DOCTYPE foo [\n"
             + "  <!ENTITY bar SYSTEM 'systemBar'>\n"
             + "]>\n"
             + "<foo>&bar;</foo>");
-
         ExpatReader reader = new ExpatReader();
-
         Handler handler = new Handler();
-
         reader.setContentHandler(handler);
         reader.setEntityResolver(handler);
-
         InputSource source = new InputSource(in);
-        source.setSystemId("http://localhost:8080/systemFoo");
+        source.setSystemId(server.getUrl("/systemFoo").toString());
         reader.parse(source);
-
-        assertEquals(Arrays.asList("foo", "bar", "/bar", "/foo"),
-                handler.elementNames);
+        assertEquals(Arrays.asList("foo", "bar", "/bar", "/foo"), handler.elementNames);
     }
 
     /**
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
index 216af26..c516f67 100644
--- a/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
+++ b/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java
@@ -973,7 +973,7 @@
                         log("Authentication required...");
                         // send Authentication Request
                         os.write(respAuthenticationRequired.getBytes());
-                        // read response
+                        // read request
                         num = is.read(buff);
                         if (num == -1) {
                             // this connection was closed,
@@ -990,8 +990,8 @@
                         log("Got authenticated request:\n" + message);
                         log("------------------");
                         // check provided authorization credentials
-                        assertTrue("Received message does not contain authorization credentials",
-                                   message.toLowerCase().indexOf("proxy-authorization:") > 0);
+                        assertTrue("no proxy-authorization credentials: " + message,
+                                   message.toLowerCase().indexOf("proxy-authorization:") != -1);
                     }
 
                     assertTrue(message.startsWith("CONNECT"));
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java
index 5284749..534cb86 100644
--- a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java
+++ b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java
@@ -360,8 +360,8 @@
 
     public void test_hashCode() {
         int hashCode = getHashCode(Support_Configuration.InetTestIP);
-        int ip6HashCode = getHashCode(Support_Configuration.InetTestIP6);
-        int ip6LOHashCode = getHashCode(Support_Configuration.InetTestIP6LO);
+        int ip6HashCode = getHashCode("fe80::20d:60ff:fe24:7410");
+        int ip6LOHashCode = getHashCode("::1");
         assertFalse("Hash collision", hashCode == ip6HashCode);
         assertFalse("Hash collision", ip6HashCode == ip6LOHashCode);
         assertFalse("Hash collision", hashCode == ip6LOHashCode);
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java
index dd6a0f0..f2d64cd 100644
--- a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java
@@ -71,24 +71,8 @@
 
     private JarURLConnection jarURLCon;
 
-    private URL jarURL;
-
     private URLConnection gifURLCon;
 
-    private URL gifURL;
-
-    public boolean isGetCalled;
-
-    public boolean isPutCalled;
-
-    private Map<String, List<String>> mockHeaderMap;
-
-    private InputStream mockIs = new MockInputStream();
-
-    public boolean isCacheWriteCalled;
-
-    public boolean isAbortCalled;
-
     /**
      * {@link java.net.URLConnection#addRequestProperty(String, String)}
      */
@@ -220,107 +204,8 @@
         }
     }
 
-    class MockCachedResponseCache extends ResponseCache {
-
-        public CacheResponse get(URI arg0, String arg1, Map arg2)
-                throws IOException {
-            if (null == arg0 || null == arg1 || null == arg2) {
-                throw new NullPointerException();
-            }
-            isGetCalled = true;
-            return new MockCacheResponse();
-        }
-
-        public CacheRequest put(URI arg0, URLConnection arg1)
-                throws IOException {
-            if (null == arg0 || null == arg1) {
-                throw new NullPointerException();
-            }
-            isPutCalled = true;
-            return new MockCacheRequest();
-        }
-    }
-
-    class MockNonCachedResponseCache extends ResponseCache {
-
-        public CacheResponse get(URI arg0, String arg1, Map arg2)
-                throws IOException {
-            isGetCalled = true;
-            return null;
-        }
-
-        public CacheRequest put(URI arg0, URLConnection arg1)
-                throws IOException {
-            isPutCalled = true;
-            return new MockCacheRequest();
-        }
-    }
-
-    class MockCacheRequest extends CacheRequest {
-
-        public OutputStream getBody() throws IOException {
-            isCacheWriteCalled = true;
-            return new MockOutputStream();
-        }
-
-        public void abort() {
-            isAbortCalled = true;
-        }
-
-    }
-
-    class MockInputStream extends InputStream {
-
-        public int read() throws IOException {
-            return 4711;
-        }
-
-        public int read(byte[] arg0, int arg1, int arg2) throws IOException {
-            return 1;
-        }
-
-        public int read(byte[] arg0) throws IOException {
-            return 1;
-        }
-
-    }
-
-    class MockOutputStream extends OutputStream {
-
-        public void write(int b) throws IOException {
-            isCacheWriteCalled = true;
-        }
-
-        public void write(byte[] b, int off, int len) throws IOException {
-            isCacheWriteCalled = true;
-        }
-
-        public void write(byte[] b) throws IOException {
-            isCacheWriteCalled = true;
-        }
-    }
-
-    class MockCacheResponse extends CacheResponse {
-
-        public Map<String, List<String>> getHeaders() throws IOException {
-            return mockHeaderMap;
-        }
-
-        public InputStream getBody() throws IOException {
-            return mockIs;
-        }
-    }
-
-
     private static int port;
 
-    static String getContentType(String fileName) throws IOException {
-        String resourceName = "org/apache/harmony/luni/tests/" + fileName;
-        URL url = ClassLoader.getSystemClassLoader().getResource(resourceName);
-        assertNotNull("Cannot find test resource " + resourceName, url);
-        return url.openConnection().getContentType();
-    }
-
     URL url;
 
     URL url2;
@@ -349,10 +234,7 @@
         fileURLCon = fileURL.openConnection();
 
         jarURLCon = openJarURLConnection();
-        jarURL = jarURLCon.getURL();
-
         gifURLCon = openGifURLConnection();
-        gifURL = gifURLCon.getURL();
     }
 
     @Override
@@ -679,7 +561,7 @@
     public void test_getDate() {
         // should be greater than 930000000000L which represents the past
         assertTrue("getDate gave wrong date: " + uc.getDate(),
-                    uc.getDate() > 930000000000L);
+                uc.getDate() > 930000000000L);
     }
 
     /**
@@ -722,65 +604,6 @@
 
     /**
      * @throws IOException
-     * {@link  java.net.URLConnection#getDefaultUseCaches()}
-     */
-    public void test_getDefaultUseCaches_CachedRC() throws IOException {
-        boolean oldSetting = uc.getDefaultUseCaches();
-
-        ResponseCache old = ResponseCache.getDefault();
-        ResponseCache rc = new MockCachedResponseCache();
-        ResponseCache.setDefault(rc);
-
-        // Recreate the connection so that we get the cache from ResponseCache.
-        uc2 = url2.openConnection();
-
-        uc2.setUseCaches(true);
-
-        uc.setDefaultUseCaches(false);
-
-        // uc unaffected
-        assertTrue(uc.getUseCaches());
-        // uc2 unaffected
-        assertTrue(uc2.getUseCaches());
-
-        //test get
-        assertFalse("getDefaultUseCaches should have returned false", uc
-                .getDefaultUseCaches());
-
-        // subsequent connections should have default value
-        URL url3 =  new URL("http://localhost:" + port + "/test2");
-        URLConnection uc3 = url3.openConnection();
-        assertFalse(uc3.getUseCaches());
-
-        // test if uc does not cache but uc2 does
-        isGetCalled = false;
-        isPutCalled = false;
-
-        // test uc
-        uc.setDoOutput(true);
-        assertFalse(isGetCalled);
-        uc.connect();
-        assertFalse(isGetCalled);
-        assertFalse(isPutCalled);
-        OutputStream os = uc.getOutputStream();
-        assertFalse(isPutCalled);
-        assertFalse(isGetCalled);
-
-        os.close();
-
-        //uc2 should be unaffected
-        uc2.setDoOutput(true);
-        assertFalse(isGetCalled);
-        uc2.connect();
-        assertTrue(isGetCalled);
-        assertFalse(isPutCalled);
-
-        uc.setDefaultUseCaches(oldSetting);
-        ResponseCache.setDefault(null);
-    }
-
-    /**
-     * @throws IOException
      * {@link java.net.URLConnection#getDoInput()}
      */
     public void test_getDoInput() throws IOException {
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/util/Base64Test.java b/luni/src/test/java/org/apache/harmony/luni/tests/util/Base64Test.java
deleted file mode 100644
index 70318ed..0000000
--- a/luni/src/test/java/org/apache/harmony/luni/tests/util/Base64Test.java
+++ /dev/null
@@ -1,45 +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 org.apache.harmony.luni.tests.util;
-
-import org.apache.harmony.luni.util.Base64;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Base64 encoder/decoder test.
- */
-public class Base64Test extends TestCase {
-
-    /**
-     * Checks the result on empty parameter.
-     */
-    public static void testDecodeEmpty() throws Exception {
-        // Regression for HARMONY-1513
-        byte[] result = Base64.decode(new byte[0]);
-        assertEquals("The length of the result differs from expected",
-                0, result.length);
-    }
-
-    public static Test suite() {
-        return new TestSuite(Base64Test.class);
-    }
-}
-
diff --git a/luni/src/test/java/tests/api/java/util/EventObjectTest.java b/luni/src/test/java/tests/api/java/util/EventObjectTest.java
deleted file mode 100644
index 201d9f3..0000000
--- a/luni/src/test/java/tests/api/java/util/EventObjectTest.java
+++ /dev/null
@@ -1,74 +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 tests.api.java.util;
-
-import java.util.EventObject;
-
-public class EventObjectTest extends junit.framework.TestCase {
-
-    Object myObject;
-
-    EventObject myEventObject;
-
-    /**
-     * java.util.EventObject#EventObject(java.lang.Object)
-     */
-    public void test_ConstructorLjava_lang_Object() {
-        try {
-            new EventObject(null);
-            fail ("IllegalArgumentException expected");
-        } catch (IllegalArgumentException e) {
-            //expected
-        }
-    }
-
-    /**
-     * java.util.EventObject#getSource()
-     */
-    public void test_getSource() {
-        // Test for method java.lang.Object java.util.EventObject.getSource()
-        assertTrue("Wrong source returned",
-                myEventObject.getSource() == myObject);
-    }
-
-    /**
-     * java.util.EventObject#toString()
-     */
-    public void test_toString() {
-        // Test for method java.lang.String java.util.EventObject.toString()
-        assertTrue("Incorrect toString returned: " + myEventObject.toString(),
-                myEventObject.toString().indexOf(
-                        "java.util.EventObject[source=java.lang.Object@") == 0);
-    }
-
-    /**
-     * Sets up the fixture, for example, open a network connection. This method
-     * is called before a test is executed.
-     */
-    protected void setUp() {
-        myObject = new Object();
-        myEventObject = new EventObject(myObject);
-    }
-
-    /**
-     * Tears down the fixture, for example, close a network connection. This
-     * method is called after a test is executed.
-     */
-    protected void tearDown() {
-    }
-}
diff --git a/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java b/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java
index d868cb2..8a02f9c 100644
--- a/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java
+++ b/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java
@@ -40,7 +40,7 @@
 import javax.net.ssl.X509TrustManager;
 import javax.security.cert.X509Certificate;
 import junit.framework.TestCase;
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
 import org.apache.harmony.xnet.tests.support.mySSLSession;
 import tests.support.Support_PortManager;
 
diff --git a/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java b/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java
index 3c49fc4..e2ebdbd 100644
--- a/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java
+++ b/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java
@@ -46,71 +46,45 @@
     /**
      * javax.net.ssl.HttpsURLConnection#HttpsURLConnection(java_net_URL)
      */
-    public final void test_Constructor() {
-        try {
-            MyHttpsURLConnection huc = new MyHttpsURLConnection(new URL("https://www.fortify.net/"));
-        } catch (Exception e) {
-            fail("Unexpected exception: " + e.toString());
-        }
-        try {
-            MyHttpsURLConnection huc = new MyHttpsURLConnection(null);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e.toString());
-        }
+    public final void test_Constructor() throws Exception {
+        new MyHttpsURLConnection(new URL("https://www.fortify.net/"));
+        new MyHttpsURLConnection(null);
     }
 
     /**
      * javax.net.ssl.HttpsURLConnection#getCipherSuite()
      */
-    public final void test_getCipherSuite() {
+    public final void test_getCipherSuite() throws Exception {
+        URL url = new URL("https://localhost:55555");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
         try {
-            URL url = new URL("https://localhost:55555");
-            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
-            try {
-                connection.getCipherSuite();
-                fail("IllegalStateException wasn't thrown");
-            } catch (IllegalStateException ise) {
-                //expected
-            }
-        } catch (Exception e) {
-            fail("Unexpected exception " + e + " for exception case");
+            connection.getCipherSuite();
+            fail("IllegalStateException wasn't thrown");
+        } catch (IllegalStateException expected) {
         }
 
-        try {
-            HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"));
-            assertEquals("CipherSuite", con.getCipherSuite());
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"));
+        assertEquals("CipherSuite", con.getCipherSuite());
     }
 
     /**
      * javax.net.ssl.HttpsURLConnection#getLocalCertificates()
      */
-    public final void test_getLocalCertificates() {
+    public final void test_getLocalCertificates() throws Exception {
+        URL url = new URL("https://localhost:55555");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
         try {
-            URL url = new URL("https://localhost:55555");
-            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
-            try {
-                connection.getLocalCertificates();
-                fail("IllegalStateException wasn't thrown");
-            } catch (IllegalStateException ise) {
-                //expected
-            }
-        } catch (Exception e) {
-            fail("Unexpected exception " + e + " for exception case");
+            connection.getLocalCertificates();
+            fail("IllegalStateException wasn't thrown");
+        } catch (IllegalStateException expected) {
         }
 
-        try {
-            HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508");
-            assertNull(con.getLocalCertificates());
-            con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509");
-            Certificate[] cert = con.getLocalCertificates();
-            assertNotNull(cert);
-            assertEquals(1, cert.length);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508");
+        assertNull(con.getLocalCertificates());
+        con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509");
+        Certificate[] cert = con.getLocalCertificates();
+        assertNotNull(cert);
+        assertEquals(1, cert.length);
     }
 
     /**
@@ -148,96 +122,67 @@
     /**
      * javax.net.ssl.HttpsURLConnection#getLocalPrincipal()
      */
-    public final void test_getLocalPrincipal() {
+    public final void test_getLocalPrincipal() throws Exception {
+        URL url = new URL("https://localhost:55555");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
         try {
-            URL url = new URL("https://localhost:55555");
-            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
-            try {
-                connection.getLocalPrincipal();
-                fail("IllegalStateException wasn't thrown");
-            } catch (IllegalStateException ise) {
-                //expected
-            }
-        } catch (Exception e) {
-            fail("Unexpected exception " + e + " for exception case");
+            connection.getLocalPrincipal();
+            fail("IllegalStateException wasn't thrown");
+        } catch (IllegalStateException expected) {
         }
 
-        try {
-            HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508");
-            assertNull(con.getLocalPrincipal());
-            con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509");
-            assertNotNull("Local principal is null", con.getLocalPrincipal());
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508");
+        assertNull(con.getLocalPrincipal());
+        con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509");
+        assertNotNull("Local principal is null", con.getLocalPrincipal());
     }
 
     /**
      * javax.net.ssl.HttpsURLConnection#getPeerPrincipal()
      */
     public final void test_getPeerPrincipal() throws Exception {
+        URL url = new URL("https://localhost:55555");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
         try {
-            URL url = new URL("https://localhost:55555");
-            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
-            try {
-                connection.getPeerPrincipal();
-                fail("IllegalStateException wasn't thrown");
-            } catch (IllegalStateException ise) {
-                //expected
-            }
-        } catch (Exception e) {
-            fail("Unexpected exception " + e + " for exception case");
+            connection.getPeerPrincipal();
+            fail("IllegalStateException wasn't thrown");
+        } catch (IllegalStateException expected) {
         }
         HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508");
         try {
             Principal p = con.getPeerPrincipal();
             fail("SSLPeerUnverifiedException wasn't thrown");
-        } catch (SSLPeerUnverifiedException e) {
-            //expected
+        } catch (SSLPeerUnverifiedException expected) {
         }
 
         con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509");
-        try {
-            Principal p = con.getPeerPrincipal();
-            assertNotNull(p);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        Principal p = con.getPeerPrincipal();
+        assertNotNull(p);
     }
 
     /**
      * javax.net.ssl.HttpsURLConnection#getServerCertificates()
      */
     public final void test_getServerCertificates() throws Exception {
+        URL url = new URL("https://localhost:55555");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
         try {
-            URL url = new URL("https://localhost:55555");
-            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
-            try {
-                connection.getServerCertificates();
-                fail("IllegalStateException wasn't thrown");
-            } catch (IllegalStateException ise) {
-                //expected
-            }
-        } catch (Exception e) {
-            fail("Unexpected exception " + e + " for exception case");
+            connection.getServerCertificates();
+            fail("IllegalStateException wasn't thrown");
+        } catch (IllegalStateException expected) {
         }
 
         HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508");
         try {
-            Certificate[] cert = con.getServerCertificates();
+            con.getServerCertificates();
             fail("SSLPeerUnverifiedException wasn't thrown");
-        } catch (SSLPeerUnverifiedException e) {
-            //expected
+        } catch (SSLPeerUnverifiedException expected) {
         }
 
         con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509");
-        try {
-            Certificate[] cert = con.getServerCertificates();
-            assertNotNull(cert);
-            assertEquals(1, cert.length);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        Certificate[] cert = con.getServerCertificates();
+        assertNotNull(cert);
+        assertEquals(1, cert.length);
     }
 
     /**
@@ -258,16 +203,13 @@
         try {
             HttpsURLConnection.setDefaultHostnameVerifier(null);
             fail("No expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected
+        } catch (IllegalArgumentException expected) {
         }
         HostnameVerifier def = HttpsURLConnection.getDefaultHostnameVerifier();
         try {
             myHostnameVerifier hnv = new myHostnameVerifier();
             HttpsURLConnection.setDefaultHostnameVerifier(hnv);
             assertEquals(hnv, HttpsURLConnection.getDefaultHostnameVerifier());
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
         } finally {
             HttpsURLConnection.setDefaultHostnameVerifier(def);
         }
@@ -281,14 +223,10 @@
         try {
             con.setHostnameVerifier(null);
             fail("No expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+        } catch (IllegalArgumentException expected) {
         }
-        try {
-            myHostnameVerifier hnv = new myHostnameVerifier();
-            con.setHostnameVerifier(hnv);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        myHostnameVerifier hnv = new myHostnameVerifier();
+        con.setHostnameVerifier(hnv);
     }
 
     /**
@@ -298,15 +236,11 @@
         try {
             HttpsURLConnection.setDefaultSSLSocketFactory(null);
             fail("No expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+        } catch (IllegalArgumentException expected) {
         }
-        try {
-            SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory
-                    .getDefault();
-            HttpsURLConnection.setDefaultSSLSocketFactory(ssf);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory
+                .getDefault();
+        HttpsURLConnection.setDefaultSSLSocketFactory(ssf);
     }
 
     /**
@@ -317,15 +251,11 @@
         try {
             con.setSSLSocketFactory(null);
             fail("No expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+        } catch (IllegalArgumentException expected) {
         }
-        try {
-            SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory
-                    .getDefault();
-            con.setSSLSocketFactory(ssf);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory
+                .getDefault();
+        con.setSSLSocketFactory(ssf);
     }
 }
 
@@ -353,32 +283,30 @@
      * @see javax.net.ssl.HttpsURLConnection#getLocalCertificates()
      */
     public Certificate[] getLocalCertificates() {
-        Certificate cert = null;
         try {
             CertificateFactory cf = CertificateFactory.getInstance(typeDone);
             byte[] barr = TestUtils.getX509Certificate_v1();
             ByteArrayInputStream bis = new ByteArrayInputStream(barr);
-            cert = cf.generateCertificate(bis);
+            Certificate cert = cf.generateCertificate(bis);
+            return new Certificate[] { cert };
         } catch (CertificateException se) {
-            cert = null;
+            return null;
         }
-        return cert == null ? null : new Certificate[]{cert};
     }
 
     /*
      * @see javax.net.ssl.HttpsURLConnection#getServerCertificates()
      */
     public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
-        Certificate cert = null;
         try {
             CertificateFactory cf = CertificateFactory.getInstance(typeDone);
             byte[] barr = TestUtils.getX509Certificate_v3();
             ByteArrayInputStream bis = new ByteArrayInputStream(barr);
-            cert = cf.generateCertificate(bis);
+            Certificate cert = cf.generateCertificate(bis);
+            return new Certificate[] { cert };
         } catch (CertificateException se) {
             throw new SSLPeerUnverifiedException("No server's end-entity certificate");
         }
-        return cert == null ? null : new Certificate[]{cert};
     }
 
     /*
diff --git a/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java b/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java
index 6c8e657..cc96782 100644
--- a/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java
+++ b/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java
@@ -18,7 +18,7 @@
 
 import junit.framework.TestCase;
 
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
 
 import tests.support.Support_PortManager;
 
@@ -470,13 +470,13 @@
         SSLContext sslContext = SSLContext.getInstance("TLS");
         sslContext.init(keyManagers, null, null);
         for (int i = 0; i < 2048; ++i) {
-            sslContext.getServerSocketFactory().createServerSocket();
+            sslContext.getServerSocketFactory().createServerSocket().close();
         }
 
         // Test the other codepath, which copies a seed from a byte[].
         sslContext.init(keyManagers, null, new SecureRandom());
         for (int i = 0; i < 2048; ++i) {
-            sslContext.getServerSocketFactory().createServerSocket();
+            sslContext.getServerSocketFactory().createServerSocket().close();
         }
     }
 }
diff --git a/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java b/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java
index 70c0eb2..ec23cae 100644
--- a/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java
+++ b/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java
@@ -36,7 +36,7 @@
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.TrustManager;
 import junit.framework.TestCase;
-import org.apache.harmony.luni.util.Base64;
+import libcore.io.Base64;
 import tests.api.javax.net.ssl.HandshakeCompletedEventTest.MyHandshakeListener;
 import tests.api.javax.net.ssl.HandshakeCompletedEventTest.TestTrustManager;
 import tests.support.Support_PortManager;
diff --git a/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java
index 84ac212..ab60f72 100644
--- a/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java
+++ b/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java
@@ -16,31 +16,34 @@
 package tests.api.javax.net.ssl;
 
 import dalvik.annotation.AndroidOnly;
-
-import javax.net.ssl.*;
-import javax.security.cert.X509Certificate;
-
-import java.net.*;
-import java.security.KeyStore;
-import java.security.SecureRandom;
-import java.util.Arrays;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.security.cert.X509Certificate;
 import junit.framework.TestCase;
-
-import org.apache.harmony.luni.util.Base64;
-
+import libcore.io.Base64;
 import tests.api.javax.net.ssl.HandshakeCompletedEventTest.TestTrustManager;
 import tests.support.Support_PortManager;
+import libcore.java.security.StandardNames;
 
 public class SSLSocketTest extends TestCase {
 
     public class HandshakeCL implements HandshakeCompletedListener {
-        HandshakeCL() {
-            super();
-        }
         public void handshakeCompleted(HandshakeCompletedEvent event) {
         }
     }
@@ -48,257 +51,164 @@
     /**
      * javax.net.ssl.SSLSocket#SSLSocket()
      */
-    public void testConstructor_01() {
-        try {
-            SSLSocket ssl = getSSLSocket();
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+    public void testConstructor_01() throws Exception {
+        SSLSocket ssl = getSSLSocket();
+        assertNotNull(ssl);
+        ssl.close();
     }
 
     /**
-     * @throws IOException
-     * @throws UnknownHostException
      * javax.net.ssl.SSLSocket#SSLSocket(InetAddress address, int port)
      */
     public void testConstructor_02() throws UnknownHostException, IOException {
-        SSLSocket ssl;
         int sport = startServer("Cons InetAddress,I");
         int[] invalidPort = {-1, Integer.MIN_VALUE, 65536, Integer.MAX_VALUE};
 
-        ssl = getSSLSocket(InetAddress.getLocalHost(), sport);
+        SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost(), sport);
         assertNotNull(ssl);
         assertEquals(sport, ssl.getPort());
+        ssl.close();
 
         try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), sport + 1);
-            fail("IOException wasn't thrown ...");
-        } catch (IOException e) {
-            //expected
+            getSSLSocket(InetAddress.getLocalHost(), sport + 1);
+            fail();
+        } catch (IOException expected) {
         }
 
         for (int i = 0; i < invalidPort.length; i++) {
             try {
-                ssl = getSSLSocket(InetAddress.getLocalHost(), invalidPort[i]);
-                fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]);
-            } catch (IllegalArgumentException iae) {
-                // expected
-            } catch (Exception e) {
-                fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]);
+                getSSLSocket(InetAddress.getLocalHost(), invalidPort[i]);
+                fail();
+            } catch (IllegalArgumentException expected) {
             }
         }
     }
 
     /**
-     * @throws IOException
-     * @throws UnknownHostException
      * javax.net.ssl.SSLSocket#SSLSocket(InetAddress address, int port,
      *                                          InetAddress clientAddress, int clientPort)
      */
     public void testConstructor_03() throws UnknownHostException, IOException {
-        SSLSocket ssl;
         int sport = startServer("Cons InetAddress,I,InetAddress,I");
         int portNumber = Support_PortManager.getNextPort();
 
-        ssl = getSSLSocket(InetAddress.getLocalHost(), sport,
-                              InetAddress.getLocalHost(), portNumber);
+        SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost(), sport,
+                                     InetAddress.getLocalHost(), portNumber);
         assertNotNull(ssl);
         assertEquals(sport, ssl.getPort());
         assertEquals(portNumber, ssl.getLocalPort());
+        ssl.close();
 
         try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), 8081, InetAddress.getLocalHost(), 8082);
-            fail("IOException wasn't thrown ...");
-        } catch (IOException e) {
-            //expected
+            getSSLSocket(InetAddress.getLocalHost(), 8081, InetAddress.getLocalHost(), 8082);
+            fail();
+        } catch (IOException expected) {
         }
 
         try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), -1,
-                                  InetAddress.getLocalHost(), sport + 1);
-            fail("IllegalArgumentException wasn't thrown for -1");
-        } catch (IllegalArgumentException iae) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IllegalArgumentException for -1");
+            getSSLSocket(InetAddress.getLocalHost(), -1, InetAddress.getLocalHost(), sport + 1);
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
         try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), sport,
-                                  InetAddress.getLocalHost(), -1);
-            fail("IllegalArgumentException wasn't thrown for -1");
-        } catch (IllegalArgumentException iae) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IllegalArgumentException for -1");
+            getSSLSocket(InetAddress.getLocalHost(), sport, InetAddress.getLocalHost(), -1);
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
 
         try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), Integer.MIN_VALUE,
-                                  InetAddress.getLocalHost(), sport + 1);
-            fail("IOException wasn't thrown for " + Integer.MIN_VALUE);
-        } catch (IOException ioe) {
-            // expected on RI
-        } catch (IllegalArgumentException iae) {
-            // expected on Android
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IOException for "
-                    + Integer.MIN_VALUE);
+            getSSLSocket(InetAddress.getLocalHost(), Integer.MIN_VALUE,
+                         InetAddress.getLocalHost(), sport + 1);
+            fail();
+        } catch (IOException expectedOnRI) {
+            assertTrue(StandardNames.IS_RI);
+        } catch (IllegalArgumentException expectedOnAndroid) {
+            assertFalse(StandardNames.IS_RI);
         }
         try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), sport,
-                                  InetAddress.getLocalHost(), Integer.MIN_VALUE);
-            fail("IllegalArgumentException wasn't thrown for "
-                    + Integer.MIN_VALUE);
-        } catch (IllegalArgumentException iae) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IllegalArgumentException for "
-                    + Integer.MIN_VALUE);
-        }
-
-        try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), 65536,
-                                  InetAddress.getLocalHost(), sport + 1);
-            fail("IOException wasn't thrown for 65536");
-        } catch (IOException ioe) {
-            // expected on RI
-        } catch (IllegalArgumentException iae) {
-            // expected on Android
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IOException for 65536");
-        }
-        try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), sport,
-                                  InetAddress.getLocalHost(), 65536);
-            fail("IllegalArgumentException wasn't thrown for 65536");
-        } catch (IllegalArgumentException iae) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IllegalArgumentException for 65536");
-        }
-
-        try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), Integer.MAX_VALUE,
-                                  InetAddress.getLocalHost(), sport + 1);
-            fail("IOException wasn't thrown for " + Integer.MAX_VALUE);
-        } catch (IOException ioe) {
-            // expected on RI
-        } catch (IllegalArgumentException iae) {
-            // expected on Android
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IOException for "
-                    + Integer.MAX_VALUE);
-        }
-        try {
-            ssl = getSSLSocket(InetAddress.getLocalHost(), sport,
-                                  InetAddress.getLocalHost(), Integer.MAX_VALUE);
-            fail("IllegalArgumentException wasn't thrown for "
-                    + Integer.MAX_VALUE);
-        } catch (IllegalArgumentException iae) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IllegalArgumentException for "
-                    + Integer.MAX_VALUE);
+            getSSLSocket(InetAddress.getLocalHost(), sport,
+                         InetAddress.getLocalHost(), Integer.MAX_VALUE);
+            fail();
+        } catch (IllegalArgumentException expectedOnAndroid) {
+            assertFalse(StandardNames.IS_RI);
         }
     }
 
     /**
-     * @throws IOException
-     * @throws UnknownHostException
      * javax.net.ssl.SSLSocket#SSLSocket(String host, int port)
      */
     public void testConstructor_04() throws UnknownHostException, IOException {
-        SSLSocket ssl;
         int sport = startServer("Cons String,I");
         int[] invalidPort = {-1, Integer.MIN_VALUE, 65536, Integer.MAX_VALUE};
 
-        ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport);
+        SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport);
         assertNotNull(ssl);
         assertEquals(sport, ssl.getPort());
+        ssl.close();
 
         try {
-            ssl = getSSLSocket("localhost", 8082);
-            fail("IOException wasn't thrown ...");
-        } catch (IOException e) {
-            //expected
+            getSSLSocket("localhost", 8082);
+            fail();
+        } catch (IOException expected) {
         }
 
         for (int i = 0; i < invalidPort.length; i++) {
             try {
-                ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i]);
-                fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]);
-            } catch (IllegalArgumentException iae) {
-                // expected
-            } catch (Exception e) {
-                fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]);
+                getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i]);
+                fail();
+            } catch (IllegalArgumentException expected) {
             }
         }
 
         try {
-            ssl = getSSLSocket("bla-bla", sport);
-            fail("UnknownHostException wasn't thrown");
-        } catch (UnknownHostException uhp) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of UnknownHostException");
+            getSSLSocket("bla-bla", sport);
+            fail();
+        } catch (UnknownHostException expected) {
         }
     }
 
     /**
-     * @throws IOException
-     * @throws UnknownHostException
      * javax.net.ssl.SSLSocket#SSLSocket(String host, int port, InetAddress clientAddress,
      *           int clientPort)
      */
     public void testConstructor_05() throws UnknownHostException, IOException {
-        SSLSocket ssl;
         int sport = startServer("Cons String,I,InetAddress,I");
         int portNumber = Support_PortManager.getNextPort();
         int[] invalidPort = {-1, Integer.MIN_VALUE, 65536, Integer.MAX_VALUE};
 
-        ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport,
-                              InetAddress.getLocalHost(), portNumber);
+        SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport,
+                                     InetAddress.getLocalHost(), portNumber);
         assertNotNull(ssl);
         assertEquals(sport, ssl.getPort());
         assertEquals(portNumber, ssl.getLocalPort());
 
         try {
-            ssl = getSSLSocket("localhost", 8081, InetAddress.getLocalHost(), 8082);
-            fail("IOException wasn't thrown ...");
-        } catch (IOException e) {
-            //expected
+            getSSLSocket("localhost", 8081, InetAddress.getLocalHost(), 8082);
+            fail();
+        } catch (IOException expected) {
         }
 
         for (int i = 0; i < invalidPort.length; i++) {
             portNumber = Support_PortManager.getNextPort();
             try {
-                ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i],
-                                      InetAddress.getLocalHost(), portNumber);
-                fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]);
-            } catch (IllegalArgumentException iae) {
-                // expected
-            } catch (Exception e) {
-                fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]);
+                getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i],
+                             InetAddress.getLocalHost(), portNumber);
+                fail();
+            } catch (IllegalArgumentException expected) {
             }
             try {
-                ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport,
-                                      InetAddress.getLocalHost(), invalidPort[i]);
-                fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]);
-            } catch (IllegalArgumentException iae) {
-                // expected
-            } catch (Exception e) {
-                fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]);
+                getSSLSocket(InetAddress.getLocalHost().getHostName(), sport,
+                             InetAddress.getLocalHost(), invalidPort[i]);
+                fail();
+            } catch (IllegalArgumentException expected) {
             }
         }
 
         portNumber = Support_PortManager.getNextPort();
         try {
-            ssl = getSSLSocket("bla-bla", sport, InetAddress.getLocalHost(), portNumber);
-            fail("UnknownHostException wasn't thrown");
-        } catch (UnknownHostException uhp) {
-            // expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of UnknownHostException");
+            getSSLSocket("bla-bla", sport, InetAddress.getLocalHost(), portNumber);
+            fail();
+        } catch (UnknownHostException expected) {
         }
     }
 
@@ -307,18 +217,17 @@
         SSLContext sslContext = SSLContext.getInstance("TLS");
         sslContext.init(null, null, null);
         for (int i = 0; i < 2048; ++i) {
-            sslContext.getSocketFactory().createSocket();
+            sslContext.getSocketFactory().createSocket().close();
         }
 
         // Test the other codepath, which copies a seed from a byte[].
         sslContext.init(null, null, new SecureRandom());
         for (int i = 0; i < 2048; ++i) {
-            sslContext.getSocketFactory().createSocket();
+            sslContext.getSocketFactory().createSocket().close();
         }
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#addHandshakeCompletedListener(HandshakeCompletedListener listener)
      */
     @AndroidOnly("RI doesn't throw the specified IAE")
@@ -327,19 +236,14 @@
         HandshakeCompletedListener ls = new HandshakeCL();
         try {
             ssl.addHandshakeCompletedListener(null);
-            fail("IllegalArgumentException wasn't thrown");
-        } catch (IllegalArgumentException iae) {
-            //expected
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
-        try {
-            ssl.addHandshakeCompletedListener(ls);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        ssl.addHandshakeCompletedListener(ls);
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#removeHandshakeCompletedListener(HandshakeCompletedListener listener)
      */
     public void test_removeHandshakeCompletedListener() throws IOException {
@@ -347,29 +251,21 @@
         HandshakeCompletedListener ls = new HandshakeCL();
         try {
             ssl.removeHandshakeCompletedListener(null);
-            fail("IllegalArgumentException wasn't thrown");
-        } catch (IllegalArgumentException iae) {
-            //expected
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
 
         try {
             ssl.removeHandshakeCompletedListener(ls);
-        } catch (IllegalArgumentException iae) {
-                //expected
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
+        } catch (IllegalArgumentException expected) {
         }
 
         ssl.addHandshakeCompletedListener(ls);
-        try {
-            ssl.removeHandshakeCompletedListener(ls);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        ssl.removeHandshakeCompletedListener(ls);
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#setEnableSessionCreation(boolean flag)
      * javax.net.ssl.SSLSocket#getEnableSessionCreation()
      */
@@ -380,11 +276,10 @@
         assertFalse(ssl.getEnableSessionCreation());
         ssl.setEnableSessionCreation(true);
         assertTrue(ssl.getEnableSessionCreation());
+        ssl.close();
     }
 
     /**
-     * @throws IOException
-     * @throws UnknownHostException
      * javax.net.ssl.SSLSocket#setNeedClientAuth(boolean need)
      * javax.net.ssl.SSLSocket#getNeedClientAuthCreation()
      */
@@ -394,11 +289,10 @@
         assertTrue(ssl.getNeedClientAuth());
         ssl.setNeedClientAuth(false);
         assertFalse(ssl.getNeedClientAuth());
+        ssl.close();
     }
 
     /**
-     * @throws IOException
-     * @throws UnknownHostException
      * javax.net.ssl.SSLSocket#setWantClientAuth(boolean want)
      * javax.net.ssl.SSLSocket#getWantClientAuthCreation()
      */
@@ -408,20 +302,20 @@
         assertTrue(ssl.getWantClientAuth());
         ssl.setWantClientAuth(false);
         assertFalse(ssl.getWantClientAuth());
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#getSupportedProtocols()
      */
     public void test_getSupportedProtocols() throws IOException {
         SSLSocket ssl = getSSLSocket();
         String[] res = ssl.getSupportedProtocols();
         assertTrue("No supported protocols found", res.length > 0);
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#getEnabledProtocols()
      * javax.net.ssl.SSLSocket#setEnabledProtocols(String[] protocols)
      */
@@ -429,50 +323,42 @@
         SSLSocket ssl = getSSLSocket();
         try {
             ssl.setEnabledProtocols(null);
-        } catch (IllegalArgumentException iae) {
-            //expected
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
-        try {
-            ssl.setEnabledProtocols(new String[] {});
-        } catch (IllegalArgumentException iae) {
-            //expected
-        }
+        ssl.setEnabledProtocols(new String[] {});
         try {
             ssl.setEnabledProtocols(new String[] {"blubb"});
-        } catch (IllegalArgumentException iae) {
-            //expected
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
         ssl.setEnabledProtocols(ssl.getEnabledProtocols());
         String[] res = ssl.getEnabledProtocols();
         assertEquals("no enabled protocols set",
-                ssl.getEnabledProtocols().length, res.length);
+                     ssl.getEnabledProtocols().length, res.length);
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#getSession()
      */
     public void test_getSession() throws IOException {
         SSLSocket ssl = getSSLSocket();
-        try {
-            assertNotNull(ssl.getSession());
-        } catch (Exception e) {
-            fail("Unexpected exception " + e);
-        }
+        assertNotNull(ssl.getSession());
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#getSupportedCipherSuites()
      */
     public void test_getSupportedCipherSuites() throws IOException {
         SSLSocket ssl = getSSLSocket();
         String[] res = ssl.getSupportedCipherSuites();
         assertTrue("no supported cipher suites", res.length > 0);
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#getEnabledCipherSuites()
      * javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[] suites)
      */
@@ -480,18 +366,14 @@
         SSLSocket ssl = getSSLSocket();
         try {
             ssl.setEnabledCipherSuites(null);
-        } catch (IllegalArgumentException iae) {
-            //expected
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
-        try {
-            ssl.setEnabledCipherSuites(new String[] {});
-        } catch (IllegalArgumentException iae) {
-            //expected
-        }
+        ssl.setEnabledCipherSuites(new String[] {});
         try {
             ssl.setEnabledCipherSuites(new String[] {"blubb"});
-        } catch (IllegalArgumentException iae) {
-            //expected
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
         ssl.setEnabledCipherSuites(ssl.getSupportedCipherSuites());
         String[] res = ssl.getEnabledCipherSuites();
@@ -499,10 +381,10 @@
         assertEquals("not all supported cipher suites were enabled",
                      Arrays.asList(ssl.getSupportedCipherSuites()),
                      Arrays.asList(res));
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#getUseClientMode()
      * javax.net.ssl.SSLSocket#setUseClientMode(boolean mode)
      */
@@ -511,6 +393,7 @@
         assertTrue(ssl.getUseClientMode());
         ssl.setUseClientMode(false);
         assertFalse(ssl.getUseClientMode());
+        ssl.close();
 
         ssl = getSSLSocket("localhost", startServer("UseClientMode"));
         try {
@@ -520,32 +403,26 @@
         }
         try {
             ssl.setUseClientMode(false);
-            fail("IllegalArgumentException wasn't thrown");
-        } catch (IllegalArgumentException iae) {
-            //expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IllegalArgumentException");
+            fail();
+        } catch (IllegalArgumentException expected) {
         }
+        ssl.close();
     }
 
     /**
-     * @throws IOException
      * javax.net.ssl.SSLSocket#startHandshake()
      */
     public void test_startHandshake() throws IOException {
         SSLSocket ssl = getSSLSocket();
         try {
             ssl.startHandshake();
-            fail("IOException wasn't thrown");
-        } catch (IOException ioe) {
-            //expected
-        } catch (Exception e) {
-            fail(e + " was thrown instead of IOException");
+            fail();
+        } catch (IOException expected) {
         }
+        ssl.close();
     }
 
-    // Change this to false if on RI
-    boolean useBKS = true;
+    boolean useBKS = !StandardNames.IS_RI;
 
     private String PASSWORD = "android";
 
@@ -557,61 +434,61 @@
      * Defines the keystore contents for the server, BKS version. Holds just a
      * single self-generated key. The subject name is "Test Server".
      */
-    private static final String SERVER_KEYS_BKS =
-        "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" +
-        "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" +
-        "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" +
-        "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" +
-        "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" +
-        "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" +
-        "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" +
-        "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" +
-        "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" +
-        "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" +
-        "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" +
-        "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" +
-        "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" +
-        "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" +
-        "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" +
-        "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" +
-        "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" +
-        "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" +
-        "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" +
-        "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" +
-        "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" +
-        "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" +
-        "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" +
-        "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
+    private static final String SERVER_KEYS_BKS = ""
+            + "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41"
+            + "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET"
+            + "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV"
+            + "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw"
+            + "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U"
+            + "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl"
+            + "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy"
+            + "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV"
+            + "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG"
+            + "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU"
+            + "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV"
+            + "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx"
+            + "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR"
+            + "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN"
+            + "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs"
+            + "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck"
+            + "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM"
+            + "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI"
+            + "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f"
+            + "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx"
+            + "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt"
+            + "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw"
+            + "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl"
+            + "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
 
     /**
      * Defines the keystore contents for the server, JKS version. Holds just a
      * single self-generated key. The subject name is "Test Server".
      */
-    private static final String SERVER_KEYS_JKS =
-        "/u3+7QAAAAIAAAABAAAAAQAFbXlrZXkAAAEaWFfBeAAAArowggK2MA4GCisGAQQBKgIRAQEFAASC" +
-        "AqI2kp5XjnF8YZkhcF92YsJNQkvsmH7zqMM87j23zSoV4DwyE3XeC/gZWq1ToScIhoqZkzlbWcu4" +
-        "T/Zfc/DrfGk/rKbBL1uWKGZ8fMtlZk8KoAhxZk1JSyJvdkyKxqmzUbxk1OFMlN2VJNu97FPVH+du" +
-        "dvjTvmpdoM81INWBW/1fZJeQeDvn4mMbbe0IxgpiLnI9WSevlaDP/sm1X3iO9yEyzHLL+M5Erspo" +
-        "Cwa558fOu5DdsICMXhvDQxjWFKFhPHnKtGe+VvwkG9/bAaDgx3kfhk0w5zvdnkKb+8Ed9ylNRzdk" +
-        "ocAa/mxlMTOsTvDKXjjsBupNPIIj7OP4GNnZaxkJjSs98pEO67op1GX2qhy6FSOPNuq8k/65HzUc" +
-        "PYn6voEeh6vm02U/sjEnzRevQ2+2wXoAdp0EwtQ/DlMe+NvcwPGWKuMgX4A4L93DZGb04N2VmAU3" +
-        "YLOtZwTO0LbuWrcCM/q99G/7LcczkxIVrO2I/rh8RXVczlf9QzcrFObFv4ATuspWJ8xG7DhsMbnk" +
-        "rT94Pq6TogYeoz8o8ZMykesAqN6mt/9+ToIemmXv+e+KU1hI5oLwWMnUG6dXM6hIvrULY6o+QCPH" +
-        "172YQJMa+68HAeS+itBTAF4Clm/bLn6reHCGGU6vNdwU0lYldpiOj9cB3t+u2UuLo6tiFWjLf5Zs" +
-        "EQJETd4g/EK9nHxJn0GAKrWnTw7pEHQJ08elzUuy04C/jEEG+4QXU1InzS4o/kR0Sqz2WTGDoSoq" +
-        "ewuPRU5bzQs/b9daq3mXrnPtRBL6HfSDAdpTK76iHqLCGdqx3avHjVSBm4zFvEuYBCev+3iKOBmg" +
-        "yh7eQRTjz4UOWfy85omMBr7lK8PtfVBDzOXpasxS0uBgdUyBDX4tO6k9jZ8a1kmQRQAAAAEABVgu" +
-        "NTA5AAACSDCCAkQwggGtAgRIR8SKMA0GCSqGSIb3DQEBBAUAMGkxCzAJBgNVBAYTAlVTMRMwEQYD" +
-        "VQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQHEwNNVFYxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMH" +
-        "QW5kcm9pZDEUMBIGA1UEAxMLVGVzdCBTZXJ2ZXIwHhcNMDgwNjA1MTA0ODQyWhcNMDgwOTAzMTA0" +
-        "ODQyWjBpMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8w" +
-        "DQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMIGf" +
-        "MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwoC6chqCI84rj1PrXuJgbiit4EV909zR6N0jNlYfg" +
-        "itwB39bP39wH03rFm8T59b3mbSptnGmCIpLZn25KPPFsYD3JJ+wFlmiUdEP9H05flfwtFQJnw9uT" +
-        "3rRIdYVMPcQ3RoZzwAMliGr882I2thIDbA6xjGU/1nRIdvk0LtxH3QIDAQABMA0GCSqGSIb3DQEB" +
-        "BAUAA4GBAJn+6YgUlY18Ie+0+Vt8oEi81DNi/bfPrAUAh63fhhBikx/3R9dl3wh09Z6p7cIdNxjW" +
-        "n2ll+cRW9eqF7z75F0Omm0C7/KAEPjukVbszmzeU5VqzkpSt0j84YWi+TfcHRrfvhLbrlmGITVpY" +
-        "ol5pHLDyqGmDs53pgwipWqsn/nEXEBgj3EoqPeqHbDf7YaP8h/5BSt0=";
+    private static final String SERVER_KEYS_JKS = ""
+            + "/u3+7QAAAAIAAAABAAAAAQAFbXlrZXkAAAEaWFfBeAAAArowggK2MA4GCisGAQQBKgIRAQEFAASC"
+            + "AqI2kp5XjnF8YZkhcF92YsJNQkvsmH7zqMM87j23zSoV4DwyE3XeC/gZWq1ToScIhoqZkzlbWcu4"
+            + "T/Zfc/DrfGk/rKbBL1uWKGZ8fMtlZk8KoAhxZk1JSyJvdkyKxqmzUbxk1OFMlN2VJNu97FPVH+du"
+            + "dvjTvmpdoM81INWBW/1fZJeQeDvn4mMbbe0IxgpiLnI9WSevlaDP/sm1X3iO9yEyzHLL+M5Erspo"
+            + "Cwa558fOu5DdsICMXhvDQxjWFKFhPHnKtGe+VvwkG9/bAaDgx3kfhk0w5zvdnkKb+8Ed9ylNRzdk"
+            + "ocAa/mxlMTOsTvDKXjjsBupNPIIj7OP4GNnZaxkJjSs98pEO67op1GX2qhy6FSOPNuq8k/65HzUc"
+            + "PYn6voEeh6vm02U/sjEnzRevQ2+2wXoAdp0EwtQ/DlMe+NvcwPGWKuMgX4A4L93DZGb04N2VmAU3"
+            + "YLOtZwTO0LbuWrcCM/q99G/7LcczkxIVrO2I/rh8RXVczlf9QzcrFObFv4ATuspWJ8xG7DhsMbnk"
+            + "rT94Pq6TogYeoz8o8ZMykesAqN6mt/9+ToIemmXv+e+KU1hI5oLwWMnUG6dXM6hIvrULY6o+QCPH"
+            + "172YQJMa+68HAeS+itBTAF4Clm/bLn6reHCGGU6vNdwU0lYldpiOj9cB3t+u2UuLo6tiFWjLf5Zs"
+            + "EQJETd4g/EK9nHxJn0GAKrWnTw7pEHQJ08elzUuy04C/jEEG+4QXU1InzS4o/kR0Sqz2WTGDoSoq"
+            + "ewuPRU5bzQs/b9daq3mXrnPtRBL6HfSDAdpTK76iHqLCGdqx3avHjVSBm4zFvEuYBCev+3iKOBmg"
+            + "yh7eQRTjz4UOWfy85omMBr7lK8PtfVBDzOXpasxS0uBgdUyBDX4tO6k9jZ8a1kmQRQAAAAEABVgu"
+            + "NTA5AAACSDCCAkQwggGtAgRIR8SKMA0GCSqGSIb3DQEBBAUAMGkxCzAJBgNVBAYTAlVTMRMwEQYD"
+            + "VQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQHEwNNVFYxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMH"
+            + "QW5kcm9pZDEUMBIGA1UEAxMLVGVzdCBTZXJ2ZXIwHhcNMDgwNjA1MTA0ODQyWhcNMDgwOTAzMTA0"
+            + "ODQyWjBpMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8w"
+            + "DQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMIGf"
+            + "MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwoC6chqCI84rj1PrXuJgbiit4EV909zR6N0jNlYfg"
+            + "itwB39bP39wH03rFm8T59b3mbSptnGmCIpLZn25KPPFsYD3JJ+wFlmiUdEP9H05flfwtFQJnw9uT"
+            + "3rRIdYVMPcQ3RoZzwAMliGr882I2thIDbA6xjGU/1nRIdvk0LtxH3QIDAQABMA0GCSqGSIb3DQEB"
+            + "BAUAA4GBAJn+6YgUlY18Ie+0+Vt8oEi81DNi/bfPrAUAh63fhhBikx/3R9dl3wh09Z6p7cIdNxjW"
+            + "n2ll+cRW9eqF7z75F0Omm0C7/KAEPjukVbszmzeU5VqzkpSt0j84YWi+TfcHRrfvhLbrlmGITVpY"
+            + "ol5pHLDyqGmDs53pgwipWqsn/nEXEBgj3EoqPeqHbDf7YaP8h/5BSt0=";
 
     protected int startServer(String name) {
         String keys = useBKS ? SERVER_KEYS_BKS : SERVER_KEYS_JKS;
@@ -620,12 +497,15 @@
         serverThread.start();
         try {
             while (!serverReady) {
+                Exception e = server.getException();
+                if (e != null) {
+                    throw new AssertionError(e);
+                }
                 Thread.currentThread().sleep(50);
             }
             // give the server 100 millis to accept
             Thread.currentThread().sleep(100);
-        } catch (InterruptedException e) {
-            // ignore
+        } catch (InterruptedException ignore) {
         }
         return server.sport;
     }
@@ -668,27 +548,34 @@
                 SSLContext sslContext = SSLContext.getInstance("TLS");
                 sslContext.init(keyManagers, trustManagers, null);
 
-                SSLServerSocket serverSocket = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket();
+                SSLServerSocket serverSocket = (SSLServerSocket)
+                        sslContext.getServerSocketFactory().createServerSocket();
+                try {
+                    serverSocket.bind(new InetSocketAddress(port));
+                    sport = serverSocket.getLocalPort();
+                    serverReady = true;
 
-                serverSocket.bind(new InetSocketAddress(port));
-                sport = serverSocket.getLocalPort();
-                serverReady = true;
+                    SSLSocket clientSocket = (SSLSocket)serverSocket.accept();
 
-                SSLSocket clientSocket = (SSLSocket)serverSocket.accept();
-
-                InputStream stream = clientSocket.getInputStream();
-
-                for (int i = 0; i < 256; i++) {
-                    int j = stream.read();
-                    if (i != j) {
-                        throw new RuntimeException("Error reading socket, expected " + i + ", got " + j);
+                    try {
+                        InputStream stream = clientSocket.getInputStream();
+                        try {
+                            for (int i = 0; i < 256; i++) {
+                                int j = stream.read();
+                                if (i != j) {
+                                    throw new RuntimeException("Error reading socket, expected " + i
+                                                               + ", got " + j);
+                                }
+                            }
+                        } finally {
+                            stream.close();
+                        }
+                    } finally {
+                        clientSocket.close();
                     }
+                } finally {
+                    serverSocket.close();
                 }
-
-                stream.close();
-                clientSocket.close();
-                serverSocket.close();
-
             } catch (Exception ex) {
                 exception = ex;
             }
@@ -724,32 +611,30 @@
     }
 
     private SSLSocket getSSLSocket() throws IOException {
-        SSLSocket ssl = null;
-        ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
-        return ssl;
+        return (SSLSocket) SSLSocketFactory.getDefault().createSocket();
     }
 
     private SSLSocket getSSLSocket(InetAddress host, int port) throws IOException {
-        SSLSocket ssl = null;
-        ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
-        return ssl;
+        return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
     }
 
     private SSLSocket getSSLSocket(String host, int port) throws UnknownHostException, IOException {
-        SSLSocket ssl = null;
-        ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
-        return ssl;
+        return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port);
     }
 
-    private SSLSocket getSSLSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
-        SSLSocket ssl = null;
-        ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port, localHost, localPort);
-        return ssl;
+    private SSLSocket getSSLSocket(InetAddress host, int port, InetAddress localHost, int localPort)
+            throws IOException {
+        return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host,
+                                                                      port,
+                                                                      localHost,
+                                                                      localPort);
     }
 
-    private SSLSocket getSSLSocket(String host, int port, InetAddress localHost, int localPort) throws UnknownHostException, IOException {
-        SSLSocket ssl = null;
-        ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port, localHost, localPort);
-        return ssl;
+    private SSLSocket getSSLSocket(String host, int port, InetAddress localHost, int localPort)
+            throws UnknownHostException, IOException {
+        return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host,
+                                                                      port,
+                                                                      localHost,
+                                                                      localPort);
     }
 }
diff --git a/run-libcore-tests b/run-libcore-tests
new file mode 100755
index 0000000..a1a66ae
--- /dev/null
+++ b/run-libcore-tests
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# make sure there's a vogar on the path, but prefer the user's one.
+export PATH=$PATH:~dalvik-prebuild/vogar/bin
+
+VOGAR="vogar $VOGAR_FLAGS $*"
+
+# We enumerate the test packages for vogar rather than just giving it the classes.jar
+# so hundreds of packages can be tested in parallel, rather than one big jar file serially.
+all_test_packages=$(find libcore/*/src/test -name "*.java" | \
+  fgrep -v junit | \
+  fgrep -v org/w3c/domts | \
+  xargs grep -h '^package ' | sed 's/^package //' | sed 's/;$//' | sort | uniq | tr "\n" " ")
+
+echo "Running tests for following test packages:"
+echo $all_test_packages | tr " " "\n"
+
+$VOGAR \
+  --vm-arg -Xmx32M \
+  --classpath out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/javalib.jar \
+  --classpath out/target/common/obj/JAVA_LIBRARIES/sqlite-jdbc_intermediates/classes.jar \
+  $all_test_packages \
+  tests.api.org.w3c.dom \
+  || true
diff --git a/support/src/test/java/libcore/net/http/HttpResponseCache.java b/support/src/test/java/libcore/net/http/HttpResponseCache.java
deleted file mode 100644
index a551e27..0000000
--- a/support/src/test/java/libcore/net/http/HttpResponseCache.java
+++ /dev/null
@@ -1,300 +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 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/tests/http/MockWebServer.java b/support/src/test/java/tests/http/MockWebServer.java
index 2d8215c..a130dde 100644
--- a/support/src/test/java/tests/http/MockWebServer.java
+++ b/support/src/test/java/tests/http/MockWebServer.java
@@ -251,9 +251,7 @@
                 Socket socket;
                 if (sslSocketFactory != null) {
                     if (tunnelProxy) {
-                        if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
-                            throw new IllegalStateException("Tunnel without any CONNECT!");
-                        }
+                        createTunnel();
                     }
                     socket = sslSocketFactory.createSocket(
                             raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
@@ -283,6 +281,22 @@
             }
 
             /**
+             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
+             * is dispatched.
+             */
+            private void createTunnel() throws IOException, InterruptedException {
+                while (true) {
+                    MockResponse connect = responseQueue.peek();
+                    if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
+                        throw new IllegalStateException("Tunnel without any CONNECT!");
+                    }
+                    if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
+                        return;
+                    }
+                }
+            }
+
+            /**
              * Reads a request and writes its response. Returns true if a request
              * was processed.
              */
diff --git a/support/src/test/java/tests/http/SocketPolicy.java b/support/src/test/java/tests/http/SocketPolicy.java
index 549c65d..820b400 100644
--- a/support/src/test/java/tests/http/SocketPolicy.java
+++ b/support/src/test/java/tests/http/SocketPolicy.java
@@ -34,6 +34,12 @@
     DISCONNECT_AT_END,
 
     /**
+     * Wrap the socket with SSL at the completion of this request/response
+     * pair. Used for CONNECT messages to tunnel SSL over an HTTP proxy.
+     */
+    UPGRADE_TO_SSL_AT_END,
+
+    /**
      * Request immediate close of connection without even reading the
      * request.
      *
diff --git a/support/src/test/java/tests/io/MockOs.java b/support/src/test/java/tests/io/MockOs.java
new file mode 100644
index 0000000..0cfe836
--- /dev/null
+++ b/support/src/test/java/tests/io/MockOs.java
@@ -0,0 +1,120 @@
+/*
+ * 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 tests.io;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.Os;
+import libcore.io.OsConstants;
+
+/**
+ * A mocking interceptor that wraps another {@link Os} to add faults. This can
+ * be useful to test otherwise hard-to-test scenarios such as a full disk.
+ */
+public final class MockOs {
+    private final InheritableThreadLocal<Map<String, Deque<InvocationHandler>>> handlers
+            = new InheritableThreadLocal<Map<String, Deque<InvocationHandler>>>() {
+        @Override protected Map<String, Deque<InvocationHandler>> initialValue() {
+            return new HashMap<String, Deque<InvocationHandler>>();
+        }
+    };
+
+    private Os delegate;
+    private final InvocationHandler delegateHandler = new InvocationHandler() {
+        @Override public Object invoke(Object o, Method method, Object[] args) throws Throwable {
+            try {
+                return method.invoke(delegate, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    };
+
+    private final InvocationHandler invocationHandler = new InvocationHandler() {
+        @Override public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            InvocationHandler handler = getHandlers(method.getName()).poll();
+            if (handler == null) {
+                handler = delegateHandler;
+            }
+            return handler.invoke(proxy, method, args);
+        }
+    };
+
+    private final Os mockOs = (Os) Proxy.newProxyInstance(MockOs.class.getClassLoader(),
+            new Class[] { Os.class }, invocationHandler);
+
+    public void install() {
+        if (delegate != null) {
+            throw new IllegalStateException("MockOs already installed!");
+        }
+        delegate = Libcore.os;
+        Libcore.os = mockOs;
+    }
+
+    public void uninstall() {
+        if (delegate == null) {
+            throw new IllegalStateException("MockOs not installed!");
+        }
+        Libcore.os = delegate;
+    }
+
+    /**
+     * Returns the invocation handlers to handle upcoming invocations of
+     * {@code methodName}. If empty, calls will be handled by the delegate.
+     */
+    public Deque<InvocationHandler> getHandlers(String methodName) {
+        Map<String, Deque<InvocationHandler>> threadFaults = handlers.get();
+        Deque<InvocationHandler> result = threadFaults.get(methodName);
+        if (result == null) {
+            result = new ArrayDeque<InvocationHandler>();
+            threadFaults.put(methodName, result);
+        }
+        return result;
+    }
+
+    /**
+     * Enqueues the specified number of normal operations. Useful to delay
+     * faults.
+     */
+    public void enqueueNormal(String methodName, int count) {
+        Deque<InvocationHandler> handlers = getHandlers(methodName);
+        for (int i = 0; i < count; i++) {
+            handlers.add(delegateHandler);
+        }
+    }
+
+    public void enqueueFault(String methodName) {
+        enqueueFault(methodName, OsConstants.EIO);
+    }
+
+    public void enqueueFault(String methodName, final int errno) {
+        getHandlers(methodName).add(new InvocationHandler() {
+            @Override public Object invoke(Object proxy, Method method, Object[] args) {
+                throw new ErrnoException(method.getName(), errno);
+            }
+        });
+    }
+}
diff --git a/support/src/test/java/tests/net/StuckServer.java b/support/src/test/java/tests/net/StuckServer.java
index 4230f17..eababce 100644
--- a/support/src/test/java/tests/net/StuckServer.java
+++ b/support/src/test/java/tests/net/StuckServer.java
@@ -40,6 +40,24 @@
         }
     }
 
+    public void unblockAfterMs(final int ms) {
+        Thread t = new Thread(new Runnable() {
+            @Override public void run() {
+                try {
+                    Thread.sleep(ms);
+                    for (Socket client : clients) {
+                        client.close();
+                    }
+                    clients.clear();
+                    clients.add(serverSocket.accept());
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+            }
+        });
+        t.start();
+    }
+
     public InetSocketAddress getLocalSocketAddress() {
         return (InetSocketAddress) serverSocket.getLocalSocketAddress();
     }
diff --git a/support/src/test/java/tests/support/Support_Configuration.java b/support/src/test/java/tests/support/Support_Configuration.java
index fb5b468..fed0bc8 100644
--- a/support/src/test/java/tests/support/Support_Configuration.java
+++ b/support/src/test/java/tests/support/Support_Configuration.java
@@ -46,8 +46,6 @@
 
     public static String HomeAddressSoftware = "Jetty(6.0.x)";
 
-    public static String ProxyServerTestHost = "jcltest.apache.org";
-
     public static String SocksServerTestHost = "jcltest.apache.org";
 
     public static int SocksServerTestPort = 1080;
@@ -75,15 +73,7 @@
 
     public static byte[] InetTestCaddr = { 9, 26, -56, -111 };
 
-    public static final String HomeAddress6 = "jcltest6.apache.org";
-
-    public static String IPv6GlobalAddressJcl4 = "FE80:0000:0000:0000:020D:60FF:FE0F:A776%4"; // this
-
-    public static String ProxyServerTestHostIPv6 = "jcltest6.apache.org";
-
-    public static String InetTestIP6 = "fe80::20d:60ff:fe24:7410";
-
-    public static String InetTestIP6LO = "::1";
+    public static String IPv6GlobalAddressJcl4 = "2001:4860:8004::67"; // ipv6.google.com
 
     // ip address that resolves to a host that is not present on the local
     // network
@@ -131,12 +121,6 @@
 
     public static long URLConnectionDate = 929106872000L;
 
-    public static boolean RunCommTests = false;
-
-    public static String Port1 = "COM1";
-
-    public static String Port2 = "COM2";
-
     static Hashtable<String, String> props = null;
     static {
         loadProperties();
@@ -149,9 +133,6 @@
         Hashtable<String, String> props = new Hashtable<String, String>();
 
         String iniName = System.getProperty("test.ini.file", "JCLAuto.ini");
-        if (System.getProperty("test.comm") != null) {
-            RunCommTests = true;
-        }
 
         try {
             in = new FileInputStream(iniName);
@@ -204,11 +185,6 @@
             HomeAddressSoftware = value;
         }
 
-        value = props.get("ProxyServerTestHost");
-        if (value != null) {
-            ProxyServerTestHost = value;
-        }
-
         value = props.get("SocksServerTestHost");
         if (value != null) {
             SocksServerTestHost = value;
@@ -313,31 +289,6 @@
             URLConnectionDate = Long.parseLong(value);
         }
 
-        value = props.get("Port1");
-        if (value != null) {
-            Port1 = value;
-        }
-
-        value = props.get("Port2");
-        if (value != null) {
-            Port2 = value;
-        }
-
-        value = props.get("InetTestIP6");
-        if (value != null) {
-            InetTestIP6 = value;
-        }
-
-        value = props.get("InetTestIP6LO");
-        if (value != null) {
-            InetTestIP6LO = value;
-        }
-
-        value = props.get("ProxyServerTestHostIPv6");
-        if (value != null) {
-            ProxyServerTestHostIPv6 = value;
-        }
-
         value = props.get("ResolvedNotExistingHost");
         if (value != null) {
             ResolvedNotExistingHost = value;