am 160dd055: am 4beeee87: Revert "Remove support for duplicate file entries."

* commit '160dd0552b2df159dec511715ed48c4420305fdb':
  Revert "Remove support for duplicate file entries."
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 59ac2e1..26a586a 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -55,9 +55,11 @@
 test_resource_dirs := $(call all-core-resource-dirs,test)
 
 ifeq ($(EMMA_INSTRUMENT),true)
+ifneq ($(EMMA_INSTRUMENT_STATIC),true)
     core_src_files += $(call all-java-files-under, ../external/emma/core ../external/emma/pregenerated)
     core_resource_dirs += ../external/emma/core/res ../external/emma/pregenerated/res
 endif
+endif
 
 local_javac_flags=-encoding UTF-8
 #local_javac_flags+=-Xlint:all -Xlint:-serial,-deprecation,-unchecked
@@ -78,8 +80,6 @@
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_DX_FLAGS := --core-library
 
-LOCAL_NO_EMMA_INSTRUMENT := true
-LOCAL_NO_EMMA_COMPILE := true
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := core
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
@@ -100,8 +100,6 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := core-tests
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
-LOCAL_NO_EMMA_INSTRUMENT := true
-LOCAL_NO_EMMA_COMPILE := true
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # This one's tricky. One of our tests needs to have a
@@ -136,8 +134,6 @@
     LOCAL_JAVACFLAGS := $(local_javac_flags)
     LOCAL_DX_FLAGS := --core-library
 
-    LOCAL_NO_EMMA_INSTRUMENT := true
-    LOCAL_NO_EMMA_COMPILE := true
     LOCAL_BUILD_HOST_DEX := true
 
     LOCAL_MODULE_TAGS := optional
@@ -157,8 +153,6 @@
     LOCAL_MODULE_TAGS := optional
     LOCAL_MODULE := core-tests-hostdex
     LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
-    LOCAL_NO_EMMA_INSTRUMENT := true
-    LOCAL_NO_EMMA_COMPILE := true
     LOCAL_BUILD_HOST_DEX := true
     include $(BUILD_HOST_JAVA_LIBRARY)
 endif
diff --git a/dalvik/src/main/java/dalvik/system/Zygote.java b/dalvik/src/main/java/dalvik/system/Zygote.java
index ec114ed..c06314e 100644
--- a/dalvik/src/main/java/dalvik/system/Zygote.java
+++ b/dalvik/src/main/java/dalvik/system/Zygote.java
@@ -41,6 +41,15 @@
     /** Enable logging of third-party JNI activity. */
     public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
 
+    /** No external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_NONE = 0;
+    /** Single-user external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_SINGLEUSER = 1;
+    /** Multi-user external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_MULTIUSER = 2;
+    /** All multi-user external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3;
+
     /**
      * When set by the system server, all subsequent apps will be launched in
      * VM safe mode.
@@ -114,27 +123,17 @@
      * @return 0 if this is the child, pid of the child
      * if this is the parent, or -1 on error.
      */
-    public static int forkAndSpecialize(int uid, int gid, int[] gids,
-            int debugFlags, int[][] rlimits, String seInfo, String niceName) {
+    public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
+            int[][] rlimits, int mountExternal, String seInfo, String niceName) {
         preFork();
-        int pid = nativeForkAndSpecialize(uid, gid, gids, debugFlags, rlimits, seInfo, niceName);
+        int pid = nativeForkAndSpecialize(
+                uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName);
         postFork();
         return pid;
     }
 
-    native public static int nativeForkAndSpecialize(int uid, int gid,
-            int[] gids, int debugFlags, int[][] rlimits, String seInfo, String niceName);
-
-    /**
-     * Forks a new VM instance.
-     * @deprecated use {@link Zygote#forkAndSpecialize(int, int, int[], int, int[][])}
-     */
-    @Deprecated
-    public static int forkAndSpecialize(int uid, int gid, int[] gids,
-            boolean enableDebugger, int[][] rlimits) {
-        int debugFlags = enableDebugger ? DEBUG_ENABLE_DEBUGGER : 0;
-        return forkAndSpecialize(uid, gid, gids, debugFlags, rlimits, null, null);
-    }
+    native public static int nativeForkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
+            int[][] rlimits, int mountExternal, String seInfo, String niceName);
 
     /**
      * Special method to start the system server process. In addition to the
@@ -159,31 +158,17 @@
      * @return 0 if this is the child, pid of the child
      * if this is the parent, or -1 on error.
      */
-    public static int forkSystemServer(int uid, int gid, int[] gids,
-            int debugFlags, int[][] rlimits,
-            long permittedCapabilities, long effectiveCapabilities) {
+    public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags,
+            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
         preFork();
-        int pid = nativeForkSystemServer(uid, gid, gids, debugFlags, rlimits,
-                                         permittedCapabilities,
-                                         effectiveCapabilities);
+        int pid = nativeForkSystemServer(
+                uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities);
         postFork();
         return pid;
     }
 
-    /**
-     * Special method to start the system server process.
-     * @deprecated use {@link Zygote#forkSystemServer(int, int, int[], int, int[][])}
-     */
-    @Deprecated
-    public static int forkSystemServer(int uid, int gid, int[] gids,
-            boolean enableDebugger, int[][] rlimits) {
-        int debugFlags = enableDebugger ? DEBUG_ENABLE_DEBUGGER : 0;
-        return forkAndSpecialize(uid, gid, gids, debugFlags, rlimits, null, null);
-    }
-
-    native public static int nativeForkSystemServer(int uid, int gid,
-            int[] gids, int debugFlags, int[][] rlimits,
-            long permittedCapabilities, long effectiveCapabilities);
+    native public static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags,
+            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
 
     /**
      * Executes "/system/bin/sh -c &lt;command&gt;" using the exec() system call.
diff --git a/expectations/brokentests.txt b/expectations/brokentests.txt
index 6fc48cd..e1d81e0 100644
--- a/expectations/brokentests.txt
+++ b/expectations/brokentests.txt
@@ -8,7 +8,12 @@
   bug: 5834665
 },
 {
-  description: "libcore.java.net.URLConnectionTest#testServerShutdownInput fails on ICL27 mysid-userdebug",
+  description: "FIONREAD/SIOCINQ returns the wrong result on sockets (5731252 is the root cause of 5534202)",
+  name: "libcore.java.net.SocketTest#testAvailable",
+  bug: 5731252
+},
+{
+  description: "libcore.java.net.URLConnectionTest#testServerShutdownInput fails on ICL27 mysid-userdebug (5534202 is caused by 5731252)",
   name: "libcore.java.net.URLConnectionTest#testServerShutdownInput",
   bug: 5534202
 },
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index 972f1ef..5e24be3 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -194,15 +194,6 @@
   bug: 2400429
 },
 {
-  description: "Concurrent close tests fail on the device",
-  names: [
-    "libcore.java.net.ConcurrentCloseTest#test_connect",
-    "libcore.java.net.ConcurrentCloseTest#test_connect_nonBlocking"
-  ],
-  modes: [ "device" ],
-  bug: 3044772
-},
-{
   description: "HTTPS connections should not be pooled.",
   name: "libcore.java.net.URLConnectionTest#testConnectViaHttpsReusingConnectionsDifferentFactories",
   bug: 3042192
diff --git a/luni/src/main/java/java/lang/Object.java b/luni/src/main/java/java/lang/Object.java
index 7f4b490..4bca034 100644
--- a/luni/src/main/java/java/lang/Object.java
+++ b/luni/src/main/java/java/lang/Object.java
@@ -361,7 +361,7 @@
      * @see java.lang.Thread
      */
     public final void wait() throws InterruptedException {
-        wait(0 ,0);
+        wait(0, 0);
     }
 
     /**
diff --git a/luni/src/main/java/java/nio/PipeImpl.java b/luni/src/main/java/java/nio/PipeImpl.java
index 50028f4..d4dde3b 100644
--- a/luni/src/main/java/java/nio/PipeImpl.java
+++ b/luni/src/main/java/java/nio/PipeImpl.java
@@ -19,8 +19,8 @@
 import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.nio.channels.FileChannel;
 import java.nio.channels.Pipe;
+import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 import libcore.io.ErrnoException;
 import libcore.io.IoUtils;
@@ -34,13 +34,16 @@
     private final PipeSinkChannel sink;
     private final PipeSourceChannel source;
 
-    public PipeImpl() throws IOException {
+    public PipeImpl(SelectorProvider selectorProvider) throws IOException {
         try {
-            FileDescriptor[] fds = Libcore.os.pipe();
-            // Which fd is used for which channel is important. Unix pipes are only guaranteed to be
-            // unidirectional, and indeed are only unidirectional on Linux.
-            this.sink = new PipeSinkChannel(fds[1]);
-            this.source = new PipeSourceChannel(fds[0]);
+            FileDescriptor fd1 = new FileDescriptor();
+            FileDescriptor fd2 = new FileDescriptor();
+            Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd1, fd2);
+
+            // It doesn't matter which file descriptor we use for which end;
+            // they're guaranteed to be indistinguishable.
+            this.sink = new PipeSinkChannel(selectorProvider, fd1);
+            this.source = new PipeSourceChannel(selectorProvider, fd2);
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
         }
@@ -54,27 +57,14 @@
         return source;
     }
 
-    /**
-     * FileChannelImpl doesn't close its fd itself; it calls close on the object it's given.
-     */
-    private static class FdCloser implements Closeable {
-        private final FileDescriptor fd;
-        private FdCloser(FileDescriptor fd) {
-            this.fd = fd;
-        }
-        public void close() throws IOException {
-            IoUtils.close(fd);
-        }
-    }
-
     private class PipeSourceChannel extends Pipe.SourceChannel implements FileDescriptorChannel {
         private final FileDescriptor fd;
-        private final FileChannel channel;
+        private final SocketChannel channel;
 
-        private PipeSourceChannel(FileDescriptor fd) throws IOException {
-            super(SelectorProvider.provider());
+        private PipeSourceChannel(SelectorProvider selectorProvider, FileDescriptor fd) throws IOException {
+            super(selectorProvider);
             this.fd = fd;
-            this.channel = NioUtils.newFileChannel(new FdCloser(fd), fd, O_RDONLY);
+            this.channel = new SocketChannelImpl(selectorProvider, fd);
         }
 
         @Override protected void implCloseSelectableChannel() throws IOException {
@@ -104,12 +94,12 @@
 
     private class PipeSinkChannel extends Pipe.SinkChannel implements FileDescriptorChannel {
         private final FileDescriptor fd;
-        private final FileChannel channel;
+        private final SocketChannel channel;
 
-        private PipeSinkChannel(FileDescriptor fd) throws IOException {
-            super(SelectorProvider.provider());
+        private PipeSinkChannel(SelectorProvider selectorProvider, FileDescriptor fd) throws IOException {
+            super(selectorProvider);
             this.fd = fd;
-            this.channel = NioUtils.newFileChannel(new FdCloser(fd), fd, O_WRONLY);
+            this.channel = new SocketChannelImpl(selectorProvider, fd);
         }
 
         @Override protected void implCloseSelectableChannel() throws IOException {
diff --git a/luni/src/main/java/java/nio/SelectorProviderImpl.java b/luni/src/main/java/java/nio/SelectorProviderImpl.java
index 03003fe..b7c34e8 100644
--- a/luni/src/main/java/java/nio/SelectorProviderImpl.java
+++ b/luni/src/main/java/java/nio/SelectorProviderImpl.java
@@ -34,7 +34,7 @@
     }
 
     public Pipe openPipe() throws IOException {
-        return new PipeImpl();
+        return new PipeImpl(this);
     }
 
     public AbstractSelector openSelector() throws IOException {
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index ff2c157..233daf0 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -103,6 +103,15 @@
     }
 
     /*
+     * Constructor for use by Pipe.SinkChannel and Pipe.SourceChannel.
+     */
+    public SocketChannelImpl(SelectorProvider selectorProvider, FileDescriptor existingFd) throws IOException {
+        super(selectorProvider);
+        status = SOCKET_STATUS_CONNECTED;
+        fd = existingFd;
+    }
+
+    /*
      * Getting the internal Socket If we have not the socket, we create a new
      * one.
      */
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index 6b20a1c..0fbe2f5 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -396,6 +396,17 @@
         if (languageCode.isEmpty()) {
             return "";
         }
+
+        // Last-minute workaround for http://b/7291355 in jb-mr1.
+        // This isn't right for all languages, but it's right for en and tl.
+        // We should have more CLDR data in a future release, but we'll still
+        // probably want to have frameworks/base translate the obsolete tl and
+        // tl-rPH locales to fil and fil-rPH at runtime, at which point
+        // libcore and icu4c will just do the right thing.
+        if (languageCode.equals("tl")) {
+            return "Filipino";
+        }
+
         String result = ICU.getDisplayLanguageNative(toString(), locale.toString());
         if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
             result = ICU.getDisplayLanguageNative(toString(), Locale.getDefault().toString());
diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java
index 61c9765..c61a3cf 100644
--- a/luni/src/main/java/libcore/io/BlockGuardOs.java
+++ b/luni/src/main/java/libcore/io/BlockGuardOs.java
@@ -181,6 +181,12 @@
         return tagSocket(os.socket(domain, type, protocol));
     }
 
+    @Override public void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException {
+        os.socketpair(domain, type, protocol, fd1, fd2);
+        tagSocket(fd1);
+        tagSocket(fd2);
+    }
+
     @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         BlockGuard.getThreadPolicy().onWriteToDisk();
         return os.write(fd, buffer);
diff --git a/luni/src/main/java/libcore/io/DropBox.java b/luni/src/main/java/libcore/io/DropBox.java
new file mode 100644
index 0000000..cf88106
--- /dev/null
+++ b/luni/src/main/java/libcore/io/DropBox.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.io;
+
+public final class DropBox {
+
+    /**
+     * Hook for customizing how events are reported.
+     */
+    private static volatile Reporter REPORTER = new DefaultReporter();
+
+    /**
+     * Used to replace default Reporter for logging events. Must be non-null.
+     */
+    public static void setReporter(Reporter reporter) {
+        if (reporter == null) {
+            throw new NullPointerException("reporter == null");
+        }
+        REPORTER = reporter;
+    }
+
+    /**
+     * Returns non-null Reporter.
+     */
+    public static Reporter getReporter() {
+        return REPORTER;
+    }
+
+    /**
+     * Interface to allow customization of reporting behavior.
+     */
+    public static interface Reporter {
+        public void addData(String tag, byte[] data, int flags);
+        public void addText(String tag, String data);
+    }
+
+    /**
+     * Default Reporter which reports events to the log.
+     */
+    private static final class DefaultReporter implements Reporter {
+
+        public void addData(String tag, byte[] data, int flags) {
+            System.out.println(tag + ": " + Base64.encode(data));
+        }
+
+        public void addText(String tag, String data) {
+            System.out.println(tag + ": " + data);
+        }
+    }
+
+    public static void addData(String tag, byte[] data, int flags) {
+        getReporter().addData(tag, data, flags);
+    }
+
+    public static void addText(String tag, String data) {
+        getReporter().addText(tag, data);
+    }
+}
diff --git a/luni/src/main/java/libcore/io/EventLogger.java b/luni/src/main/java/libcore/io/EventLogger.java
new file mode 100644
index 0000000..9709cc9
--- /dev/null
+++ b/luni/src/main/java/libcore/io/EventLogger.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.io;
+
+public final class EventLogger {
+
+    /**
+     * Hook for customizing how events are reported.
+     */
+    private static volatile Reporter REPORTER = new DefaultReporter();
+
+    /**
+     * Used to replace default Reporter for logging events. Must be non-null.
+     */
+    public static void setReporter(Reporter reporter) {
+        if (reporter == null) {
+            throw new NullPointerException("reporter == null");
+        }
+        REPORTER = reporter;
+    }
+
+    /**
+     * Returns non-null Reporter.
+     */
+    public static Reporter getReporter() {
+        return REPORTER;
+    }
+
+    /**
+     * Interface to allow customization of reporting behavior.
+     */
+    public static interface Reporter {
+        public void report (int code, Object... list);
+    }
+
+    /**
+     * Default Reporter which reports events to the log.
+     */
+    private static final class DefaultReporter implements Reporter {
+        @Override
+        public void report (int code, Object... list) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(code);
+            for (Object o : list) {
+                sb.append(",");
+                sb.append(o.toString());
+            }
+            System.out.println(sb);
+        }
+    }
+
+    public static void writeEvent(int code, Object... list) {
+        getReporter().report(code, list);
+    }
+}
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 719c5f0..ee26d04 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -121,6 +121,7 @@
     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 void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { os.socketpair(domain, type, protocol, fd1, fd2); }
     public StructStat stat(String path) throws ErrnoException { return os.stat(path); }
     public StructStatFs statfs(String path) throws ErrnoException { return os.statfs(path); }
     public String strerror(int errno) { return os.strerror(errno); }
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 55588d8..1f42497 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -114,6 +114,7 @@
     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 void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException;
     public StructStat stat(String path) throws ErrnoException;
     /* TODO: replace statfs with statvfs. */
     public StructStatFs statfs(String path) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index ad01bcf..2fb187e 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -165,6 +165,7 @@
     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 void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException;
     public native StructStat stat(String path) throws ErrnoException;
     public native StructStatFs statfs(String path) throws ErrnoException;
     public native String strerror(int errno);
diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java
index a370956..42c9b96 100644
--- a/luni/src/main/java/libcore/net/http/HttpEngine.java
+++ b/luni/src/main/java/libcore/net/http/HttpEngine.java
@@ -491,8 +491,15 @@
                 reusable = false;
             }
 
-            // If the headers specify that the connection shouldn't be reused, don't reuse it.
-            if (hasConnectionCloseHeader()) {
+            // If the request specified that the connection shouldn't be reused,
+            // don't reuse it. This advice doesn't apply to CONNECT requests because
+            // the "Connection: close" header goes the origin server, not the proxy.
+            if (requestHeaders.hasConnectionClose() && method != CONNECT) {
+                reusable = false;
+            }
+
+            // If the response specified that the connection shouldn't be reused, don't reuse it.
+            if (responseHeaders != null && responseHeaders.hasConnectionClose()) {
                 reusable = false;
             }
 
@@ -524,8 +531,13 @@
             /*
              * If the response was transparently gzipped, remove the gzip header field
              * so clients don't double decompress. http://b/3009828
+             *
+             * Also remove the Content-Length in this case because it contains the length
+             * of the gzipped response. This isn't terribly useful and is dangerous because
+             * clients can query the content length, but not the content encoding.
              */
             responseHeaders.stripContentEncoding();
+            responseHeaders.stripContentLength();
             responseBodyIn = new GZIPInputStream(transferStream);
         } else {
             responseBodyIn = transferStream;
@@ -759,11 +771,6 @@
         return agent != null ? agent : ("Java" + System.getProperty("java.version"));
     }
 
-    private boolean hasConnectionCloseHeader() {
-        return (responseHeaders != null && responseHeaders.hasConnectionClose())
-                || requestHeaders.hasConnectionClose();
-    }
-
     protected final String getOriginAddress(URL url) {
         int port = url.getPort();
         String result = url.getHost();
diff --git a/luni/src/main/java/libcore/net/http/ResponseHeaders.java b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
index 003b445..c0b4200 100644
--- a/luni/src/main/java/libcore/net/http/ResponseHeaders.java
+++ b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
@@ -187,6 +187,11 @@
         headers.removeAll("Content-Encoding");
     }
 
+    public void stripContentLength() {
+        contentLength = -1;
+        headers.removeAll("Content-Length");
+    }
+
     public boolean isChunked() {
         return "chunked".equalsIgnoreCase(transferEncoding);
     }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java
new file mode 100644
index 0000000..4cdd8d2
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.DefaultHostnameVerifier;
+import libcore.io.IoUtils;
+import libcore.util.BasicLruCache;
+
+/**
+ * This class provides a simple interface for cert pinning.
+ */
+public class CertPinManager {
+
+    private long lastModified;
+
+    private final Map<String, PinListEntry> entries = new HashMap<String, PinListEntry>();
+    private final BasicLruCache<String, String> hostnameCache = new BasicLruCache<String, String>(10);
+    private final DefaultHostnameVerifier verifier = new DefaultHostnameVerifier();
+
+    private boolean initialized = false;
+    private static final boolean DEBUG = false;
+
+    private final File pinFile;
+    private final TrustedCertificateStore certStore;
+
+    public CertPinManager(TrustedCertificateStore store) throws PinManagerException {
+        pinFile = new File("/data/misc/keychain/pins");
+        certStore = store;
+        rebuild();
+    }
+
+    /** Test only */
+    public CertPinManager(String path, TrustedCertificateStore store) throws PinManagerException {
+        if (path == null) {
+            throw new NullPointerException("path == null");
+        }
+        pinFile = new File(path);
+        certStore = store;
+        rebuild();
+    }
+
+    /**
+     * This is the public interface for cert pinning.
+     *
+     * Given a hostname and a certificate chain this verifies that the chain includes
+     * certs from the pinned list provided.
+     *
+     * If the chain doesn't include those certs and is in enforcing mode, then this method
+     * returns true and the certificate check should fail.
+     */
+    public boolean chainIsNotPinned(String hostname, List<X509Certificate> chain)
+            throws PinManagerException {
+        // lookup the entry
+        PinListEntry entry = lookup(hostname);
+
+        // return its result or false if there's no pin
+        if (entry != null) {
+            return entry.chainIsNotPinned(chain);
+        }
+        return false;
+    }
+
+    private synchronized void rebuild() throws PinManagerException {
+        // reread the pin file
+        String pinFileContents = readPinFile();
+
+        if (pinFileContents != null) {
+            // rebuild the pinned certs
+            for (String entry : getPinFileEntries(pinFileContents)) {
+                try {
+                    PinListEntry pin = new PinListEntry(entry, certStore);
+                    entries.put(pin.getCommonName(), pin);
+                } catch (PinEntryException e) {
+                    log("Pinlist contains a malformed pin: " + entry, e);
+                }
+            }
+
+            // clear the cache
+            hostnameCache.evictAll();
+
+            // set the last modified time
+            lastModified = pinFile.lastModified();
+
+            // we've been fully initialized and are ready to go
+            initialized = true;
+        }
+    }
+
+    private String readPinFile() throws PinManagerException {
+        try {
+            return IoUtils.readFileAsString(pinFile.getPath());
+        } catch (FileNotFoundException e) {
+            // there's no pin list, all certs are unpinned
+            return null;
+        } catch (IOException e) {
+            // this is unexpected, fail
+            throw new PinManagerException("Unexpected error reading pin list; failing.", e);
+        }
+    }
+
+    private static String[] getPinFileEntries(String pinFileContents) {
+        return pinFileContents.split("\n");
+    }
+
+    private synchronized PinListEntry lookup(String hostname) throws PinManagerException {
+
+        // if we don't have any data, don't bother
+        if (!initialized) {
+            return null;
+        }
+
+        // check to see if our cache is valid
+        if (cacheIsNotValid()) {
+            rebuild();
+        }
+
+        // if so, check the hostname cache
+        String cn = hostnameCache.get(hostname);
+        if (cn != null) {
+            // if we hit, return the corresponding entry
+            return entries.get(cn);
+        }
+
+        // otherwise, get the matching cn
+        cn = getMatchingCN(hostname);
+        if (cn != null) {
+            hostnameCache.put(hostname, cn);
+            // we have a matching CN, return that entry
+            return entries.get(cn);
+        }
+
+        // if we got here, we don't have a matching CN for this hostname
+        return null;
+    }
+
+    private boolean cacheIsNotValid() {
+        return pinFile.lastModified() != lastModified;
+    }
+
+    private String getMatchingCN(String hostname) {
+        String bestMatch = "";
+        for (String cn : entries.keySet()) {
+            // skip shorter CNs since they can't be better matches
+            if (cn.length() < bestMatch.length()) {
+                continue;
+            }
+            // now verify that the CN matches at all
+            if (verifier.verifyHostName(hostname, cn)) {
+                bestMatch = cn;
+            }
+        }
+        return bestMatch;
+    }
+
+    private static void log(String s, Exception e) {
+        if (DEBUG) {
+            System.out.println("PINFILE: " + s);
+            if (e != null) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
index 83f86ae..c855c0c 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
@@ -37,6 +37,7 @@
 import javax.crypto.spec.SecretKeySpec;
 import javax.net.ssl.X509ExtendedKeyManager;
 import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
 
 /**
@@ -88,7 +89,7 @@
             if (engineOwner != null) {
                 session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
             } else {
-                session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort());
+                session.setPeer(socketOwner.getPeerHostName(), socketOwner.getPeerPort());
             }
             session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols());
             recordProtocol.setVersion(session.protocol.version);
@@ -109,7 +110,7 @@
             if (engineOwner != null) {
                 session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
             } else {
-                session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort());
+                session.setPeer(socketOwner.getPeerHostName(), socketOwner.getPeerPort());
             }
             session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols());
             recordProtocol.setVersion(session.protocol.version);
@@ -527,8 +528,21 @@
         if (authType == null) {
             return;
         }
+        String hostname = null;
+        if (engineOwner != null) {
+            hostname = engineOwner.getPeerHost();
+        } else {
+            // we don't want to do an inet address lookup here in case we're talking to a proxy
+            hostname = socketOwner.getWrappedHostName();
+        }
         try {
-            parameters.getTrustManager().checkServerTrusted(serverCert.certs, authType);
+            X509TrustManager x509tm = parameters.getTrustManager();
+            if (x509tm instanceof TrustManagerImpl) {
+                TrustManagerImpl tm = (TrustManagerImpl) x509tm;
+                tm.checkServerTrusted(serverCert.certs, authType, hostname);
+            } else {
+                x509tm.checkServerTrusted(serverCert.certs, authType);
+            }
         } catch (CertificateException e) {
             fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e);
             return;
@@ -559,8 +573,8 @@
             host = engineOwner.getPeerHost();
             port = engineOwner.getPeerPort();
         } else {
-            host = socketOwner.getInetAddress().getHostName();
-            port = socketOwner.getPort();
+            host = socketOwner.getPeerHostName();
+            port = socketOwner.getPeerPort();
         }
         if (host == null || port == -1) {
             return null; // starts new session
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
index 84f33f2..65373ff 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
@@ -78,6 +78,8 @@
 
     public static native void EVP_PKEY_free(int pkey);
 
+    public static native int EVP_PKEY_cmp(int pkey1, int pkey2);
+
     public static native byte[] i2d_PKCS8_PRIV_KEY_INFO(int pkey);
 
     public static native int d2i_PKCS8_PRIV_KEY_INFO(byte[] data);
@@ -643,7 +645,7 @@
     public static native int SSL_read(int sslNativePointer,
                                       FileDescriptor fd,
                                       SSLHandshakeCallbacks shc,
-                                      byte[] b, int off, int len, int timeoutMillis)
+                                      byte[] b, int off, int len, int readTimeoutMillis)
         throws IOException;
 
     /**
@@ -652,7 +654,7 @@
     public static native void SSL_write(int sslNativePointer,
                                         FileDescriptor fd,
                                         SSLHandshakeCallbacks shc,
-                                        byte[] b, int off, int len)
+                                        byte[] b, int off, int len, int writeTimeoutMillis)
         throws IOException;
 
     public static native void SSL_interrupt(int sslNativePointer);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherRSA.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherRSA.java
index a11ffa9..ddf2e0d 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherRSA.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherRSA.java
@@ -110,16 +110,34 @@
 
     @Override
     protected int engineGetBlockSize() {
-        return 0;
+        if (encrypting) {
+            return paddedBlockSizeBytes();
+        }
+        return keySizeBytes();
     }
 
     @Override
     protected int engineGetOutputSize(int inputLen) {
+        if (encrypting) {
+            return keySizeBytes();
+        }
+        return paddedBlockSizeBytes();
+    }
+
+    private int paddedBlockSizeBytes() {
+        int paddedBlockSizeBytes = keySizeBytes();
+        if (padding == NativeCrypto.RSA_PKCS1_PADDING) {
+            paddedBlockSizeBytes--;  // for 0 prefix
+            paddedBlockSizeBytes -= 10;  // PKCS1 padding header length
+        }
+        return paddedBlockSizeBytes;
+    }
+
+    private int keySizeBytes() {
         if (key == null) {
             throw new IllegalStateException("cipher is not initialized");
         }
-
-        return buffer.length;
+        return NativeCrypto.RSA_size(this.key.getPkeyContext());
     }
 
     @Override
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
index e1d0fb0..d4aa57f 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
@@ -73,33 +73,32 @@
         // put("KeyFactory.DSA", OpenSSLDSAKeyFactory.class.getName());
 
         // Signatures
-        put("Signature.MD5WithRSAEncryption", OpenSSLSignature.MD5RSA.class.getName());
-        put("Alg.Alias.Signature.MD5WithRSA", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.MD5/RSA", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1",
-                "MD5WithRSAEncryption");
+        put("Signature.MD5WithRSA", OpenSSLSignature.MD5RSA.class.getName());
+        put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5WithRSA");
+        put("Alg.Alias.Signature.MD5/RSA", "MD5WithRSA");
+        put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5WithRSA");
+        put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5WithRSA");
 
-        put("Signature.SHA1WithRSAEncryption", OpenSSLSignature.SHA1RSA.class.getName());
-        put("Alg.Alias.Signature.SHA1WithRSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA1/RSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.SHA-1/RSA", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1WithRSAEncryption");
-        put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1WithRSAEncryption");
+        put("Signature.SHA1WithRSA", OpenSSLSignature.SHA1RSA.class.getName());
+        put("Alg.Alias.Signature.SHA1WithRSA", "SHA1WithRSA");
+        put("Alg.Alias.Signature.SHA1/RSA", "SHA1WithRSA");
+        put("Alg.Alias.Signature.SHA-1/RSA", "SHA1WithRSA");
+        put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1WithRSA");
+        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1WithRSA");
+        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1WithRSA");
+        put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1WithRSA");
 
-        put("Signature.SHA256WithRSAEncryption", OpenSSLSignature.SHA256RSA.class.getName());
-        put("Alg.Alias.Signature.SHA256WithRSA", "SHA256WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256WithRSAEncryption");
+        put("Signature.SHA256WithRSA", OpenSSLSignature.SHA256RSA.class.getName());
+        put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256WithRSA");
+        put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256WithRSA");
 
-        put("Signature.SHA384WithRSAEncryption", OpenSSLSignature.SHA384RSA.class.getName());
-        put("Alg.Alias.Signature.SHA384WithRSA", "SHA384WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384WithRSAEncryption");
+        put("Signature.SHA384WithRSA", OpenSSLSignature.SHA384RSA.class.getName());
+        put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384WithRSA");
+        put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384WithRSA");
 
-        put("Signature.SHA512WithRSAEncryption", OpenSSLSignature.SHA512RSA.class.getName());
-        put("Alg.Alias.Signature.SHA512WithRSA", "SHA512WithRSAEncryption");
-        put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512WithRSAEncryption");
+        put("Signature.SHA512WithRSA", OpenSSLSignature.SHA512RSA.class.getName());
+        put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512WithRSA");
+        put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512WithRSA");
 
         put("Signature.SHA1withDSA", OpenSSLSignature.SHA1DSA.class.getName());
         put("Alg.Alias.Signature.SHA/DSA", "SHA1withDSA");
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
index 48e1494..4303e5a 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
@@ -191,8 +191,8 @@
             return true;
         }
 
-        if (o instanceof OpenSSLRSAPrivateCrtKey) {
-            OpenSSLRSAPrivateCrtKey other = (OpenSSLRSAPrivateCrtKey) o;
+        if (o instanceof OpenSSLRSAPrivateKey) {
+            OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
 
             /*
              * We can shortcut the true case, but it still may be equivalent but
@@ -202,28 +202,35 @@
                 return true;
             }
 
-            /*
-             * If this key is ENGINE-based, it could be equivalent to another
-             * ENGINE-based key. The modulus may be equal, but that occurrence
-             * should be so improbably low as to never happen.
-             */
-            if (getOpenSSLKey().isEngineBased() || other.getOpenSSLKey().isEngineBased()) {
-                return publicExponent.equals(other.getPublicExponent())
-                        && getModulus().equals(other.getModulus());
-            }
+            return NativeCrypto.EVP_PKEY_cmp(getPkeyContext(), other.getPkeyContext()) == 1;
         }
 
         if (o instanceof RSAPrivateCrtKey) {
             ensureReadParams();
             RSAPrivateCrtKey other = (RSAPrivateCrtKey) o;
 
-            return getModulus().equals(other.getModulus())
-                    && publicExponent.equals(other.getPublicExponent())
-                    && getPrivateExponent().equals(other.getPrivateExponent())
-                    && primeP.equals(other.getPrimeP()) && primeQ.equals(other.getPrimeQ())
-                    && primeExponentP.equals(other.getPrimeExponentP())
-                    && primeExponentQ.equals(other.getPrimeExponentQ())
-                    && crtCoefficient.equals(other.getCrtCoefficient());
+            if (getOpenSSLKey().isEngineBased()) {
+                return getModulus().equals(other.getModulus())
+                        && publicExponent.equals(other.getPublicExponent());
+            } else {
+                return getModulus().equals(other.getModulus())
+                        && publicExponent.equals(other.getPublicExponent())
+                        && getPrivateExponent().equals(other.getPrivateExponent())
+                        && primeP.equals(other.getPrimeP()) && primeQ.equals(other.getPrimeQ())
+                        && primeExponentP.equals(other.getPrimeExponentP())
+                        && primeExponentQ.equals(other.getPrimeExponentQ())
+                        && crtCoefficient.equals(other.getCrtCoefficient());
+            }
+        } else if (o instanceof RSAPrivateKey) {
+            ensureReadParams();
+            RSAPrivateKey other = (RSAPrivateKey) o;
+
+            if (getOpenSSLKey().isEngineBased()) {
+                return getModulus().equals(other.getModulus());
+            } else {
+                return getModulus().equals(other.getModulus())
+                        && getPrivateExponent().equals(other.getPrivateExponent());
+            }
         }
 
         return false;
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
index 5c2f075..adb05a9 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
@@ -201,14 +201,7 @@
                 return true;
             }
 
-            /*
-             * If this key is ENGINE-based, it could be equivalent to another
-             * ENGINE-based key. The modulus may be equal, but that occurrence
-             * should be so improbably low as to never happen.
-             */
-            if (key.isEngineBased() || other.getOpenSSLKey().isEngineBased()) {
-                return modulus.equals(other.getModulus());
-            }
+            return NativeCrypto.EVP_PKEY_cmp(getPkeyContext(), other.getPkeyContext()) == 1;
         }
 
         if (o instanceof RSAPrivateKey) {
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
index 3720ba2..4cc16e6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
@@ -42,7 +42,11 @@
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
+import static libcore.io.OsConstants.*;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
 import libcore.io.Streams;
+import libcore.io.StructTimeval;
 import org.apache.harmony.security.provider.cert.X509CertImpl;
 
 /**
@@ -93,7 +97,8 @@
      * OpenSSLSocketImplWrapper overrides setSoTimeout and
      * getSoTimeout to delegate to the wrapped socket.
      */
-    private int timeoutMilliseconds = 0;
+    private int readTimeoutMilliseconds = 0;
+    private int writeTimeoutMilliseconds = 0;
 
     private int handshakeTimeoutMilliseconds = -1;  // -1 = same as timeout; 0 = infinite
     private String wrappedHost;
@@ -361,9 +366,11 @@
             }
 
             // Temporarily use a different timeout for the handshake process
-            int savedTimeoutMilliseconds = getSoTimeout();
+            int savedReadTimeoutMilliseconds = getSoTimeout();
+            int savedWriteTimeoutMilliseconds = getSoWriteTimeout();
             if (handshakeTimeoutMilliseconds >= 0) {
                 setSoTimeout(handshakeTimeoutMilliseconds);
+                setSoWriteTimeout(handshakeTimeoutMilliseconds);
             }
 
             int sslSessionNativePointer;
@@ -399,7 +406,8 @@
 
             // Restore the original timeout now that the handshake is complete
             if (handshakeTimeoutMilliseconds >= 0) {
-                setSoTimeout(savedTimeoutMilliseconds);
+                setSoTimeout(savedReadTimeoutMilliseconds);
+                setSoWriteTimeout(savedWriteTimeoutMilliseconds);
             }
 
             // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback
@@ -418,7 +426,7 @@
         }
     }
 
-    private String getPeerHostName() {
+    String getPeerHostName() {
         if (wrappedHost != null) {
             return wrappedHost;
         }
@@ -429,7 +437,7 @@
         return null;
     }
 
-    private int getPeerPort() {
+    int getPeerPort() {
         return wrappedHost == null ? super.getPort() : wrappedPort;
     }
 
@@ -570,8 +578,13 @@
             }
             boolean client = sslParameters.getUseClientMode();
             if (client) {
-                sslParameters.getTrustManager().checkServerTrusted(peerCertificateChain,
-                                                                   authMethod);
+                X509TrustManager x509tm = sslParameters.getTrustManager();
+                if (x509tm instanceof TrustManagerImpl) {
+                    TrustManagerImpl tm = (TrustManagerImpl) x509tm;
+                    tm.checkServerTrusted(peerCertificateChain, authMethod, wrappedHost);
+                } else {
+                    x509tm.checkServerTrusted(peerCertificateChain, authMethod);
+                }
             } else {
                 String authType = peerCertificateChain[0].getPublicKey().getAlgorithm();
                 sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain,
@@ -691,7 +704,7 @@
                     return;
                 }
                 NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
-                        OpenSSLSocketImpl.this, buf, offset, byteCount);
+                        OpenSSLSocketImpl.this, buf, offset, byteCount, writeTimeoutMilliseconds);
             }
         }
     }
@@ -822,21 +835,42 @@
         throw new SocketException("Methods sendUrgentData, setOOBInline are not supported.");
     }
 
-    @Override public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
-        super.setSoTimeout(timeoutMilliseconds);
-        this.timeoutMilliseconds = timeoutMilliseconds;
+    @Override public void setSoTimeout(int readTimeoutMilliseconds) throws SocketException {
+        super.setSoTimeout(readTimeoutMilliseconds);
+        this.readTimeoutMilliseconds = readTimeoutMilliseconds;
     }
 
     @Override public int getSoTimeout() throws SocketException {
-        return timeoutMilliseconds;
+        return readTimeoutMilliseconds;
+    }
+
+    /**
+     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+     */
+    public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
+        this.writeTimeoutMilliseconds = writeTimeoutMilliseconds;
+
+        StructTimeval tv = StructTimeval.fromMillis(writeTimeoutMilliseconds);
+        try {
+            Libcore.os.setsockoptTimeval(getFileDescriptor$(), SOL_SOCKET, SO_SNDTIMEO, tv);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
+
+    /**
+     * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
+     */
+    public int getSoWriteTimeout() throws SocketException {
+        return writeTimeoutMilliseconds;
     }
 
     /**
      * Set the handshake timeout on this socket.  This timeout is specified in
      * milliseconds and will be used only during the handshake process.
      */
-    public void setHandshakeTimeout(int timeoutMilliseconds) throws SocketException {
-        this.handshakeTimeoutMilliseconds = timeoutMilliseconds;
+    public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
+        this.handshakeTimeoutMilliseconds = handshakeTimeoutMilliseconds;
     }
 
     @Override public void close() throws IOException {
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinEntryException.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinEntryException.java
new file mode 100644
index 0000000..8b74514
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinEntryException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+// public for testing by CertPinManagerTest
+public class PinEntryException extends Exception {
+
+    PinEntryException() {
+    }
+
+    PinEntryException(String msg) {
+        super(msg);
+    }
+}
+
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java
new file mode 100644
index 0000000..40b1838
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import libcore.io.Base64;
+import libcore.io.DropBox;
+
+public class PinFailureLogger {
+
+    private static final long LOG_INTERVAL_NANOS = 1000 * 1000 * 1000 * 60 * 60;
+
+    private static long lastLoggedNanos = 0;
+
+    public static synchronized void log(String cn, boolean chainContainsUserCert,
+                                        boolean pinIsEnforcing,
+                                        List<X509Certificate> chain) {
+        // if we've logged recently, don't do it again
+        if (!timeToLog()) {
+            return;
+        }
+        // otherwise, log the event
+        writeToLog(cn, chainContainsUserCert, pinIsEnforcing, chain);
+        // update the last logged time
+        lastLoggedNanos = System.nanoTime();
+    }
+
+    protected static synchronized void writeToLog(String cn, boolean chainContainsUserCert,
+                                                  boolean pinIsEnforcing,
+                                                  List<X509Certificate> chain) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(cn);
+        sb.append("|");
+        sb.append(chainContainsUserCert);
+        sb.append("|");
+        sb.append(pinIsEnforcing);
+        sb.append("|");
+        for (X509Certificate cert : chain) {
+            try {
+                sb.append(Base64.encode(cert.getEncoded()));
+            } catch (CertificateEncodingException e) {
+                sb.append("Error: could not encode certificate");
+            }
+            sb.append("|");
+        }
+        DropBox.addText("cert_pin_failure", sb.toString());
+    }
+
+    protected static boolean timeToLog() {
+        long currentTimeNanos = System.nanoTime();
+        return ((currentTimeNanos - lastLoggedNanos) > LOG_INTERVAL_NANOS);
+    }
+}
+
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java
new file mode 100644
index 0000000..c05a391
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import libcore.io.EventLogger;
+
+/**
+ * This class represents a single entry in the pin file.
+ */
+// public for testing by CertPinManagerTest
+public class PinListEntry {
+
+    /** The Common Name (CN) as used on the SSL certificate */
+    private final String cn;
+
+    /**
+     * Determines whether a failed match here will prevent the chain from being accepted. If true,
+     *  an unpinned chain will log and cause a match failure. If false, it will merely log.
+     */
+    private final boolean enforcing;
+
+    private final Set<String> pinnedFingerprints = new HashSet<String>();
+
+    private static final boolean DEBUG = false;
+
+    private final TrustedCertificateStore certStore;
+
+    public String getCommonName() {
+        return cn;
+    }
+
+    public boolean getEnforcing() {
+        return enforcing;
+    }
+
+    public PinListEntry(String entry, TrustedCertificateStore store) throws PinEntryException {
+        if (entry == null) {
+            throw new NullPointerException("entry == null");
+        }
+        certStore = store;
+        // Examples:
+        // *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d
+        // *.android.com=true|ca05f...8a0d34c
+        // clients.google.com=false|9e04ca05f...34c8a0d,34c8a0d...9e04ca05f
+        String[] values = entry.split("[=,|]");
+        // entry must have a CN, an enforcement value, and at least one pin
+        if (values.length < 3) {
+            throw new PinEntryException("Received malformed pin entry");
+        }
+        // get the cn
+        cn = values[0];        // is there more validation we can do here?
+        enforcing = enforcementValueFromString(values[1]);
+        // the remainder should be pins
+        addPins(Arrays.copyOfRange(values, 2, values.length));
+    }
+
+    private static boolean enforcementValueFromString(String val) throws PinEntryException {
+        if (val.equals("true")) {
+            return true;
+        } else if (val.equals("false")) {
+            return false;
+        } else {
+            throw new PinEntryException("Enforcement status is not a valid value");
+        }
+    }
+
+    /**
+     * Checks the given chain against the pin list corresponding to this entry.
+     *
+     * If the pin list does not contain the required certs and the enforcing field is true then
+     * this returns true, indicating a verification error. Otherwise, it returns false and
+     * verification should proceed.
+     */
+    public boolean chainIsNotPinned(List<X509Certificate> chain) {
+        for (X509Certificate cert : chain) {
+            String fingerprint = getFingerprint(cert);
+            if (pinnedFingerprints.contains(fingerprint)) {
+                return false;
+            }
+        }
+        logPinFailure(chain);
+        return enforcing;
+    }
+
+    private static String getFingerprint(X509Certificate cert) {
+        try {
+            MessageDigest dgst = MessageDigest.getInstance("SHA512");
+            byte[] encoded = cert.getPublicKey().getEncoded();
+            byte[] fingerprint = dgst.digest(encoded);
+            return IntegralToString.bytesToHexString(fingerprint, false);
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    private void addPins(String[] pins) {
+        for (String pin : pins) {
+            validatePin(pin);
+        }
+        Collections.addAll(pinnedFingerprints, pins);
+    }
+
+    private static void validatePin(String pin) {
+        // check to make sure the length is correct
+        if (pin.length() != 128) {
+            throw new IllegalArgumentException("Pin is not a valid length");
+        }
+        // check to make sure that it's a valid hex string
+        try {
+            new BigInteger(pin, 16);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("Pin is not a valid hex string", e);
+        }
+    }
+
+    private boolean chainContainsUserCert(List<X509Certificate> chain) {
+        if (certStore == null) {
+            return false;
+        }
+        for (X509Certificate cert : chain) {
+            if (certStore.isUserAddedCertificate(cert)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void logPinFailure(List<X509Certificate> chain) {
+        PinFailureLogger.log(cn, chainContainsUserCert(chain), enforcing, chain);
+    }
+}
+
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinManagerException.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinManagerException.java
new file mode 100644
index 0000000..74b3c65
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinManagerException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+class PinManagerException extends Exception {
+
+    PinManagerException() {
+    }
+
+    PinManagerException(String msg) {
+        super(msg);
+    }
+
+    PinManagerException(String msg, Exception e) {
+        super(msg, e);
+    }
+}
+
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java
index be9a7fc..93496cf 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java
@@ -88,7 +88,7 @@
         if (instantiationException != null) {
             throw instantiationException;
         }
-        return new SSLSocketWrapper(s, autoClose, (SSLParametersImpl) sslParameters
+        return new SSLSocketWrapper(s, host, port, autoClose, (SSLParametersImpl) sslParameters
                 .clone());
     }
 
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java
index 6e5fddd..2cd2cf5 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java
@@ -41,6 +41,10 @@
     // indicates if handshake has been started
     private boolean handshake_started = false;
 
+    // used when we're wrapping a socket
+    private final String wrappedHost;
+    private final int wrappedPort;
+
     // record protocol to be used
     protected SSLRecordProtocol recordProtocol;
     // handshake protocol to be used
@@ -83,6 +87,8 @@
      */
     protected SSLSocketImpl(SSLParametersImpl sslParameters) {
         this.sslParameters = sslParameters;
+        this.wrappedHost = null;
+        this.wrappedPort = -1;
         // init should be called after creation!
     }
 
@@ -99,6 +105,8 @@
     protected SSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
             throws IOException, UnknownHostException {
         super(host, port);
+        this.wrappedHost = host;
+        this.wrappedPort = port;
         this.sslParameters = sslParameters;
         init();
     }
@@ -120,6 +128,8 @@
             SSLParametersImpl sslParameters) throws IOException,
             UnknownHostException {
         super(host, port, localHost, localPort);
+        this.wrappedHost = host;
+        this.wrappedPort = port;
         this.sslParameters = sslParameters;
         init();
     }
@@ -138,6 +148,8 @@
             SSLParametersImpl sslParameters) throws IOException {
         super(host, port);
         this.sslParameters = sslParameters;
+        this.wrappedHost = null;
+        this.wrappedPort = -1;
         init();
     }
 
@@ -158,6 +170,8 @@
             SSLParametersImpl sslParameters) throws IOException {
         super(address, port, localAddress, localPort);
         this.sslParameters = sslParameters;
+        this.wrappedHost = null;
+        this.wrappedPort = -1;
         init();
     }
 
@@ -193,6 +207,29 @@
         }
     }
 
+    String getWrappedHostName() {
+        return wrappedHost;
+    }
+
+    int getWrappedPort() {
+        return wrappedPort;
+    }
+
+    String getPeerHostName() {
+        if (wrappedHost != null) {
+            return wrappedHost;
+        }
+        InetAddress inetAddress = super.getInetAddress();
+        if (inetAddress != null) {
+            return inetAddress.getHostName();
+        }
+        return null;
+    }
+
+    int getPeerPort() {
+        return (wrappedPort == -1) ? super.getPort() : wrappedPort;
+    }
+
     // --------------- SSLParameters based methods ---------------------
 
     /**
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java
index 27bbead..a393e24 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java
@@ -32,8 +32,9 @@
     private final Socket socket;
     private final boolean autoClose;
 
-    protected SSLSocketWrapper(Socket socket, boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
-        super(sslParameters);
+    protected SSLSocketWrapper(Socket socket, String host, int port, boolean autoClose,
+                               SSLParametersImpl sslParameters) throws IOException {
+        super(host, port, sslParameters);
         if (!socket.isConnected()) {
             throw new SocketException("Socket is not connected.");
         }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
index 3f362c5..0218249 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
@@ -35,6 +35,7 @@
 import java.util.List;
 import java.util.Set;
 import javax.net.ssl.X509TrustManager;
+import libcore.io.EventLogger;
 
 /**
  *
@@ -52,6 +53,11 @@
     private final KeyStore rootKeyStore;
 
     /**
+     * The CertPinManager, which validates the chain against a host-to-pin mapping
+     */
+    private CertPinManager pinManager;
+
+    /**
      * The backing store for the AndroidCAStore if non-null. This will
      * be null when the rootKeyStore is null, implying we are not
      * using the AndroidCAStore.
@@ -83,6 +89,13 @@
      * @param ks
      */
     public TrustManagerImpl(KeyStore keyStore) {
+        this(keyStore, null);
+    }
+
+    /**
+     * For testing only
+     */
+    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) {
         CertPathValidator validatorLocal = null;
         CertificateFactory factoryLocal = null;
         KeyStore rootKeyStoreLocal = null;
@@ -111,6 +124,17 @@
         } catch (Exception e) {
             errLocal = e;
         }
+
+        if (manager != null) {
+            this.pinManager = manager;
+        } else {
+            try {
+                pinManager = new CertPinManager(trustedCertificateStoreLocal);
+            } catch (PinManagerException e) {
+                throw new SecurityException("Could not initialize CertPinManager", e);
+            }
+        }
+
         this.rootKeyStore = rootKeyStoreLocal;
         this.trustedCertificateStore = trustedCertificateStoreLocal;
         this.validator = validatorLocal;
@@ -155,12 +179,22 @@
 
     @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
             throws CertificateException {
-        checkTrusted(chain, authType);
+        checkTrusted(chain, authType, null);
     }
 
     @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
             throws CertificateException {
-        checkTrusted(chain, authType);
+        checkTrusted(chain, authType, null);
+    }
+
+    /**
+     * Validates whether a server is trusted. If hostname is given and non-null it also checks if
+     * chain is pinned appropriately for that host. If null, it does not check for pinned certs.
+     * The return value is a list of the certificates used for making the trust decision.
+     */
+    public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
+                                                    String host) throws CertificateException {
+        return checkTrusted(chain, authType, host);
     }
 
     public void handleTrustStorageUpdate() {
@@ -168,10 +202,10 @@
             trustedCertificateIndex.reset();
         } else {
             trustedCertificateIndex.reset(trustAnchors(acceptedIssuers));
-        }
+       }
     }
 
-    private void checkTrusted(X509Certificate[] chain, String authType)
+    private List<X509Certificate> checkTrusted(X509Certificate[] chain, String authType, String host)
             throws CertificateException {
         if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
             throw new IllegalArgumentException("null or zero-length parameter");
@@ -180,21 +214,71 @@
             throw new CertificateException(err);
         }
 
-        Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
-        X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors);
-        if (newChain.length == 0) {
-            // chain was entirely trusted, skip the validator
-            return;
+        // get the cleaned up chain and trust anchor
+        Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one!
+        X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor);
+
+        // add the first trust anchor to the chain, which may be an intermediate
+        List<X509Certificate> wholeChain = new ArrayList<X509Certificate>();
+        wholeChain.addAll(Arrays.asList(newChain));
+        // trustAnchor is actually just a single element
+        for (TrustAnchor trust : trustAnchor) {
+            wholeChain.add(trust.getTrustedCert());
         }
 
+        // add all the cached certificates from the cert index, avoiding loops
+        // this gives us a full chain from leaf to root, which we use for cert pinning and pass
+        // back out to callers when we return.
+        X509Certificate last = wholeChain.get(wholeChain.size() - 1);
+        while (true) {
+            TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last);
+            // the cachedTrust can be null if there isn't anything in the index or if a user has
+            // trusted a non-self-signed cert.
+            if (cachedTrust == null) {
+                break;
+            }
+
+            // at this point we have a cached trust anchor, but don't know if its one we got from
+            // the server. Extract the cert, compare it to the last element in the chain, and add it
+            // if we haven't seen it before.
+            X509Certificate next = cachedTrust.getTrustedCert();
+            if (next != last) {
+                wholeChain.add(next);
+                last = next;
+            } else {
+                // if next == last then we found a self-signed cert and the chain is done
+                break;
+            }
+        }
+
+        // build the cert path from the array of certs sans trust anchors
         CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
-        if (trustAnchors.isEmpty()) {
+
+        if (host != null) {
+            boolean chainIsNotPinned = true;
+            try {
+                chainIsNotPinned = pinManager.chainIsNotPinned(host, wholeChain);
+            } catch (PinManagerException e) {
+                throw new CertificateException(e);
+            }
+            if (chainIsNotPinned) {
+                throw new CertificateException(new CertPathValidatorException(
+                        "Certificate path is not properly pinned.", null, certPath, -1));
+            }
+        }
+
+        if (newChain.length == 0) {
+            // chain was entirely trusted, skip the validator
+            return wholeChain;
+        }
+
+        if (trustAnchor.isEmpty()) {
             throw new CertificateException(new CertPathValidatorException(
                     "Trust anchor for certification path not found.", null, certPath, -1));
         }
 
         try {
-            PKIXParameters params = new PKIXParameters(trustAnchors);
+            PKIXParameters params = new PKIXParameters(trustAnchor);
             params.setRevocationEnabled(false);
             validator.validate(certPath, params);
             // Add intermediate CAs to the index to tolerate sites
@@ -211,6 +295,8 @@
         } catch (CertPathValidatorException e) {
             throw new CertificateException(e);
         }
+
+        return wholeChain;
     }
 
     /**
@@ -232,17 +318,9 @@
         // Start with the first certificate in the chain, assuming it
         // is the leaf certificate (server or client cert).
         for (currIndex = 0; currIndex < chain.length; currIndex++) {
-            // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
-            // This avoids including "bridge" CA certs that added for legacy compatability.
-            TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[currIndex]);
-            if (trustAnchor != null) {
-                trustAnchors.add(trustAnchor);
-                currIndex--;
-                break;
-            }
-            // Walk the rest of the chain to find a "subject" matching
+            // Walk the chain to find a "subject" matching
             // the "issuer" of the current certificate. In a properly
-            // order chain this should be the next cert and be fast.
+            // ordered chain this should be the next cert and be fast.
             // If not, we reorder things to be as the validator will
             // expect.
             boolean foundNext = false;
@@ -271,15 +349,27 @@
             }
         }
 
-        // 2. If the chain is now shorter, copy to an appropriately sized array.
-        int chainLength = currIndex + 1;
+        // 2. Find the trust anchor in the chain, if any
+        int anchorIndex;
+        for (anchorIndex = 0; anchorIndex < chain.length; anchorIndex++) {
+            // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
+            // This avoids including "bridge" CA certs that added for legacy compatibility.
+            TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]);
+            if (trustAnchor != null) {
+                trustAnchors.add(trustAnchor);
+                break;
+            }
+        }
+
+        // 3. If the chain is now shorter, copy to an appropriately sized array.
+        int chainLength = anchorIndex;
         X509Certificate[] newChain = ((chainLength == chain.length)
                                       ? chain
                                       : Arrays.copyOf(chain, chainLength));
 
-        // 3. If no TrustAnchor was found in cleanup, look for one now
+        // 4. If we didn't find a trust anchor earlier, look for one now
         if (trustAnchors.isEmpty()) {
-            TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[chainLength-1]);
+            TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]);
             if (trustAnchor != null) {
                 trustAnchors.add(trustAnchor);
             }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java
index abb6e55..e7b1a7c 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java
@@ -298,6 +298,14 @@
     }
 
     /**
+     * Returns true to indicate that the certificate was added by the
+     * user, false otherwise.
+     */
+    public boolean isUserAddedCertificate(X509Certificate cert) {
+        return getCertificateFile(addedDir, cert).exists();
+    }
+
+    /**
      * Returns a File for where the certificate is found if it exists
      * or where it should be installed if it does not exist. The
      * caller can disambiguate these cases by calling {@code
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index dd9b87f..acb3dc2 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -1168,6 +1168,15 @@
     return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL;
 }
 
+static void Posix_socketpair(JNIEnv* env, jobject, jint domain, jint type, jint protocol, jobject javaFd1, jobject javaFd2) {
+    int fds[2];
+    int rc = throwIfMinusOne(env, "socketpair", TEMP_FAILURE_RETRY(socketpair(domain, type, protocol, fds)));
+    if (rc != -1) {
+        jniSetFileDescriptorOfFD(env, javaFd1, fds[0]);
+        jniSetFileDescriptorOfFD(env, javaFd2, fds[1]);
+    }
+}
+
 static jobject Posix_stat(JNIEnv* env, jobject, jstring javaPath) {
     return doStat(env, javaPath, false);
 }
@@ -1341,6 +1350,7 @@
     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, socketpair, "(IIILjava/io/FileDescriptor;Ljava/io/FileDescriptor;)V"),
     NATIVE_METHOD(Posix, stat, "(Ljava/lang/String;)Llibcore/io/StructStat;"),
     NATIVE_METHOD(Posix, statfs, "(Ljava/lang/String;)Llibcore/io/StructStatFs;"),
     NATIVE_METHOD(Posix, strerror, "(I)Ljava/lang/String;"),
diff --git a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index a04bebf..322f416 100644
--- a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -505,11 +505,11 @@
 /**
  * Converts an OpenSSL BIGNUM to a Java byte[] array.
  */
-static jbyteArray bignumToArray(JNIEnv* env, BIGNUM* source) {
-    JNI_TRACE("bignumToArray(%p)", source);
+static jbyteArray bignumToArray(JNIEnv* env, BIGNUM* source, const char* sourceName) {
+    JNI_TRACE("bignumToArray(%p, %s)", source, sourceName);
 
     if (source == NULL) {
-        jniThrowNullPointerException(env, NULL);
+        jniThrowNullPointerException(env, sourceName);
         return NULL;
     }
 
@@ -517,7 +517,7 @@
     jbyteArray javaBytes = env->NewByteArray(len);
     ScopedByteArrayRW bytes(env, javaBytes);
     if (bytes.get() == NULL) {
-        JNI_TRACE("bignumToArray(%p) => NULL", source);
+        JNI_TRACE("bignumToArray(%p, %s) => NULL", source, sourceName);
         return NULL;
     }
 
@@ -535,7 +535,7 @@
         return NULL;
     }
 
-    JNI_TRACE("bignumToArray(%p) => %p", source, javaBytes);
+    JNI_TRACE("bignumToArray(%p, %s) => %p", source, sourceName, javaBytes);
     return javaBytes;
 }
 
@@ -923,6 +923,26 @@
     }
 }
 
+static jint NativeCrypto_EVP_PKEY_cmp(JNIEnv* env, jclass, jint pkey1Ref, jint pkey2Ref) {
+    EVP_PKEY* pkey1 = reinterpret_cast<EVP_PKEY*>(pkey1Ref);
+    EVP_PKEY* pkey2 = reinterpret_cast<EVP_PKEY*>(pkey2Ref);
+    JNI_TRACE("EVP_PKEY_cmp(%p, %p)", pkey1, pkey2);
+
+    if (pkey1 == NULL) {
+        JNI_TRACE("EVP_PKEY_cmp(%p, %p) => failed pkey1 == NULL", pkey1, pkey2);
+        jniThrowNullPointerException(env, "pkey1 == NULL");
+        return -1;
+    } else if (pkey2 == NULL) {
+        JNI_TRACE("EVP_PKEY_cmp(%p, %p) => failed pkey2 == NULL", pkey1, pkey2);
+        jniThrowNullPointerException(env, "pkey2 == NULL");
+        return -1;
+    }
+
+    int result = EVP_PKEY_cmp(pkey1, pkey2);
+    JNI_TRACE("EVP_PKEY_cmp(%p, %p) => %d", pkey1, pkey2, result);
+    return result;
+}
+
 /*
  * static native byte[] i2d_PKCS8_PRIV_KEY_INFO(int, byte[])
  */
@@ -1184,13 +1204,13 @@
         return NULL;
     }
 
-    jbyteArray n = bignumToArray(env, rsa->n);
+    jbyteArray n = bignumToArray(env, rsa->n, "n");
     if (env->ExceptionCheck()) {
         return NULL;
     }
     env->SetObjectArrayElement(joa, 0, n);
 
-    jbyteArray e = bignumToArray(env, rsa->e);
+    jbyteArray e = bignumToArray(env, rsa->e, "e");
     if (env->ExceptionCheck()) {
         return NULL;
     }
@@ -1217,14 +1237,14 @@
         return NULL;
     }
 
-    jbyteArray n = bignumToArray(env, rsa->n);
+    jbyteArray n = bignumToArray(env, rsa->n, "n");
     if (env->ExceptionCheck()) {
         return NULL;
     }
     env->SetObjectArrayElement(joa, 0, n);
 
     if (rsa->e != NULL) {
-        jbyteArray e = bignumToArray(env, rsa->e);
+        jbyteArray e = bignumToArray(env, rsa->e, "e");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1232,7 +1252,7 @@
     }
 
     if (rsa->d != NULL) {
-        jbyteArray d = bignumToArray(env, rsa->d);
+        jbyteArray d = bignumToArray(env, rsa->d, "d");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1240,7 +1260,7 @@
     }
 
     if (rsa->p != NULL) {
-        jbyteArray p = bignumToArray(env, rsa->p);
+        jbyteArray p = bignumToArray(env, rsa->p, "p");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1248,7 +1268,7 @@
     }
 
     if (rsa->q != NULL) {
-        jbyteArray q = bignumToArray(env, rsa->q);
+        jbyteArray q = bignumToArray(env, rsa->q, "q");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1256,7 +1276,7 @@
     }
 
     if (rsa->dmp1 != NULL) {
-        jbyteArray dmp1 = bignumToArray(env, rsa->dmp1);
+        jbyteArray dmp1 = bignumToArray(env, rsa->dmp1, "dmp1");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1264,7 +1284,7 @@
     }
 
     if (rsa->dmq1 != NULL) {
-        jbyteArray dmq1 = bignumToArray(env, rsa->dmq1);
+        jbyteArray dmq1 = bignumToArray(env, rsa->dmq1, "dmq1");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1272,7 +1292,7 @@
     }
 
     if (rsa->iqmp != NULL) {
-        jbyteArray iqmp = bignumToArray(env, rsa->iqmp);
+        jbyteArray iqmp = bignumToArray(env, rsa->iqmp, "iqmp");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1377,26 +1397,26 @@
         return NULL;
     }
 
-    jbyteArray g = bignumToArray(env, dsa->g);
+    jbyteArray g = bignumToArray(env, dsa->g, "g");
     if (env->ExceptionCheck()) {
         return NULL;
     }
     env->SetObjectArrayElement(joa, 0, g);
 
-    jbyteArray p = bignumToArray(env, dsa->p);
+    jbyteArray p = bignumToArray(env, dsa->p, "p");
     if (env->ExceptionCheck()) {
         return NULL;
     }
     env->SetObjectArrayElement(joa, 1, p);
 
-    jbyteArray q = bignumToArray(env, dsa->q);
+    jbyteArray q = bignumToArray(env, dsa->q, "q");
     if (env->ExceptionCheck()) {
         return NULL;
     }
     env->SetObjectArrayElement(joa, 2, q);
 
     if (dsa->pub_key != NULL) {
-        jbyteArray pub_key = bignumToArray(env, dsa->pub_key);
+        jbyteArray pub_key = bignumToArray(env, dsa->pub_key, "pub_key");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1404,7 +1424,7 @@
     }
 
     if (dsa->priv_key != NULL) {
-        jbyteArray priv_key = bignumToArray(env, dsa->priv_key);
+        jbyteArray priv_key = bignumToArray(env, dsa->priv_key, "priv_key");
         if (env->ExceptionCheck()) {
             return NULL;
         }
@@ -1790,6 +1810,13 @@
     if (ok < 0) {
         throwExceptionIfNecessary(env, "NativeCrypto_EVP_VerifyFinal");
     }
+
+    /*
+     * For DSA keys, OpenSSL appears to have a bug where it returns
+     * errors for any result != 1. See dsa_ossl.c in dsa_do_verify
+     */
+    freeOpenSslErrorState();
+
     JNI_TRACE("NativeCrypto_EVP_VerifyFinal(%p, %p, %d, %d, %p) => %d",
               ctx, buffer, offset, length, pkey, ok);
 
@@ -2244,12 +2271,15 @@
     static AppData* create() {
         UniquePtr<AppData> appData(new AppData());
         if (pipe(appData.get()->fdsEmergency) == -1) {
+            ALOGE("AppData::create pipe(2) failed: %s", strerror(errno));
             return NULL;
         }
         if (!setBlocking(appData.get()->fdsEmergency[0], false)) {
+            ALOGE("AppData::create fcntl(2) failed: %s", strerror(errno));
             return NULL;
         }
         if (MUTEX_SETUP(appData.get()->mutex) == -1) {
+            ALOGE("pthread_mutex_init(3) failed: %s", strerror(errno));
             return NULL;
         }
         return appData.release();
@@ -3735,7 +3765,7 @@
  * cleanly shut down, or THROW_SSLEXCEPTION if an exception should be thrown.
  */
 static int sslRead(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, char* buf, jint len,
-                   int* sslReturnCode, int* sslErrorCode, int timeout_millis) {
+                   int* sslReturnCode, int* sslErrorCode, int read_timeout_millis) {
     JNI_TRACE("ssl=%p sslRead buf=%p len=%d", ssl, buf, len);
 
     if (len == 0) {
@@ -3814,7 +3844,7 @@
             // Need to wait for availability of underlying layer, then retry.
             case SSL_ERROR_WANT_READ:
             case SSL_ERROR_WANT_WRITE: {
-                int selectResult = sslSelect(env, sslError, fdObject, appData, timeout_millis);
+                int selectResult = sslSelect(env, sslError, fdObject, appData, read_timeout_millis);
                 if (selectResult == THROWN_EXCEPTION) {
                     return THROWN_EXCEPTION;
                 }
@@ -3866,11 +3896,11 @@
  */
 static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jint ssl_address, jobject fdObject,
                                   jobject shc, jbyteArray b, jint offset, jint len,
-                                  jint timeout_millis)
+                                  jint read_timeout_millis)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_read fd=%p shc=%p b=%p offset=%d len=%d timeout_millis=%d",
-              ssl, fdObject, shc, b, offset, len, timeout_millis);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_read fd=%p shc=%p b=%p offset=%d len=%d read_timeout_millis=%d",
+              ssl, fdObject, shc, b, offset, len, read_timeout_millis);
     if (ssl == NULL) {
         return 0;
     }
@@ -3894,7 +3924,7 @@
     int sslErrorCode = SSL_ERROR_NONE;;
 
     int ret = sslRead(env, ssl, fdObject, shc, reinterpret_cast<char*>(bytes.get() + offset), len,
-                      &returnCode, &sslErrorCode, timeout_millis);
+                      &returnCode, &sslErrorCode, read_timeout_millis);
 
     int result;
     switch (ret) {
@@ -3934,8 +3964,9 @@
  * cleanly shut down, or THROW_SSLEXCEPTION if an exception should be thrown.
  */
 static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const char* buf, jint len,
-                    int* sslReturnCode, int* sslErrorCode) {
-    JNI_TRACE("ssl=%p sslWrite buf=%p len=%d", ssl, buf, len);
+                    int* sslReturnCode, int* sslErrorCode, int write_timeout_millis) {
+    JNI_TRACE("ssl=%p sslWrite buf=%p len=%d write_timeout_millis=%d",
+              ssl, buf, len, write_timeout_millis);
 
     if (len == 0) {
         // Don't bother doing anything in this case.
@@ -4020,7 +4051,7 @@
             // it's also not standard Java behavior, so we wait forever here.
             case SSL_ERROR_WANT_READ:
             case SSL_ERROR_WANT_WRITE: {
-                int selectResult = sslSelect(env, sslError, fdObject, appData, 0);
+                int selectResult = sslSelect(env, sslError, fdObject, appData, write_timeout_millis);
                 if (selectResult == THROWN_EXCEPTION) {
                     return THROWN_EXCEPTION;
                 }
@@ -4071,11 +4102,11 @@
  * OpenSSL write function (2): write into buffer at offset n chunks.
  */
 static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jint ssl_address, jobject fdObject,
-                                   jobject shc, jbyteArray b, jint offset, jint len)
+                                   jobject shc, jbyteArray b, jint offset, jint len, jint write_timeout_millis)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
-    JNI_TRACE("ssl=%p NativeCrypto_SSL_write fd=%p shc=%p b=%p offset=%d len=%d",
-              ssl, fdObject, shc, b, offset, len);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_write fd=%p shc=%p b=%p offset=%d len=%d write_timeout_millis=%d",
+              ssl, fdObject, shc, b, offset, len, write_timeout_millis);
     if (ssl == NULL) {
         return;
     }
@@ -4098,7 +4129,7 @@
     int returnCode = 0;
     int sslErrorCode = SSL_ERROR_NONE;
     int ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast<const char*>(bytes.get() + offset),
-                       len, &returnCode, &sslErrorCode);
+                       len, &returnCode, &sslErrorCode, write_timeout_millis);
 
     switch (ret) {
         case THROW_SSLEXCEPTION:
@@ -4404,6 +4435,7 @@
     NATIVE_METHOD(NativeCrypto, EVP_PKEY_type, "(I)I"),
     NATIVE_METHOD(NativeCrypto, EVP_PKEY_size, "(I)I"),
     NATIVE_METHOD(NativeCrypto, EVP_PKEY_free, "(I)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_cmp, "(II)I"),
     NATIVE_METHOD(NativeCrypto, i2d_PKCS8_PRIV_KEY_INFO, "(I)[B"),
     NATIVE_METHOD(NativeCrypto, d2i_PKCS8_PRIV_KEY_INFO, "([B)I"),
     NATIVE_METHOD(NativeCrypto, i2d_PUBKEY, "(I)[B"),
@@ -4466,7 +4498,7 @@
     NATIVE_METHOD(NativeCrypto, SSL_get_certificate, "(I)[[B"),
     NATIVE_METHOD(NativeCrypto, SSL_get_peer_cert_chain, "(I)[[B"),
     NATIVE_METHOD(NativeCrypto, SSL_read, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)I"),
-    NATIVE_METHOD(NativeCrypto, SSL_write, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "[BII)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_write, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)V"),
     NATIVE_METHOD(NativeCrypto, SSL_interrupt, "(I)V"),
     NATIVE_METHOD(NativeCrypto, SSL_shutdown, "(I" FILE_DESCRIPTOR SSL_CALLBACKS ")V"),
     NATIVE_METHOD(NativeCrypto, SSL_free, "(I)V"),
diff --git a/luni/src/main/native/zip.h b/luni/src/main/native/zip.h
index 0f3c0c1..303c940 100644
--- a/luni/src/main/native/zip.h
+++ b/luni/src/main/native/zip.h
@@ -23,7 +23,7 @@
 #include "UniquePtr.h"
 #include "jni.h"
 #include "zlib.h"
-#include "zutil.h"
+#include "zutil.h" // For DEF_WBITS and DEF_MEM_LEVEL.
 
 static void throwExceptionForZlibError(JNIEnv* env, const char* exceptionClassName, int error) {
     if (error == Z_MEM_ERROR) {
diff --git a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
index e1f51bd..e46df5d 100755
--- a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
+++ b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
@@ -46,10 +46,16 @@
 
     private Socket[] sockets;
 
+    @Override protected void setUp() throws Exception {
+        Thread.interrupted(); // clear interrupted bit
+        super.tearDown();
+    }
+
     @Override protected void tearDown() throws Exception {
         if (sockets != null) {
             sockets[0].close();
             sockets[1].close();
+            sockets = null;
         }
         Thread.interrupted(); // clear interrupted bit
         super.tearDown();
@@ -111,56 +117,66 @@
     }
 
     private void testInterruptInputStream(final InputStream in) throws Exception {
-        interruptMeLater();
+        Thread thread = interruptMeLater();
         try {
             in.read();
             fail();
         } catch (InterruptedIOException expected) {
+        } finally {
+            waitForInterrupt(thread);
         }
     }
 
     private void testInterruptReader(final PipedReader reader) throws Exception {
-        interruptMeLater();
+        Thread thread = interruptMeLater();
         try {
             reader.read();
             fail();
         } catch (InterruptedIOException expected) {
+        } finally {
+            waitForInterrupt(thread);
         }
     }
 
     private void testInterruptReadableChannel(final ReadableByteChannel channel) throws Exception {
-        interruptMeLater();
+        Thread thread = interruptMeLater();
         try {
             channel.read(ByteBuffer.allocate(BUFFER_SIZE));
             fail();
         } catch (ClosedByInterruptException expected) {
+        } finally {
+            waitForInterrupt(thread);
         }
     }
 
     private void testInterruptOutputStream(final OutputStream out) throws Exception {
-        interruptMeLater();
+        Thread thread = interruptMeLater();
         try {
             // this will block when the receiving buffer fills up
             while (true) {
                 out.write(new byte[BUFFER_SIZE]);
             }
         } catch (InterruptedIOException expected) {
+        } finally {
+            waitForInterrupt(thread);
         }
     }
 
     private void testInterruptWriter(final PipedWriter writer) throws Exception {
-        interruptMeLater();
+        Thread thread = interruptMeLater();
         try {
             // this will block when the receiving buffer fills up
             while (true) {
                 writer.write(new char[BUFFER_SIZE]);
             }
         } catch (InterruptedIOException expected) {
+        } finally {
+            waitForInterrupt(thread);
         }
     }
 
     private void testInterruptWritableChannel(final WritableByteChannel channel) throws Exception {
-        interruptMeLater();
+        Thread thread = interruptMeLater();
         try {
             // this will block when the receiving buffer fills up
             while (true) {
@@ -168,12 +184,14 @@
             }
         } catch (ClosedByInterruptException expected) {
         } catch (ClosedChannelException expected) {
+        } finally {
+            waitForInterrupt(thread);
         }
     }
 
-    private void interruptMeLater() throws Exception {
+    private Thread interruptMeLater() throws Exception {
         final Thread toInterrupt = Thread.currentThread();
-        new Thread(new Runnable () {
+        Thread thread = new Thread(new Runnable () {
             @Override public void run() {
                 try {
                     Thread.sleep(1000);
@@ -181,6 +199,20 @@
                 }
                 toInterrupt.interrupt();
             }
-        }).start();
+        });
+        thread.start();
+        return thread;
+    }
+
+    private static void waitForInterrupt(Thread thread) throws Exception {
+        try {
+            thread.join();
+        } catch (InterruptedException ignore) {
+            // There is currently a race between Thread.interrupt in
+            // interruptMeLater and Thread.join here. Most of the time
+            // we won't get an InterruptedException, but occasionally
+            // we do, so for now ignore this exception.
+            // http://b/6951157
+        }
     }
 }
diff --git a/luni/src/test/java/libcore/java/lang/OldObjectTest.java b/luni/src/test/java/libcore/java/lang/OldObjectTest.java
index 08471b2..3ab0327 100644
--- a/luni/src/test/java/libcore/java/lang/OldObjectTest.java
+++ b/luni/src/test/java/libcore/java/lang/OldObjectTest.java
@@ -16,8 +16,6 @@
  */
 package libcore.java.lang;
 
-import dalvik.annotation.SideEffect;
-import java.util.Vector;
 import junit.framework.TestCase;
 
 public class OldObjectTest extends TestCase {
@@ -187,7 +185,36 @@
             fail("InterruptedException was thrown.");
         }
         assertEquals(3, status);
+    }
 
+    public void test_waitJI_invalid() throws Exception {
+        Object o = new Object();
+        synchronized (o) {
+            try {
+                o.wait(-1, 0);
+                fail();
+            } catch (IllegalArgumentException expected) {
+            }
+
+            try {
+                o.wait(0, -1);
+                fail();
+            } catch (IllegalArgumentException expected) {
+            }
+
+            try {
+                o.wait(-1, -1);
+                fail();
+            } catch (IllegalArgumentException expected) {
+            }
+
+            // The ms timeout must fit in 32 bits.
+            try {
+                o.wait(Integer.MAX_VALUE + 1, 0);
+                fail();
+            } catch (IllegalArgumentException expected) {
+            }
+        }
     }
 
     public void test_waitJ() {
diff --git a/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java b/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java
index d33c5f3..99a479c 100644
--- a/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java
+++ b/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java
@@ -16,11 +16,13 @@
 
 package libcore.java.net;
 
+import java.io.IOException;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
 import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketAddress;
 import java.net.SocketException;
 import java.nio.channels.AsynchronousCloseException;
 import java.nio.channels.ClosedChannelException;
@@ -36,25 +38,25 @@
  */
 public class ConcurrentCloseTest extends junit.framework.TestCase {
     public void test_accept() throws Exception {
-        ServerSocket s = new ServerSocket(0);
-        new Killer(s).start();
+        ServerSocket ss = new ServerSocket(0);
+        new Killer(ss).start();
         try {
             System.err.println("accept...");
-            s.accept();
-            fail("accept returned!");
+            Socket s = ss.accept();
+            fail("accept returned " + s + "!");
         } catch (SocketException expected) {
             assertEquals("Socket closed", expected.getMessage());
         }
     }
 
     public void test_connect() throws Exception {
-        StuckServer ss = new StuckServer();
+        StuckServer ss = new StuckServer(false);
         Socket s = new Socket();
         new Killer(s).start();
         try {
             System.err.println("connect...");
             s.connect(ss.getLocalSocketAddress());
-            fail("connect returned!");
+            fail("connect returned: " + s + "!");
         } catch (SocketException expected) {
             assertEquals("Socket closed", expected.getMessage());
         } finally {
@@ -63,13 +65,13 @@
     }
 
     public void test_connect_timeout() throws Exception {
-        StuckServer ss = new StuckServer();
+        StuckServer ss = new StuckServer(false);
         Socket s = new Socket();
         new Killer(s).start();
         try {
             System.err.println("connect (with timeout)...");
             s.connect(ss.getLocalSocketAddress(), 3600 * 1000);
-            fail("connect returned!");
+            fail("connect returned: " + s + "!");
         } catch (SocketException expected) {
             assertEquals("Socket closed", expected.getMessage());
         } finally {
@@ -78,7 +80,7 @@
     }
 
     public void test_connect_nonBlocking() throws Exception {
-        StuckServer ss = new StuckServer();
+        StuckServer ss = new StuckServer(false);
         SocketChannel s = SocketChannel.open();
         new Killer(s.socket()).start();
         try {
@@ -88,7 +90,7 @@
             while (!s.finishConnect()) {
                 // Spin like a mad thing!
             }
-            fail("connect returned!");
+            fail("connect returned: " + s + "!");
         } catch (SocketException expected) {
             assertEquals("Socket closed", expected.getMessage());
         } catch (AsynchronousCloseException alsoOkay) {
@@ -102,23 +104,14 @@
     }
 
     public void test_read() throws Exception {
-        final ServerSocket ss = new ServerSocket(0);
-        new Thread(new Runnable() {
-            public void run() {
-                try {
-                    ss.accept();
-                } catch (Exception ex) {
-                    ex.printStackTrace();
-                }
-            }
-        }).start();
+        SilentServer ss = new SilentServer();
         Socket s = new Socket();
         s.connect(ss.getLocalSocketAddress());
         new Killer(s).start();
         try {
             System.err.println("read...");
             int i = s.getInputStream().read();
-            fail("read returned " + i);
+            fail("read returned: " + i);
         } catch (SocketException expected) {
             assertEquals("Socket closed", expected.getMessage());
         }
@@ -126,16 +119,7 @@
     }
 
     public void test_read_multiple() throws Throwable {
-        final ServerSocket ss = new ServerSocket(0);
-        new Thread(new Runnable() {
-            public void run() {
-                try {
-                    ss.accept();
-                } catch (Exception ex) {
-                    ex.printStackTrace();
-                }
-            }
-        }).start();
+        SilentServer ss = new SilentServer();
         final Socket s = new Socket();
         s.connect(ss.getLocalSocketAddress());
 
@@ -152,7 +136,7 @@
                         try {
                             System.err.println("read...");
                             int i = s.getInputStream().read();
-                            fail("read returned " + i);
+                            fail("read returned: " + i);
                         } catch (SocketException expected) {
                             assertEquals("Socket closed", expected.getMessage());
                         }
@@ -173,6 +157,8 @@
         for (Throwable exception : thrownExceptions) {
             throw exception;
         }
+
+        ss.close();
     }
 
     public void test_recv() throws Exception {
@@ -190,21 +176,7 @@
     }
 
     public void test_write() throws Exception {
-        final ServerSocket ss = new ServerSocket(0);
-        new Thread(new Runnable() {
-            public void run() {
-                try {
-                    System.err.println("accepting...");
-
-                    Socket client = ss.accept();
-                    System.err.println("accepted...");
-                    Thread.sleep(30 * 1000);
-                    System.err.println("server exiting...");
-                } catch (Exception ex) {
-                    ex.printStackTrace();
-                }
-            }
-        }).start();
+        final SilentServer ss = new SilentServer();
         Socket s = new Socket();
         s.connect(ss.getLocalSocketAddress());
         new Killer(s).start();
@@ -224,6 +196,37 @@
         ss.close();
     }
 
+    // This server accepts connections, but doesn't read or write anything.
+    // It holds on to the Socket connecting to the client so it won't be GCed.
+    // Call "close" to close both the server socket and its client connection.
+    static class SilentServer {
+        private final ServerSocket ss;
+        private Socket client;
+
+        public SilentServer() throws IOException {
+            ss = new ServerSocket(0);
+            new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        client = ss.accept();
+                    } catch (Exception ex) {
+                        ex.printStackTrace();
+                    }
+                }
+            }).start();
+        }
+
+        public SocketAddress getLocalSocketAddress() {
+            return ss.getLocalSocketAddress();
+        }
+
+        public void close() throws IOException {
+            client.close();
+            ss.close();
+        }
+    }
+
+    // This thread calls the "close" method on the supplied T after 2s.
     static class Killer<T> extends Thread {
         private final T s;
 
diff --git a/luni/src/test/java/libcore/java/net/OldSocketTest.java b/luni/src/test/java/libcore/java/net/OldSocketTest.java
index fda9557..033a7bf 100644
--- a/luni/src/test/java/libcore/java/net/OldSocketTest.java
+++ b/luni/src/test/java/libcore/java/net/OldSocketTest.java
@@ -38,7 +38,6 @@
 import java.nio.channels.SocketChannel;
 import java.security.Permission;
 import tests.support.Support_Configuration;
-import tests.support.Support_PortManager;
 
 public class OldSocketTest extends OldSocketTestCase {
 
@@ -115,17 +114,15 @@
 
     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);
+                InetAddress.getLocalHost(), 0);
         assertTrue("Failed to create socket", s.getPort() == sport);
     }
 
     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);
+        Socket s1 = new Socket("www.google.com", 80, null, 0);
         try {
-            Socket s2 = new Socket("www.google.com", 80, null, testPort);
+            Socket s2 = new Socket("www.google.com", 80, null, s1.getLocalPort());
             try {
                 s2.close();
             } catch (IOException ignored) {
@@ -162,10 +159,8 @@
         // Test for method java.net.Socket(java.net.InetAddress, int,
         // java.net.InetAddress, int)
         int sport = startServer("Cons InetAddress,I,InetAddress,I");
-        int portNumber = Support_PortManager.getNextPort();
         s = new Socket(InetAddress.getLocalHost().getHostName(), sport,
-                InetAddress.getLocalHost(), portNumber);
-        assertTrue("Failed to create socket", s.getLocalPort() == portNumber);
+                InetAddress.getLocalHost(), 0);
     }
 
     public void test_ConstructorLjava_net_InetAddressIZ() throws IOException {
@@ -180,8 +175,7 @@
     public void test_close() throws IOException {
         // Test for method void java.net.Socket.close()
         int sport = startServer("SServer close");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+        s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
         try {
             s.setSoLinger(false, 100);
         } catch (IOException e) {
@@ -199,8 +193,7 @@
     public void test_getInetAddress() throws IOException {
         // Test for method java.net.InetAddress java.net.Socket.getInetAddress()
         int sport = startServer("SServer getInetAddress");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+        s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
         assertTrue("Returned incorrect InetAddress", s.getInetAddress().equals(
                 InetAddress.getLocalHost()));
 
@@ -220,9 +213,7 @@
     public void test_getKeepAlive() {
         try {
             int sport = startServer("SServer getKeepAlive");
-            int portNumber = Support_PortManager.getNextPort();
-            Socket theSocket = new Socket(InetAddress.getLocalHost(), sport,
-                    null, portNumber);
+            Socket theSocket = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             theSocket.setKeepAlive(true);
             assertTrue("getKeepAlive false when it should be true", theSocket
                     .getKeepAlive());
@@ -254,8 +245,7 @@
         // Test for method java.net.InetAddress
         // java.net.Socket.getLocalAddress()
         int sport = startServer("SServer getLocAddress");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+        s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
         assertEquals("Returned incorrect InetAddress",
                 InetAddress.getLocalHost(), s.getLocalAddress());
 
@@ -271,10 +261,10 @@
     public void test_getLocalPort() throws IOException {
         // Test for method int java.net.Socket.getLocalPort()
         int sport = startServer("SServer getLocalPort");
-        int portNumber = Support_PortManager.getNextPort();
         s = new Socket(InetAddress.getLocalHost().getHostName(), sport,
-                InetAddress.getLocalHost(), portNumber);
-        assertTrue("Returned incorrect port", s.getLocalPort() == portNumber);
+                InetAddress.getLocalHost(), 0);
+        // There's nothing we can usefully assert about the kernel-assigned port.
+        s.getLocalPort();
     }
 
     @SuppressWarnings("deprecation")
@@ -282,15 +272,13 @@
         // Test for method java.io.OutputStream
         // java.net.Socket.getOutputStream()
         int sport = startServer("SServer getOutputStream");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+        s = new Socket(InetAddress.getLocalHost(), sport);
         java.io.OutputStream os = s.getOutputStream();
         assertNotNull("Failed to get stream", os);
         os.write(1);
         s.close();
         // Regression test for harmony-2934
-        s = new Socket("127.0.0.1", Support_PortManager.getNextPort(),
-                false);
+        s = new Socket("127.0.0.1", sport, false);
         OutputStream o = s.getOutputStream();
         o.write(1);
         try {
@@ -301,8 +289,7 @@
         s.close();
 
         // Regression test for harmony-2942
-        s = new Socket("0.0.0.0", Support_PortManager.getNextPort(),
-                false);
+        s = new Socket("0.0.0.0", sport, false);
         o = s.getOutputStream();
         o.write(1);
         try {
@@ -316,18 +303,15 @@
     public void test_getPort() throws IOException {
         // Test for method int java.net.Socket.getPort()
         int sport = startServer("SServer getPort");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
-        assertTrue("Returned incorrect port" + s.getPort(),
-                s.getPort() == sport);
+        s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
+        assertTrue("Returned incorrect port" + s.getPort(), s.getPort() == sport);
     }
 
     public void test_getSoLinger() {
         // Test for method int java.net.Socket.getSoLinger()
         int sport = startServer("SServer getSoLinger");
         try {
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.setSoLinger(true, 200);
             assertEquals("Returned incorrect linger", 200, s.getSoLinger());
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_LINGER);
@@ -337,8 +321,7 @@
         }
 
         try {
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.close();
             try {
                 s.getSoLinger();
@@ -354,9 +337,7 @@
     public void test_getReceiveBufferSize() {
         try {
             int sport = startServer("SServer getReceiveBufferSize");
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost().getHostName(), sport,
-                    null, portNumber);
+            s = new Socket(InetAddress.getLocalHost().getHostName(), sport, null, 0);
             s.setReceiveBufferSize(130);
             assertTrue("Incorrect buffer size", s.getReceiveBufferSize() >= 130);
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_RCVBUF);
@@ -381,9 +362,7 @@
     public void test_getSendBufferSize() {
         int sport = startServer("SServer setSendBufferSize");
         try {
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost().getHostName(), sport,
-                    null, portNumber);
+            s = new Socket(InetAddress.getLocalHost().getHostName(), sport, null, 0);
             s.setSendBufferSize(134);
             assertTrue("Incorrect buffer size", s.getSendBufferSize() >= 134);
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_SNDBUF);
@@ -391,8 +370,7 @@
             handleException(e, SO_SNDBUF);
         }
         try {
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.close();
             try {
                 s.getSendBufferSize();
@@ -430,8 +408,7 @@
         // Test for method boolean java.net.Socket.getTcpNoDelay()
         int sport = startServer("SServer getTcpNoDelay");
         try {
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             boolean bool = !s.getTcpNoDelay();
             s.setTcpNoDelay(bool);
             assertTrue("Failed to get no delay setting: " + s.getTcpNoDelay(),
@@ -442,8 +419,7 @@
         }
 
         try {
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.close();
             try {
                 s.getTcpNoDelay();
@@ -461,9 +437,7 @@
         // crashed machines. Just make sure we can set it
         try {
             int sport = startServer("SServer setKeepAlive");
-            int portNumber = Support_PortManager.getNextPort();
-            Socket theSocket = new Socket(InetAddress.getLocalHost(), sport,
-                    null, portNumber);
+            Socket theSocket = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             theSocket.setKeepAlive(true);
             theSocket.setKeepAlive(false);
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_KEEPALIVE);
@@ -509,8 +483,7 @@
     public void test_setSendBufferSizeI() {
         try {
             int sport = startServer("SServer setSendBufferSizeI");
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.setSendBufferSize(134);
             assertTrue("Incorrect buffer size", s.getSendBufferSize() >= 134);
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_SNDBUF);
@@ -533,8 +506,7 @@
     public void test_setReceiveBufferSizeI() {
         try {
             int sport = startServer("SServer setReceiveBufferSizeI");
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.setReceiveBufferSize(130);
             assertTrue("Incorrect buffer size", s.getReceiveBufferSize() >= 130);
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_RCVBUF);
@@ -558,8 +530,7 @@
         // Test for method void java.net.Socket.setSoLinger(boolean, int)
         try {
             int sport = startServer("SServer setSoLingerZI");
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             s.setSoLinger(true, 500);
             assertEquals("Set incorrect linger", 500, s.getSoLinger());
             ensureExceptionThrownIfOptionIsUnsupportedOnOS(SO_LINGER);
@@ -584,8 +555,7 @@
         // Test for method void java.net.Socket.setTcpNoDelay(boolean)
         try {
             int sport = startServer("SServer setTcpNoDelayZ");
-            int portNumber = Support_PortManager.getNextPort();
-            s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+            s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
             boolean bool;
             s.setTcpNoDelay(bool = !s.getTcpNoDelay());
             assertTrue("Failed to set no delay setting: " + s.getTcpNoDelay(),
@@ -610,9 +580,8 @@
     public void test_toString() throws IOException {
         // Test for method java.lang.String java.net.Socket.toString()
         int sport = startServer("SServer toString");
-        int portNumber = Support_PortManager.getNextPort();
         s = new Socket(InetAddress.getLocalHost().getHostName(), sport,
-                InetAddress.getLocalHost(), portNumber);
+                InetAddress.getLocalHost(), 0);
         assertEquals("Socket[address=" + InetAddress.getLocalHost() + ",port=" + s.getPort()
                 + ",localPort=" + s.getLocalPort() + "]", s.toString());
     }
@@ -620,9 +589,8 @@
     // AndroidOnly: RI returns wrong value for EOF
     public void test_shutdownInput() throws Exception {
         InetAddress addr = InetAddress.getLocalHost();
-        int port = Support_PortManager.getNextPort();
-        ServerSocket serverSocket = new ServerSocket(port, 5, addr);
-        Socket theSocket = new Socket(addr, port);
+        ServerSocket serverSocket = new ServerSocket(0, 5, addr);
+        Socket theSocket = new Socket(addr, serverSocket.getLocalPort());
         Socket servSock = serverSocket.accept();
 
         InputStream theInput = theSocket.getInputStream();
@@ -656,10 +624,8 @@
     }
 
     public void test_shutdownOutput() throws IOException {
-        InetAddress addr = InetAddress.getLocalHost();
-        int port = Support_PortManager.getNextPort();
-        ServerSocket serverSocket = new ServerSocket(port, 5, addr);
-        Socket theSocket = new Socket(addr, port);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        Socket theSocket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
         Socket servSock = serverSocket.accept();
 
         InputStream theInput = theSocket.getInputStream();
@@ -692,17 +658,9 @@
         // set up server connect and then validate that we get the right
         // response for the local address
         int sport = startServer("SServer getLocSocketAddress");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, 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 = new Socket(InetAddress.getLocalHost(), sport, null, 0);
+        assertEquals(new InetSocketAddress(InetAddress.getLocalHost(), s.getLocalPort()),
+                     s.getLocalSocketAddress());
         s.close();
 
         // now create a socket that is not bound and validate we get the
@@ -713,21 +671,12 @@
                 theSocket.getLocalSocketAddress());
 
         // now bind the socket and make sure we get the right answer
-        portNumber = Support_PortManager.getNextPort();
-        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.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));
+        assertEquals(new InetSocketAddress(InetAddress.getLocalHost(), theSocket.getLocalPort()),
+                     theSocket.getLocalSocketAddress());
         theSocket.close();
 
-        // now validate that behaviour when the any address is returned
+        // now validate that behavior when the any address is returned
         s = new Socket();
         s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0));
 
@@ -747,8 +696,7 @@
         // set up server connect and then validate that we get the right
         // response for the remote address
         int sport = startServer("SServer getLocRemoteAddress");
-        int portNumber = Support_PortManager.getNextPort();
-        s = new Socket(InetAddress.getLocalHost(), sport, null, portNumber);
+        s = new Socket(InetAddress.getLocalHost(), sport, null, 0);
         assertTrue("Returned incorrect InetSocketAddress(1):"
                 + s.getLocalSocketAddress().toString(),
                 s.getRemoteSocketAddress()
@@ -760,9 +708,7 @@
         // now create one that is not connect and validate that we get the
         // right answer
         Socket theSocket = new Socket();
-        portNumber = Support_PortManager.getNextPort();
-        theSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(),
-                portNumber));
+        theSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));
 
         assertNull("Returned incorrect InetSocketAddress -unconnected socket:"
                 + "Expected: NULL", theSocket.getRemoteSocketAddress());
@@ -781,10 +727,8 @@
     }
 
     public void test_isBound() throws IOException {
-        InetAddress addr = InetAddress.getLocalHost();
-        int port = Support_PortManager.getNextPort();
-        ServerSocket serverSocket = new ServerSocket(port, 5, addr);
-        Socket theSocket = new Socket(addr, port);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        Socket theSocket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
         Socket servSock = serverSocket.accept();
         assertTrue("Socket indicated  not bound when it should be (1)",
                 theSocket.isBound());
@@ -793,14 +737,11 @@
 
         // now do it with the new constructors and revalidate. Connect causes
         // the socket to be bound
-        InetSocketAddress theAddress = new InetSocketAddress(InetAddress
-                .getLocalHost(), Support_PortManager.getNextPort());
         theSocket = new Socket();
         assertFalse("Socket indicated bound when it was not (2)", theSocket
                 .isBound());
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         servSock = serverSocket.accept();
         assertTrue("Socket indicated not bound when it should be (2)",
                 theSocket.isBound());
@@ -808,12 +749,10 @@
         serverSocket.close();
 
         // now test when we bind explicitly
-        InetSocketAddress theLocalAddress = new InetSocketAddress(InetAddress
-                .getLocalHost(), Support_PortManager.getNextPort());
         theSocket = new Socket();
         assertFalse("Socket indicated bound when it was not (3)", theSocket
                 .isBound());
-        theSocket.bind(theLocalAddress);
+        theSocket.bind(null);
         assertTrue("Socket indicated not bound when it should be (3a)",
                 theSocket.isBound());
         theSocket.close();
@@ -822,10 +761,8 @@
     }
 
     public void test_isConnected() throws IOException {
-        InetAddress addr = InetAddress.getLocalHost();
-        int port = Support_PortManager.getNextPort();
-        ServerSocket serverSocket = new ServerSocket(port, 5, addr);
-        Socket theSocket = new Socket(addr, port);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        Socket theSocket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
         Socket servSock = serverSocket.accept();
         assertTrue("Socket indicated  not connected when it should be",
                 theSocket.isConnected());
@@ -833,14 +770,11 @@
         serverSocket.close();
 
         // now do it with the new constructors and revalidate
-        InetSocketAddress theAddress = new InetSocketAddress(InetAddress
-                .getLocalHost(), Support_PortManager.getNextPort());
         theSocket = new Socket();
         assertFalse("Socket indicated connected when it was not", theSocket
                 .isConnected());
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         servSock = serverSocket.accept();
         assertTrue("Socket indicated  not connected when it should be",
                 theSocket.isConnected());
@@ -849,10 +783,8 @@
     }
 
     public void test_isClosed() throws IOException {
-        InetAddress addr = InetAddress.getLocalHost();
-        int port = Support_PortManager.getNextPort();
-        ServerSocket serverSocket = new ServerSocket(port, 5, addr);
-        Socket theSocket = new Socket(addr, port);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        Socket theSocket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
         Socket servSock = serverSocket.accept();
 
         // validate isClosed returns expected values
@@ -862,7 +794,7 @@
         assertTrue("Socket should indicate it is closed(1):", theSocket
                 .isClosed());
 
-        theSocket = new Socket(addr, port);
+        theSocket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
         assertFalse("Socket should indicate it is not closed(2):", theSocket
                 .isClosed());
         theSocket.close();
@@ -891,7 +823,7 @@
         try {
             theSocket.bind(new InetSocketAddress(InetAddress
                     .getByAddress(Support_Configuration.nonLocalAddressBytes),
-                    Support_PortManager.getNextPort()));
+                    80));
             fail("No exception when binding to bad address:"
                    + theSocket.getLocalSocketAddress().toString());
         } catch (IOException ex) {
@@ -900,39 +832,21 @@
 
         // now create a socket that is not bound and then bind it
         theSocket = new Socket();
-        int portNumber = Support_PortManager.getNextPort();
         theSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(),
-                portNumber));
+                0));
 
         // 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)));
+        assertEquals(new InetSocketAddress(InetAddress.getLocalHost(), theSocket.getLocalPort()),
+                     theSocket.getLocalSocketAddress());
 
         // make sure we can now connect and that connections appear to come
         // from the address we bound to.
-        InetSocketAddress theAddress = new InetSocketAddress(InetAddress
-                .getLocalHost(), Support_PortManager.getNextPort());
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         Socket servSock = serverSocket.accept();
-        assertTrue(
-                "Returned Remote address from server connected to does not match expected local address:"
-                        + servSock.getRemoteSocketAddress().toString()
-                        + " Expected: "
-                        + (new InetSocketAddress(InetAddress.getLocalHost(),
-                                portNumber)).toString(), servSock
-                        .getRemoteSocketAddress().equals(
-                                new InetSocketAddress(InetAddress
-                                        .getLocalHost(), portNumber)));
+        assertEquals(new InetSocketAddress(InetAddress.getLocalHost(), theSocket.getLocalPort()),
+                     servSock.getRemoteSocketAddress());
         theSocket.close();
         servSock.close();
         serverSocket.close();
@@ -951,10 +865,8 @@
         theSocket = new Socket();
         Socket theSocket2 = new Socket();
         try {
-            theAddress = new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort());
-            theSocket.bind(theAddress);
-            theSocket2.bind(theAddress);
+            theSocket.bind(null);
+            theSocket2.bind(theSocket.getLocalSocketAddress());
             fail("No exception binding to address that is not available");
         } catch (IOException ex) {
         }
@@ -1020,7 +932,7 @@
         }
 
         // start by validating the error checks
-        int portNumber = Support_PortManager.getNextPort();
+        int portNumber = 0;
         Socket theSocket = null;
         ServerSocket serverSocket = null;
         SocketAddress theAddress = null;
@@ -1084,17 +996,8 @@
 
         // now validate that we can actually connect when somebody is listening
         theSocket = new Socket();
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
-        theSocket.close();
-        serverSocket.close();
-
-        // now validate that we can actually connect when somebody is listening
-        theSocket = new Socket();
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
 
         // validate that when a socket is connected that it answers
         // correctly to related queries
@@ -1119,10 +1022,9 @@
         // are already connected
         try {
             theSocket = new Socket();
-            serverSocket = new ServerSocket();
-            serverSocket.bind(theAddress);
-            theSocket.connect(theAddress);
-            theSocket.connect(theAddress);
+            serverSocket = new ServerSocket(0, 5);
+            theSocket.connect(serverSocket.getLocalSocketAddress());
+            theSocket.connect(serverSocket.getLocalSocketAddress());
             theSocket.close();
             serverSocket.close();
             fail("No exception when we try to connect on a connected socket: ");
@@ -1145,9 +1047,8 @@
 
         // now validate that connected socket can be used to read/write
         theSocket = new Socket();
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         Socket servSock = serverSocket.accept();
         InputStream theInput = theSocket.getInputStream();
         OutputStream theOutput = servSock.getOutputStream();
@@ -1197,10 +1098,8 @@
         SocketChannel channel = SocketChannel.open();
         channel.configureBlocking(false);
         Socket socket = channel.socket();
-        int port = Support_PortManager.getNextPort();
         try {
-            socket.connect( new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort()));
+            socket.connect(serverSocket.getLocalSocketAddress());
             fail("IllegalBlockingModeException was not thrown.");
         } catch (IllegalBlockingModeException expected) {
         }
@@ -1263,28 +1162,14 @@
         }
 
         // start by validating the error checks
-        int portNumber = Support_PortManager.getNextPort();
+        byte[] theBytes = { 0, 0, 0, 0 };
+        SocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), 0);
+        SocketAddress nonConnectableAddress = new InetSocketAddress(InetAddress.getByAddress(theBytes), 0);
+        SocketAddress nonReachableAddress = new InetSocketAddress(InetAddress.getByName(Support_Configuration.ResolvedNotExistingHost), 0);
+        SocketAddress invalidType = new mySocketAddress();
+
         Socket theSocket = null;
         ServerSocket serverSocket = null;
-        SocketAddress theAddress = null;
-        SocketAddress nonConnectableAddress = null;
-        SocketAddress nonReachableAddress = null;
-        SocketAddress nonListeningAddress = null;
-        SocketAddress invalidType = null;
-        byte[] theBytes = { 0, 0, 0, 0 };
-
-        theAddress = new InetSocketAddress(InetAddress.getLocalHost(),
-                portNumber);
-        nonConnectableAddress = new InetSocketAddress(InetAddress
-                .getByAddress(theBytes), portNumber);
-        nonReachableAddress = new InetSocketAddress(InetAddress
-                .getByName(Support_Configuration.ResolvedNotExistingHost),
-                portNumber);
-        // make sure we get another port
-        Thread.sleep(7000);
-        nonListeningAddress = new InetSocketAddress(InetAddress.getLocalHost(),
-                Support_PortManager.getNextPort());
-        invalidType = new mySocketAddress();
 
         try {
             theSocket = new Socket();
@@ -1340,9 +1225,8 @@
 
         // now validate that we can actually connect when somebody is listening
         theSocket = new Socket();
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress, 0);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         theSocket.close();
         serverSocket.close();
 
@@ -1350,7 +1234,7 @@
         // an address on which nobody is listening
         try {
             theSocket = new Socket();
-            theSocket.connect(nonListeningAddress, 100000);
+            theSocket.connect(new InetSocketAddress(InetAddress.getLocalHost(), 80), 100000);
             theSocket.close();
             fail("No exception when connecting to address nobody listening on: ");
         } catch (Exception e) {
@@ -1390,12 +1274,9 @@
         }
 
         // now validate that we can actually connect when somebody is listening
-        new InetSocketAddress(InetAddress.getLocalHost(), Support_PortManager
-                .getNextPort());
         theSocket = new Socket();
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress, 100000);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
 
         // validate that when a socket is connected that it answers
         // correctly to related queries
@@ -1419,8 +1300,6 @@
         // now validate that we get the right exception if we connect when we
         // are already connected
         try {
-            new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort());
             theSocket = new Socket();
             serverSocket = new ServerSocket();
             serverSocket.bind(theAddress);
@@ -1447,12 +1326,9 @@
         }
 
         // now validate that connected socket can be used to read/write
-        new InetSocketAddress(InetAddress.getLocalHost(), Support_PortManager
-                .getNextPort());
         theSocket = new Socket();
-        serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress, 100000);
+        serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         Socket servSock = serverSocket.accept();
         InputStream theInput = theSocket.getInputStream();
         OutputStream theOutput = servSock.getOutputStream();
@@ -1515,10 +1391,8 @@
         SocketChannel channel = SocketChannel.open();
         channel.configureBlocking(false);
         Socket socket = channel.socket();
-        int port = Support_PortManager.getNextPort();
         try {
-            socket.connect( new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort()), port);
+            socket.connect(serverSocket.getLocalSocketAddress());
             fail("IllegalBlockingModeException was not thrown.");
         } catch (IllegalBlockingModeException expected) {
         }
@@ -1526,12 +1400,9 @@
     }
 
     public void test_isInputShutdown() throws IOException {
-        InetSocketAddress theAddress = new InetSocketAddress(InetAddress
-                .getLocalHost(), Support_PortManager.getNextPort());
         Socket theSocket = new Socket();
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         Socket servSock = serverSocket.accept();
         InputStream theInput = theSocket.getInputStream();
         OutputStream theOutput = servSock.getOutputStream();
@@ -1559,12 +1430,9 @@
     }
 
     public void test_isOutputShutdown() throws IOException {
-        InetSocketAddress theAddress = new InetSocketAddress(InetAddress
-                .getLocalHost(), Support_PortManager.getNextPort());
         Socket theSocket = new Socket();
-        ServerSocket serverSocket = new ServerSocket();
-        serverSocket.bind(theAddress);
-        theSocket.connect(theAddress);
+        ServerSocket serverSocket = new ServerSocket(0, 5);
+        theSocket.connect(serverSocket.getLocalSocketAddress());
         Socket servSock = serverSocket.accept();
         InputStream theInput = theSocket.getInputStream();
         OutputStream theOutput = servSock.getOutputStream();
@@ -1591,18 +1459,14 @@
 
     }
 
-    public void test_setReuseAddressZ() {
+    public void test_setReuseAddressZ() throws Exception {
 
         try {
             InetAddress allAddresses[] = InetAddress.getAllByName(InetAddress
                     .getLocalHost().getHostName());
             if (allAddresses.length > 1) {
 
-                InetSocketAddress theAddress = new InetSocketAddress(
-                        InetAddress.getLocalHost(), Support_PortManager
-                                .getNextPort());
-                ServerSocket serverSocket = new ServerSocket();
-                serverSocket.bind(theAddress);
+                ServerSocket serverSocket = new ServerSocket(0, 5);
 
                 // try to bind to port address that is already in use with
                 // reuseAddress = false.
@@ -1612,17 +1476,15 @@
                 // what the expected result is. It seems that on linux
                 // platforms we also don't get an exception.
                 InetSocketAddress theLocalAddress = new InetSocketAddress(
-                        (InetAddress) allAddresses[1], Support_PortManager
-                                .getNextPort());
+                        (InetAddress) allAddresses[1], 0);
                 InetSocketAddress theOtherLocalAddress = new InetSocketAddress(
-                        (InetAddress) allAddresses[0], theLocalAddress
-                                .getPort());
+                        (InetAddress) allAddresses[0], theLocalAddress.getPort());
                 Socket theSocket = new Socket();
                 theSocket.setReuseAddress(false);
                 theSocket.bind(theLocalAddress);
                 Socket theSocket2 = null;
                 String platform = System.getProperty("os.name");
-                try {
+
                     theSocket2 = new Socket();
                     theSocket2.setReuseAddress(false);
                     theSocket2.bind(theOtherLocalAddress);
@@ -1639,69 +1501,34 @@
                                 + ":"
                                 + theOtherLocalAddress.toString());
                     }
-                } catch (IOException ex) {
-                    if ((platform.startsWith("Linux"))
-                            || ((platform.startsWith("Windows")) && ((((InetAddress) allAddresses[0]) instanceof Inet4Address) && (((InetAddress) allAddresses[1]) instanceof Inet4Address)))) {
-                        fail("Got unexpected exception when binding with setReuseAddress false on windows platform:"
-                                + theAddress.toString() + ":" + ex.toString());
-                    }
-                }
                 theSocket.close();
                 theSocket2.close();
 
                 // try to bind to port that is already in use with reuseAddress
                 // = true
-                theLocalAddress = new InetSocketAddress(
-                        (InetAddress) allAddresses[0], Support_PortManager
-                                .getNextPort());
-                theOtherLocalAddress = new InetSocketAddress(
-                        (InetAddress) allAddresses[1], theLocalAddress
-                                .getPort());
+                theLocalAddress = new InetSocketAddress((InetAddress) allAddresses[0], 0);
 
                 theSocket = new Socket();
                 theSocket.setReuseAddress(true);
                 theSocket.bind(theLocalAddress);
-                try {
-                    theSocket2 = new Socket();
-                    theSocket2.setReuseAddress(true);
-                    theSocket2.bind(theOtherLocalAddress);
-                    theSocket2.close();
-                } catch (IOException ex) {
-                    fail("IOException when setReuseAddress is true and we bind :"
-                            + ex.toString());
-                }
+                theSocket2 = new Socket();
+                theSocket2.setReuseAddress(true);
+                theOtherLocalAddress = new InetSocketAddress((InetAddress) allAddresses[1], theSocket.getLocalPort());
+                theSocket2.bind(theOtherLocalAddress);
+                theSocket2.close();
                 theSocket.close();
                 serverSocket.close();
 
                 // try with default behavior which should be the same on all
                 // platforms
-                theLocalAddress = new InetSocketAddress(
-                        (InetAddress) allAddresses[0], Support_PortManager
-                                .getNextPort());
-                theOtherLocalAddress = new InetSocketAddress(
-                        (InetAddress) allAddresses[1], theLocalAddress
-                                .getPort());
+                theLocalAddress = new InetSocketAddress((InetAddress) allAddresses[0], 0);
 
                 theSocket = new Socket();
                 theSocket.bind(theLocalAddress);
-                try {
-                    theSocket2 = new Socket();
-                    theSocket2.bind(theOtherLocalAddress);
-                    theSocket2.close();
-                    if ((!platform.startsWith("Linux"))
-                            && ((!platform.startsWith("Windows")) || !((((InetAddress) allAddresses[0]) instanceof Inet4Address) && (((InetAddress) allAddresses[1]) instanceof Inet4Address)))) {
-                        fail("No exception when setReuseAddress is default and we bind:"
-                                + theLocalAddress.toString()
-                                + ":"
-                                + theOtherLocalAddress.toString());
-                    }
-                } catch (IOException ex) {
-                    if ((platform.startsWith("Linux"))
-                            || ((platform.startsWith("Windows")) && ((((InetAddress) allAddresses[0]) instanceof Inet4Address) && (((InetAddress) allAddresses[1]) instanceof Inet4Address)))) {
-                        fail("Got unexpected exception when binding with setReuseAddress default on windows platform:"
-                                + theAddress.toString() + ":" + ex.toString());
-                    }
-                }
+                theSocket2 = new Socket();
+                theOtherLocalAddress = new InetSocketAddress((InetAddress) allAddresses[1], theSocket.getLocalPort());
+                theSocket2.bind(theOtherLocalAddress);
+                theSocket2.close();
                 theSocket.close();
                 serverSocket.close();
 
@@ -1754,8 +1581,6 @@
     public void test_setOOBInlineZ() {
         // mostly tested in getOOBInline. Just set to make sure call works ok
         try {
-            new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort());
             Socket theSocket = new Socket();
             theSocket.setOOBInline(true);
             assertTrue("expected OOBIline to be true", theSocket.getOOBInline());
@@ -1779,8 +1604,6 @@
     public void test_getOOBInline() {
 
         try {
-            new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort());
             Socket theSocket = new Socket();
 
             // validate that value reflects what we set it to true after true,
@@ -1812,8 +1635,6 @@
             int IPTOS_LOWCOST = 0x2;
             int IPTOS_THROUGHPUT = 0x8;
 
-            new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort());
             Socket theSocket = new Socket();
 
             // validate that value set must be between 0 and 255
@@ -1851,8 +1672,6 @@
 
     public void test_getTrafficClass() {
         try {
-            new InetSocketAddress(InetAddress.getLocalHost(),
-                    Support_PortManager.getNextPort());
             Socket theSocket = new Socket();
 
             /*
@@ -1888,13 +1707,9 @@
             // is silently ignored
             String urgentData = "U";
             try {
-                InetSocketAddress theAddress = new InetSocketAddress(
-                        InetAddress.getLocalHost(), Support_PortManager
-                                .getNextPort());
                 Socket theSocket = new Socket();
-                ServerSocket serverSocket = new ServerSocket();
-                serverSocket.bind(theAddress);
-                theSocket.connect(theAddress);
+                ServerSocket serverSocket = new ServerSocket(0, 5);
+                theSocket.connect(serverSocket.getLocalSocketAddress());
                 Socket servSock = serverSocket.accept();
                 InputStream theInput = theSocket.getInputStream();
                 OutputStream theOutput = servSock.getOutputStream();
@@ -1931,12 +1746,9 @@
 
                 // now validate that urgent data is received as expected. Expect
                 // that it should be between the two writes.
-                theAddress = new InetSocketAddress(InetAddress.getLocalHost(),
-                        Support_PortManager.getNextPort());
                 theSocket = new Socket();
-                serverSocket = new ServerSocket();
-                serverSocket.bind(theAddress);
-                theSocket.connect(theAddress);
+                serverSocket = new ServerSocket(0, 5);
+                theSocket.connect(serverSocket.getLocalSocketAddress());
                 servSock = serverSocket.accept();
                 theInput = theSocket.getInputStream();
                 theOutput = servSock.getOutputStream();
@@ -1973,12 +1785,9 @@
                 serverSocket.close();
 
                 // now test case where we try to send two urgent bytes.
-                theAddress = new InetSocketAddress(InetAddress.getLocalHost(),
-                        Support_PortManager.getNextPort());
                 theSocket = new Socket();
-                serverSocket = new ServerSocket();
-                serverSocket.bind(theAddress);
-                theSocket.connect(theAddress);
+                serverSocket = new ServerSocket(0, 5);
+                theSocket.connect(serverSocket.getLocalSocketAddress());
                 servSock = serverSocket.accept();
                 theInput = theSocket.getInputStream();
                 theOutput = servSock.getOutputStream();
@@ -2022,12 +1831,9 @@
                  */
                 if (!platform.startsWith("Windows")) {
                     // now test the case were we send turn the OOBInline on/off
-                    theAddress = new InetSocketAddress(InetAddress
-                            .getLocalHost(), Support_PortManager.getNextPort());
                     theSocket = new Socket();
-                    serverSocket = new ServerSocket();
-                    serverSocket.bind(theAddress);
-                    theSocket.connect(theAddress);
+                    serverSocket = new ServerSocket(0, 5);
+                    theSocket.connect(serverSocket.getLocalSocketAddress());
                     servSock = serverSocket.accept();
                     theInput = theSocket.getInputStream();
                     theOutput = servSock.getOutputStream();
@@ -2139,12 +1945,9 @@
                 }
 
                 // now test the case where there is only urgent data
-                theAddress = new InetSocketAddress(InetAddress.getLocalHost(),
-                        Support_PortManager.getNextPort());
                 theSocket = new Socket();
-                serverSocket = new ServerSocket();
-                serverSocket.bind(theAddress);
-                theSocket.connect(theAddress);
+                serverSocket = new ServerSocket(0, 5);
+                theSocket.connect(serverSocket.getLocalSocketAddress());
                 servSock = serverSocket.accept();
                 theInput = theSocket.getInputStream();
                 theOutput = servSock.getOutputStream();
@@ -2370,13 +2173,9 @@
 
     }
 
-    /**
-     *
-     */
     protected int startServer(String name) {
-        int portNumber = Support_PortManager.getNextPort();
         try {
-            ss = new ServerSocket(portNumber, 5);
+            ss = new ServerSocket(0, 5);
         } catch (IOException e) {
             fail(name + ": " + e);
         }
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 762bac4..1f2ebf9 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -795,6 +795,27 @@
         assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*");
     }
 
+    // Don't disconnect after building a tunnel with CONNECT
+    // http://code.google.com/p/android/issues/detail?id=37221
+    public void testProxyWithConnectionClose() throws IOException {
+        TestSSLContext testSSLContext = TestSSLContext.create();
+        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
+        server.enqueue(new MockResponse()
+                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
+                .clearHeaders());
+        server.enqueue(new MockResponse().setBody("this response comes via a proxy"));
+        server.play();
+
+        URL url = new URL("https://android.com/foo");
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(
+                server.toProxyAddress());
+        connection.setRequestProperty("Connection", "close");
+        connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+        connection.setHostnameVerifier(new RecordingHostnameVerifier());
+
+        assertContent("this response comes via a proxy", connection);
+    }
+
     public void testDisconnectedConnection() throws IOException {
         server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"));
         server.play();
@@ -989,21 +1010,25 @@
         URLConnection connection = server.getUrl("/").openConnection();
         assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
         assertNull(connection.getContentEncoding());
+        assertEquals(-1, connection.getContentLength());
 
         RecordedRequest request = server.takeRequest();
         assertContains(request.getHeaders(), "Accept-Encoding: gzip");
     }
 
     public void testClientConfiguredGzipContentEncoding() throws Exception {
+        byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"));
         server.enqueue(new MockResponse()
-                .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")))
-                .addHeader("Content-Encoding: gzip"));
+                .setBody(bodyBytes)
+                .addHeader("Content-Encoding: gzip")
+                .addHeader("Content-Length: " + bodyBytes.length));
         server.play();
 
         URLConnection connection = server.getUrl("/").openConnection();
         connection.addRequestProperty("Accept-Encoding", "gzip");
         InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
         assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
+        assertEquals(bodyBytes.length, connection.getContentLength());
 
         RecordedRequest request = server.takeRequest();
         assertContains(request.getHeaders(), "Accept-Encoding: gzip");
@@ -1620,7 +1645,7 @@
      * addresses. This is typically one IPv4 address and one IPv6 address.
      */
     public void testConnectTimeouts() throws IOException {
-        StuckServer ss = new StuckServer();
+        StuckServer ss = new StuckServer(false);
         int serverPort = ss.getLocalPort();
         URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection();
         int timeout = 1000;
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 f73b6d5..4fc70c4 100644
--- a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java
+++ b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java
@@ -55,11 +55,10 @@
     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();
+        StuckServer ss = new StuckServer(true);
         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());
diff --git a/luni/src/test/java/libcore/java/security/KeyStoreTest.java b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
index 15314c9..ddee6ce 100644
--- a/luni/src/test/java/libcore/java/security/KeyStoreTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
@@ -45,6 +45,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -65,10 +66,12 @@
     private static final String ALIAS_SECRET = "secret";
 
     private static final String ALIAS_ALT_CASE_PRIVATE = "pRiVaTe";
+    private static final String ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE = "PrIvAtE-no-password";
     private static final String ALIAS_ALT_CASE_CERTIFICATE = "cErTiFiCaTe";
     private static final String ALIAS_ALT_CASE_SECRET = "sEcRet";
 
     private static final String ALIAS_UNICODE_PRIVATE = "\u6400\u7902\u3101\u8c02\u5002\u8702\udd01";
+    private static final String ALIAS_UNICODE_NO_PASSWORD_PRIVATE = "\u926c\u0967\uc65b\ubc78";
     private static final String ALIAS_UNICODE_CERTIFICATE = "\u5402\udd01\u7902\u8702\u3101\u5f02\u3101\u5402\u5002\u8702\udd01";
     private static final String ALIAS_UNICODE_SECRET = "\ue224\ud424\ud224\ue124\ud424\ue324";
 
@@ -146,7 +149,8 @@
         // JKS key stores cannot store secret keys, neither can the RI's PKCS12
         return (!(ks.getType().equals("JKS")
                   || ks.getType().equals("CaseExactJKS")
-                  || (ks.getType().equals("PKCS12"))));
+                  || (ks.getType().equals("PKCS12"))
+                  || (ks.getType().equals("AndroidKeyStore"))));
     }
 
     private static boolean isCertificateEnabled(KeyStore ks) {
@@ -157,13 +161,16 @@
     private static boolean isCaseSensitive(KeyStore ks) {
         return (ks.getType().equals("CaseExactJKS")
                 || ks.getType().equals("BKS")
-                || ks.getType().equals("BouncyCastle"));
+                || ks.getType().equals("BouncyCastle")
+                || ks.getType().equals("AndroidKeyStore"));
 
     }
 
     private static boolean isUnsupported(KeyStore ks) {
         // Don't bother testing BC on RI
-        return (StandardNames.IS_RI && ks.getProvider().getName().equals("BC"));
+        // TODO enable AndroidKeyStore when CTS can set up the keystore
+        return (StandardNames.IS_RI && ks.getProvider().getName().equals("BC"))
+                || "AndroidKeyStore".equalsIgnoreCase(ks.getType());
     }
 
     private static boolean isNullPasswordAllowed(KeyStore ks) {
@@ -172,7 +179,9 @@
                   || ks.getType().equals("JCEKS")
                   || ks.getType().equals("PKCS12")));
     }
-
+    private static boolean isKeyPasswordSupported(KeyStore ks) {
+        return !ks.getType().equals("AndroidKeyStore");
+    }
     private static boolean isKeyPasswordIgnored(KeyStore ks) {
         // BouncyCastle's PKCS12 ignores the key password unlike the RI which requires it
         return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC"));
@@ -183,6 +192,14 @@
         return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC"));
     }
 
+    private static boolean isPersistentStorage(KeyStore ks) {
+        return ks.getType().equalsIgnoreCase("AndroidKeyStore");
+    }
+
+    private static boolean isLoadStoreUnsupported(KeyStore ks) {
+        return ks.getType().equalsIgnoreCase("AndroidKeyStore");
+    }
+
     private static boolean isSetKeyByteArrayUnimplemented(KeyStore ks) {
         // All of BouncyCastle's
         // KeyStore.setKeyEntry(String,byte[],char[]) implementations
@@ -203,16 +220,13 @@
     }
 
     public static void populate(KeyStore ks) throws Exception {
-        ks.load(null, null);
-        if (isReadOnly(ks)) {
-            try {
-                setPrivateKey(ks);
-                fail(ks.toString());
-            } catch (UnsupportedOperationException e) {
-            }
+        boolean readOnly = clearKeyStore(ks);
+        if (readOnly) {
             return;
         }
-        setPrivateKey(ks);
+        if (isKeyPasswordSupported(ks)) {
+            setPrivateKey(ks);
+        }
         if (isNullPasswordAllowed(ks)) {
             ks.setKeyEntry(ALIAS_NO_PASSWORD_PRIVATE,
                            getPrivateKey().getPrivateKey(),
@@ -234,6 +248,30 @@
         }
     }
 
+    private static boolean clearKeyStore(KeyStore ks) throws Exception {
+        ks.load(null, null);
+        if (isReadOnly(ks)) {
+            try {
+                setPrivateKey(ks);
+                fail(ks.toString());
+            } catch (UnsupportedOperationException e) {
+            }
+            return true;
+        }
+        if (isPersistentStorage(ks)) {
+            Enumeration<String> aliases = ks.aliases();
+            while (aliases.hasMoreElements()) {
+                String alias = aliases.nextElement();
+                ks.deleteEntry(alias);
+            }
+        }
+        return false;
+    }
+
+    public static void setPrivateKeyNoPassword(KeyStore ks, String alias, PrivateKeyEntry privateKey)
+            throws Exception {
+        ks.setKeyEntry(alias, privateKey.getPrivateKey(), null, privateKey.getCertificateChain());
+    }
     public static void setPrivateKey(KeyStore ks) throws Exception {
         setPrivateKey(ks, ALIAS_PRIVATE);
     }
@@ -492,7 +530,12 @@
             if (isReadOnly(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
             } else {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                }
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                }
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 } else {
@@ -503,21 +546,27 @@
             // test case insensitive
             if (isCaseSensitive(keyStore) || isReadOnly(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, PASSWORD_KEY));
                 assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
             } else {
-                assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                }
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                }
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
                 }
             }
 
             // test with null passwords
-            if (isKeyPasswordIgnored(keyStore)) {
+            if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) {
                 assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, null));
             } else {
                 if (isReadOnly(keyStore)) {
                     assertNull(keyStore.getKey(ALIAS_PRIVATE, null));
-                } else {
+                } else if (isKeyPasswordSupported(keyStore)) {
                     try {
                         keyStore.getKey(ALIAS_PRIVATE, null);
                         fail(keyStore.getType());
@@ -546,9 +595,9 @@
             // test with bad passwords
             if (isReadOnly(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_PRIVATE, null));
-            } else if (isKeyPasswordIgnored(keyStore)) {
+            } else if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) {
                 assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, null));
-            } else {
+            } else if (isKeyPasswordSupported(keyStore)) {
                 try {
                     keyStore.getKey(ALIAS_PRIVATE, PASSWORD_BAD);
                     fail(keyStore.getType());
@@ -593,8 +642,10 @@
             // test case sensitive
             if (isReadOnly(keyStore)) {
                 assertNull(keyStore.getCertificateChain(ALIAS_PRIVATE));
-            } else {
+            } else if (isKeyPasswordSupported(keyStore)) {
                 assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            } else if (isNullPasswordAllowed(keyStore)) {
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE));
             }
 
             // test case insensitive
@@ -657,9 +708,10 @@
         }
         long before = System.currentTimeMillis();
         for (KeyStore keyStore : keyStores()) {
+            populate(keyStore);
+
             // add 1000 since some key stores round of time to nearest second
             long after = System.currentTimeMillis() + 1000;
-            populate(keyStore);
 
             // test odd inputs
             try {
@@ -673,8 +725,10 @@
             if (!isReadOnly(keyStore) && isCertificateEnabled(keyStore)) {
                 Date date = keyStore.getCreationDate(ALIAS_CERTIFICATE);
                 assertNotNull(date);
-                assertTrue(before <= date.getTime());
-                assertTrue(date.getTime() <= after);
+                assertTrue("date should be after start time: " + date.getTime() + " >= " + before,
+                        before <= date.getTime());
+                assertTrue("date should be before expiry time: " + date.getTime() + " <= " + after,
+                        date.getTime() <= after);
             } else {
                 assertNull(keyStore.getCreationDate(ALIAS_CERTIFICATE));
             }
@@ -737,15 +791,24 @@
                                      PASSWORD_KEY,
                                      null);
                 fail(keyStore.getType());
-            } catch (IllegalArgumentException expected) {
+            } catch (Exception e) {
+                if (e.getClass() != IllegalArgumentException.class
+                        && e.getClass() != KeyStoreException.class) {
+                    throw e;
+                }
             }
         }
 
         for (KeyStore keyStore : keyStores()) {
-            keyStore.load(null, null);
+            clearKeyStore(keyStore);
 
             // test case sensitive
-            assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+            if (isKeyPasswordSupported(keyStore)) {
+                assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                assertNull(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+            }
             if (isReadOnly(keyStore)) {
                 try {
                     keyStore.setKeyEntry(ALIAS_SECRET, getSecretKey(), PASSWORD_KEY, null);
@@ -754,9 +817,16 @@
                 }
                 continue;
             }
-            setPrivateKey(keyStore);
-            assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-            assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                setPrivateKey(keyStore);
+                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                setPrivateKeyNoPassword(keyStore, ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey());
+                assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE));
+            }
             if (isSecretKeyEnabled(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 setSecretKey(keyStore);
@@ -783,11 +853,22 @@
                 assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
             } else if (isCaseSensitive(keyStore)) {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
-                setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                    setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                }
+
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                    setPrivateKeyNoPassword(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE,
+                            getPrivateKey2());
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                }
 
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
@@ -797,11 +878,22 @@
                     assertSecretKey2(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
                 }
             } else {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
-                setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
-                assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                    setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
+                    assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                }
+
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, null));
+                    assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                    setPrivateKey(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, getPrivateKey2());
+                    assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, null));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                }
+
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                     assertSecretKey(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
@@ -913,10 +1005,15 @@
                 continue;
             }
 
-            keyStore.load(null, null);
+            clearKeyStore(keyStore);
 
             // test case sensitive
-            assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+            if (isKeyPasswordSupported(keyStore)) {
+                assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                assertNull(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+            }
             if (isReadOnly(keyStore)) {
                 try {
                     setPrivateKeyBytes(keyStore);
@@ -925,9 +1022,16 @@
                 }
                 continue;
             }
-            setPrivateKeyBytes(keyStore);
-            assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-            assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                setPrivateKeyBytes(keyStore);
+                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                setPrivateKeyNoPassword(keyStore, ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey());
+                assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE));
+            }
             if (isSecretKeyEnabled(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 setSecretKeyBytes(keyStore);
@@ -959,11 +1063,21 @@
                 assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
             } else if (isCaseSensitive(keyStore)) {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
-                setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                    setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                }
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                    setPrivateKeyNoPassword(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE,
+                            getPrivateKey2());
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                }
 
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
@@ -973,11 +1087,21 @@
                     assertSecretKey2(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
                 }
             } else {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
-                setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
-                assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                    setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2());
+                    assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                }
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                    setPrivateKeyNoPassword(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE,
+                            getPrivateKey2());
+                    assertPrivateKey2(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                }
 
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
@@ -1031,13 +1155,17 @@
                 try {
                     int size = keyStore.size();
                     keyStore.setCertificateEntry(ALIAS_CERTIFICATE, null);
-                    assertNull(keyStore.getCertificate(ALIAS_CERTIFICATE));
-                    assertEquals(size, keyStore.size());
-                    assertTrue(keyStore.isCertificateEntry(ALIAS_CERTIFICATE));
-                    assertTrue(Collections.list(keyStore.aliases()).contains(ALIAS_CERTIFICATE));
+                    assertNull(keyStore.getType(), keyStore.getCertificate(ALIAS_CERTIFICATE));
+                    assertEquals(keyStore.getType(), size, keyStore.size());
+                    assertTrue(keyStore.getType(), keyStore.isCertificateEntry(ALIAS_CERTIFICATE));
+                    assertTrue(keyStore.getType(),
+                            Collections.list(keyStore.aliases()).contains(ALIAS_CERTIFICATE));
                 } catch (NullPointerException expectedSometimes) {
-                    assertEquals("PKCS12", keyStore.getType());
-                    assertEquals("BC", keyStore.getProvider().getName());
+                    if (!("PKCS12".equalsIgnoreCase(keyStore.getType()) &&
+                                "BC".equalsIgnoreCase(keyStore.getProvider().getName()))
+                            && !"AndroidKeyStore".equalsIgnoreCase(keyStore.getType())) {
+                        throw expectedSometimes;
+                    }
                 }
             } else {
                 try {
@@ -1053,9 +1181,8 @@
                 continue;
             }
 
-            keyStore.load(null, null);
+            clearKeyStore(keyStore);
 
-            // test case sensitive
             assertNull(keyStore.getCertificate(ALIAS_CERTIFICATE));
             if (isReadOnly(keyStore)) {
                 try {
@@ -1077,10 +1204,8 @@
             populate(keyStore);
 
             if (isReadOnly(keyStore)) {
-                assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
-                assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
-                assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
+                assertNull(keyStore.getCertificate(ALIAS_CERTIFICATE));
+                assertNull(keyStore.getCertificate(ALIAS_ALT_CASE_CERTIFICATE));
             } else if (isCaseSensitive(keyStore)) {
                 assertCertificate(keyStore.getCertificate(ALIAS_CERTIFICATE));
                 assertNull(keyStore.getCertificate(ALIAS_ALT_CASE_CERTIFICATE));
@@ -1146,10 +1271,18 @@
             }
 
             // test case sensitive
-            assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-            assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
-            keyStore.deleteEntry(ALIAS_PRIVATE);
-            assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+            if (isKeyPasswordSupported(keyStore)) {
+                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+                keyStore.deleteEntry(ALIAS_PRIVATE);
+                assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE));
+                keyStore.deleteEntry(ALIAS_NO_PASSWORD_PRIVATE);
+                assertNull(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+            }
 
             if (isSecretKeyEnabled(keyStore)) {
                 assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
@@ -1174,9 +1307,16 @@
             // test case insensitive
 
             if (isCaseSensitive(keyStore)) {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                keyStore.deleteEntry(ALIAS_ALT_CASE_PRIVATE);
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    keyStore.deleteEntry(ALIAS_ALT_CASE_PRIVATE);
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                }
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    keyStore.deleteEntry(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE);
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                }
 
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
@@ -1208,10 +1348,14 @@
 
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
-            if (hasDefaultContents(keyStore)) {
-                assertTrue(keyStore.aliases().hasMoreElements());
+            if (isPersistentStorage(keyStore)) {
+                assertNotNull("Should be able to query size: " + keyStore.getType(),
+                        keyStore.aliases());
+            } else if (hasDefaultContents(keyStore)) {
+                assertTrue("Should have more than one alias already: " + keyStore.getType(),
+                        keyStore.aliases().hasMoreElements());
             } else {
-                assertEquals(Collections.EMPTY_SET,
+                assertEquals("Should have no aliases:" + keyStore.getType(), Collections.EMPTY_SET,
                         new HashSet(Collections.list(keyStore.aliases())));
             }
         }
@@ -1220,7 +1364,9 @@
             populate(keyStore);
 
             Set<String> expected = new HashSet<String>();
-            expected.add(ALIAS_PRIVATE);
+            if (isKeyPasswordSupported(keyStore)) {
+                expected.add(ALIAS_PRIVATE);
+            }
             if (isNullPasswordAllowed(keyStore)) {
                 expected.add(ALIAS_NO_PASSWORD_PRIVATE);
             }
@@ -1233,7 +1379,10 @@
             if (isCertificateEnabled(keyStore)) {
                 expected.add(ALIAS_CERTIFICATE);
             }
-            if (hasDefaultContents(keyStore)) {
+            if (isPersistentStorage(keyStore)) {
+                assertNotNull("Should be able to query size: " + keyStore.getType(),
+                        keyStore.aliases());
+            } else if (hasDefaultContents(keyStore)) {
                 assertTrue(keyStore.aliases().hasMoreElements());
             } else {
                 assertEquals(expected, new HashSet<String>(Collections.list(keyStore.aliases())));
@@ -1271,7 +1420,11 @@
                 assertFalse(keyStore.containsAlias(ALIAS_PRIVATE));
                 continue;
             }
-            assertTrue(keyStore.containsAlias(ALIAS_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                assertTrue(keyStore.containsAlias(ALIAS_PRIVATE));
+            } else if (isNullPasswordAllowed(keyStore)) {
+                assertTrue(keyStore.containsAlias(ALIAS_NO_PASSWORD_PRIVATE));
+            }
             assertEquals(isSecretKeyEnabled(keyStore), keyStore.containsAlias(ALIAS_SECRET));
             assertEquals(isCertificateEnabled(keyStore), keyStore.containsAlias(ALIAS_CERTIFICATE));
 
@@ -1295,21 +1448,29 @@
 
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
-            if (hasDefaultContents(keyStore)) {
-                assertTrue(keyStore.size() > 0);
+            if (isPersistentStorage(keyStore)) {
+                assertTrue("Should successfully query size: " + keyStore.getType(),
+                        keyStore.size() >= 0);
+            } else if (hasDefaultContents(keyStore)) {
+                assertTrue("Should have non-empty store: " + keyStore.getType(),
+                        keyStore.size() > 0);
             } else {
-                assertEquals(0, keyStore.size());
+                assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size());
             }
         }
 
         for (KeyStore keyStore : keyStores()) {
             populate(keyStore);
             if (hasDefaultContents(keyStore)) {
-                assertTrue(keyStore.size() > 0);
+                assertTrue("Should have non-empty store: " + keyStore.getType(),
+                        keyStore.size() > 0);
                 continue;
             }
 
-            int expected = 1;
+            int expected = 0;
+            if (isKeyPasswordSupported(keyStore)) {
+                expected++;
+            }
             if (isNullPasswordAllowed(keyStore)) {
                 expected++;
             }
@@ -1355,7 +1516,12 @@
                 assertFalse(keyStore.isKeyEntry(ALIAS_PRIVATE));
                 continue;
             }
-            assertTrue(keyStore.isKeyEntry(ALIAS_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                assertTrue(keyStore.isKeyEntry(ALIAS_PRIVATE));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                assertTrue(keyStore.isKeyEntry(ALIAS_NO_PASSWORD_PRIVATE));
+            }
             assertEquals(isSecretKeyEnabled(keyStore), keyStore.isKeyEntry(ALIAS_SECRET));
             assertFalse(keyStore.isKeyEntry(ALIAS_CERTIFICATE));
 
@@ -1397,7 +1563,12 @@
 
             assertFalse(keyStore.isCertificateEntry(""));
 
-            assertFalse(keyStore.isCertificateEntry(ALIAS_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                assertFalse(keyStore.isCertificateEntry(ALIAS_PRIVATE));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                assertFalse(keyStore.isCertificateEntry(ALIAS_NO_PASSWORD_PRIVATE));
+            }
             assertFalse(keyStore.isCertificateEntry(ALIAS_SECRET));
             assertEquals(isCertificateEnabled(keyStore) && !isReadOnly(keyStore),
                     keyStore.isCertificateEntry(ALIAS_CERTIFICATE));
@@ -1429,7 +1600,9 @@
             populate(keyStore);
 
             Set<String> expected = new HashSet<String>();
-            expected.add(ALIAS_PRIVATE);
+            if (isKeyPasswordSupported(keyStore)) {
+                expected.add(ALIAS_PRIVATE);
+            }
             if (isNullPasswordAllowed(keyStore)) {
                 expected.add(ALIAS_NO_PASSWORD_PRIVATE);
             }
@@ -1487,7 +1660,7 @@
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
             ByteArrayOutputStream out = new ByteArrayOutputStream();
-            if (isReadOnly(keyStore)) {
+            if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) {
                 try {
                     keyStore.store(out, null);
                     fail(keyStore.getType());
@@ -1517,11 +1690,11 @@
             populate(keyStore);
 
             ByteArrayOutputStream out = new ByteArrayOutputStream();
-            if (isReadOnly(keyStore)) {
+            if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) {
                 try {
                     keyStore.store(out, null);
                     fail(keyStore.getType());
-                } catch (UnsupportedOperationException e) {
+                } catch (UnsupportedOperationException expected) {
                 }
             } else if (isNullPasswordAllowed(keyStore)) {
                 keyStore.store(out, null);
@@ -1542,7 +1715,7 @@
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
             ByteArrayOutputStream out = new ByteArrayOutputStream();
-            if (isReadOnly(keyStore)) {
+            if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) {
                 try {
                     keyStore.store(out, PASSWORD_STORE);
                     fail(keyStore.getType());
@@ -1557,7 +1730,7 @@
         for (KeyStore keyStore : keyStores()) {
             populate(keyStore);
             ByteArrayOutputStream out = new ByteArrayOutputStream();
-            if (isReadOnly(keyStore)) {
+            if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) {
                 try {
                     keyStore.store(out, PASSWORD_STORE);
                     fail(keyStore.getType());
@@ -1596,19 +1769,30 @@
     public void test_KeyStore_load_InputStream() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
-            if (hasDefaultContents(keyStore)) {
-                assertTrue(keyStore.size() > 0);
+            if (isPersistentStorage(keyStore)) {
+                assertTrue("Should be able to query size: " + keyStore.getType(),
+                        keyStore.size() >= 0);
+            } else if (hasDefaultContents(keyStore)) {
+                assertTrue("Should have non-empty store: " + keyStore.getType(),
+                        keyStore.size() > 0);
             } else {
-                assertEquals(0, keyStore.size());
+                assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size());
             }
         }
 
         for (KeyStore keyStore : keyStores()) {
+            if (isLoadStoreUnsupported(keyStore)) {
+                continue;
+            }
             keyStore.load(null, PASSWORD_STORE);
-            if (hasDefaultContents(keyStore)) {
-                assertTrue(keyStore.size() > 0);
+            if (isPersistentStorage(keyStore)) {
+                assertTrue("Should be able to query size: " + keyStore.getType(),
+                        keyStore.size() >= 0);
+            } else if (hasDefaultContents(keyStore)) {
+                assertTrue("Should have non-empty store: " + keyStore.getType(),
+                        keyStore.size() > 0);
             } else {
-                assertEquals(0, keyStore.size());
+                assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size());
             }
         }
 
@@ -1618,10 +1802,14 @@
     public void test_KeyStore_load_LoadStoreParameter() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null);
-            if (hasDefaultContents(keyStore)) {
-                assertTrue(keyStore.size() > 0);
+            if (isPersistentStorage(keyStore)) {
+                assertTrue("Should be able to query size: " + keyStore.getType(),
+                        keyStore.size() >= 0);
+            } else if (hasDefaultContents(keyStore)) {
+                assertTrue("Should have non-empty store: " + keyStore.getType(),
+                        keyStore.size() > 0);
             } else {
-                assertEquals(0, keyStore.size());
+                assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size());
             }
         }
 
@@ -1668,7 +1856,11 @@
             if (isReadOnly(keyStore)) {
                 assertNull(keyStore.getEntry(ALIAS_PRIVATE, PARAM_KEY));
             } else {
-                assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, PARAM_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, PARAM_KEY));
+                } else if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getEntry(ALIAS_NO_PASSWORD_PRIVATE, null));
+                }
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getEntry(ALIAS_SECRET, PARAM_KEY));
                 } else {
@@ -1704,9 +1896,9 @@
                 assertNull(keyStore.getEntry(ALIAS_NO_PASSWORD_PRIVATE, null));
             } else if (isNullPasswordAllowed(keyStore)) {
                 assertPrivateKey(keyStore.getEntry(ALIAS_NO_PASSWORD_PRIVATE, null));
-            } else if (isKeyPasswordIgnored(keyStore)) {
+            } else if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) {
                 assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, null));
-            } else {
+            } else if (isKeyPasswordIgnored(keyStore)) {
                 try {
                     keyStore.getEntry(ALIAS_PRIVATE, null);
                     fail(keyStore.getType());
@@ -1734,9 +1926,9 @@
             // test with bad passwords
             if (isReadOnly(keyStore)) {
                 assertNull(keyStore.getEntry(ALIAS_PRIVATE, PARAM_BAD));
-            } else if (isKeyPasswordIgnored(keyStore)) {
+            } else if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) {
                 assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, PARAM_BAD));
-            } else {
+            } else if (isKeyPasswordSupported(keyStore)) {
                 try {
                     keyStore.getEntry(ALIAS_PRIVATE, PARAM_BAD);
                     fail(keyStore.getType());
@@ -1773,7 +1965,7 @@
 
             try {
                 keyStore.setEntry(ALIAS_PRIVATE, getPrivateKey(), new FakeProtectionParameter());
-                fail("Should not accept unknown ProtectionParameter");
+                fail("Should not accept unknown ProtectionParameter: " + keyStore.getProvider());
             } catch (KeyStoreException expected) {
             }
         }
@@ -1808,7 +2000,7 @@
         }
 
         for (KeyStore keyStore : keyStores()) {
-            keyStore.load(null, null);
+            clearKeyStore(keyStore);
 
             // test case sensitive
             assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
@@ -1820,9 +2012,16 @@
                 }
                 continue;
             }
-            keyStore.setEntry(ALIAS_PRIVATE, getPrivateKey(), PARAM_KEY);
-            assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-            assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                keyStore.setEntry(ALIAS_PRIVATE, getPrivateKey(), PARAM_KEY);
+                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                keyStore.setEntry(ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey(), null);
+                assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE));
+            }
             if (isSecretKeyEnabled(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 keyStore.setEntry(ALIAS_SECRET, new SecretKeyEntry(getSecretKey()), PARAM_KEY);
@@ -1849,9 +2048,17 @@
                 } catch (KeyStoreException expected) {
                 }
             }
-            keyStore.setEntry(ALIAS_UNICODE_PRIVATE, getPrivateKey(), PARAM_KEY);
-            assertPrivateKey(keyStore.getKey(ALIAS_UNICODE_PRIVATE, PASSWORD_KEY));
-            assertCertificateChain(keyStore.getCertificateChain(ALIAS_UNICODE_PRIVATE));
+            if (isKeyPasswordSupported(keyStore)) {
+                keyStore.setEntry(ALIAS_UNICODE_PRIVATE, getPrivateKey(), PARAM_KEY);
+                assertPrivateKey(keyStore.getKey(ALIAS_UNICODE_PRIVATE, PASSWORD_KEY));
+                assertCertificateChain(keyStore.getCertificateChain(ALIAS_UNICODE_PRIVATE));
+            }
+            if (isNullPasswordAllowed(keyStore)) {
+                keyStore.setEntry(ALIAS_UNICODE_NO_PASSWORD_PRIVATE, getPrivateKey(), null);
+                assertPrivateKey(keyStore.getKey(ALIAS_UNICODE_NO_PASSWORD_PRIVATE, null));
+                assertCertificateChain(keyStore
+                        .getCertificateChain(ALIAS_UNICODE_NO_PASSWORD_PRIVATE));
+            }
             if (isSecretKeyEnabled(keyStore)) {
                 assertNull(keyStore.getKey(ALIAS_UNICODE_SECRET, PASSWORD_KEY));
                 keyStore.setEntry(ALIAS_UNICODE_SECRET, new SecretKeyEntry(getSecretKey()), PARAM_KEY);
@@ -1874,11 +2081,21 @@
                 assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
                 assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY));
             } else if (isCaseSensitive(keyStore)) {
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
-                keyStore.setEntry(ALIAS_ALT_CASE_PRIVATE, getPrivateKey2(), PARAM_KEY);
-                assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
-                assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                if (isKeyPasswordSupported(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                    keyStore.setEntry(ALIAS_ALT_CASE_PRIVATE, getPrivateKey2(), PARAM_KEY);
+                    assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY));
+                }
+
+                if (isNullPasswordAllowed(keyStore)) {
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                    keyStore.setEntry(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, getPrivateKey2(), null);
+                    assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null));
+                    assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null));
+                }
 
                 if (isSecretKeyEnabled(keyStore)) {
                     assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY));
@@ -2073,10 +2290,17 @@
             }
 
             // test case sensitive
-            assertTrue(keyStore.entryInstanceOf(ALIAS_PRIVATE, PrivateKeyEntry.class));
+            assertEquals(isKeyPasswordSupported(keyStore),
+                    keyStore.entryInstanceOf(ALIAS_PRIVATE, PrivateKeyEntry.class));
             assertFalse(keyStore.entryInstanceOf(ALIAS_PRIVATE, SecretKeyEntry.class));
             assertFalse(keyStore.entryInstanceOf(ALIAS_PRIVATE, TrustedCertificateEntry.class));
 
+            assertEquals(isNullPasswordAllowed(keyStore),
+                    keyStore.entryInstanceOf(ALIAS_NO_PASSWORD_PRIVATE, PrivateKeyEntry.class));
+            assertFalse(keyStore.entryInstanceOf(ALIAS_NO_PASSWORD_PRIVATE, SecretKeyEntry.class));
+            assertFalse(keyStore.entryInstanceOf(ALIAS_NO_PASSWORD_PRIVATE,
+                    TrustedCertificateEntry.class));
+
             assertEquals(isSecretKeyEnabled(keyStore),
                          keyStore.entryInstanceOf(ALIAS_SECRET, SecretKeyEntry.class));
             assertFalse(keyStore.entryInstanceOf(ALIAS_SECRET, PrivateKeyEntry.class));
@@ -2173,7 +2397,7 @@
             OutputStream os = null;
             try {
                 os = new FileOutputStream(file);
-                if (isReadOnly(keyStore)) {
+                if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) {
                     try {
                         keyStore.store(os, PASSWORD_STORE);
                         fail(keyStore.getType());
@@ -2204,6 +2428,9 @@
         }
 
         for (KeyStore keyStore : keyStores()) {
+            if (isLoadStoreUnsupported(keyStore)) {
+                continue;
+            }
             Builder builder = Builder.newInstance(keyStore.getType(),
                                                   keyStore.getProvider(),
                                                   PARAM_STORE);
diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java
index 92b5513..63daa88 100644
--- a/luni/src/test/java/libcore/java/security/SignatureTest.java
+++ b/luni/src/test/java/libcore/java/security/SignatureTest.java
@@ -1253,4 +1253,23 @@
         sig.update(Vector2Data);
         assertTrue("Signature must verify correctly", sig.verify(SHA1withDSA_Vector2Signature));
     }
+
+    // NetscapeCertRequest looks up Signature algorithms by OID from
+    // BC but BC version 1.47 had registration bugs and MD5withRSA was
+    // overlooked.  http://b/7453821
+    public void testGetInstanceFromOID() throws Exception {
+        assertBouncyCastleSignatureFromOID("1.2.840.113549.1.1.4");  // MD5withRSA
+        assertBouncyCastleSignatureFromOID("1.2.840.113549.1.1.5");  // SHA1withRSA
+        assertBouncyCastleSignatureFromOID("1.3.14.3.2.29");         // SHA1withRSA
+        assertBouncyCastleSignatureFromOID("1.2.840.113549.1.1.11"); // SHA256withRSA
+        assertBouncyCastleSignatureFromOID("1.2.840.113549.1.1.12"); // SHA384withRSA
+        assertBouncyCastleSignatureFromOID("1.2.840.113549.1.1.13"); // SHA512withRSA
+        assertBouncyCastleSignatureFromOID("1.2.840.10040.4.3");     // SHA1withDSA
+    }
+
+    private void assertBouncyCastleSignatureFromOID(String oid) throws Exception {
+        Signature signature = Signature.getInstance(oid, "BC");
+        assertNotNull(oid, signature);
+        assertEquals(oid, signature.getAlgorithm());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java
index 541dd66..b146b1a 100644
--- a/luni/src/test/java/libcore/java/util/LocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/LocaleTest.java
@@ -46,6 +46,16 @@
         assertEquals("Deutsch", Locale.GERMAN.getDisplayLanguage(Locale.GERMAN));
     }
 
+    // http://b/7291355; Locale.getDisplayLanguage fails for tl in tl in ICU 4.9.
+    public void test_tl() throws Exception {
+        Locale tl = new Locale("tl");
+        Locale tl_PH = new Locale("tl", "PH");
+        assertEquals("Filipino", tl.getDisplayLanguage(Locale.ENGLISH));
+        assertEquals("Filipino", tl_PH.getDisplayLanguage(Locale.ENGLISH));
+        assertEquals("Filipino", tl.getDisplayLanguage(tl));
+        assertEquals("Filipino", tl_PH.getDisplayLanguage(tl_PH));
+    }
+
     // http://b/3452611; Locale.getDisplayLanguage fails for the obsolete language codes.
     public void test_getDisplayName_obsolete() throws Exception {
         // he (new) -> iw (obsolete)
diff --git a/luni/src/test/java/libcore/java/util/prefs/OldPreferencesTest.java b/luni/src/test/java/libcore/java/util/prefs/OldPreferencesTest.java
index aa5f088..f8a8154 100644
--- a/luni/src/test/java/libcore/java/util/prefs/OldPreferencesTest.java
+++ b/luni/src/test/java/libcore/java/util/prefs/OldPreferencesTest.java
@@ -29,8 +29,10 @@
 
 public final class OldPreferencesTest extends TestCase {
 
-    final static String longKey;
-    final static String longValue;
+    private static final boolean ENCOURAGE_RACES = false;
+
+    private static final String longKey;
+    private static final String longValue;
 
     static {
         StringBuilder key = new StringBuilder(Preferences.MAX_KEY_LENGTH);
@@ -316,8 +318,7 @@
         String longName = name.toString();
 
         Preferences root = Preferences.userRoot();
-        Preferences parent = Preferences
-                .userNodeForPackage(Preferences.class);
+        Preferences parent = Preferences.userNodeForPackage(Preferences.class);
         Preferences pref = parent.node("mock");
 
         try {
@@ -352,8 +353,7 @@
 
     public void testNodeExists() throws BackingStoreException {
 
-        Preferences parent = Preferences
-                .userNodeForPackage(Preferences.class);
+        Preferences parent = Preferences.userNodeForPackage(Preferences.class);
         Preferences pref = parent.node("mock");
 
         try {
@@ -393,16 +393,14 @@
     }
 
     public void testParent() {
-        Preferences parent = Preferences
-                .userNodeForPackage(Preferences.class);
+        Preferences parent = Preferences.userNodeForPackage(Preferences.class);
         Preferences pref = parent.node("mock");
 
         assertSame(parent, pref.parent());
     }
 
     public void testPut() throws BackingStoreException {
-        Preferences pref = Preferences
-        .userNodeForPackage(Preferences.class);
+        Preferences pref = Preferences.userNodeForPackage(Preferences.class);
         pref.put("", "emptyvalue");
         assertEquals("emptyvalue", pref.get("", null));
         pref.put("testPutkey", "value1");
@@ -468,8 +466,7 @@
     }
 
     public void testPutDouble() {
-        Preferences pref = Preferences
-        .userNodeForPackage(Preferences.class);
+        Preferences pref = Preferences.userNodeForPackage(Preferences.class);
         try {
             pref.putDouble(null, 3);
             fail();
@@ -580,8 +577,7 @@
     }
 
     public void testRemove() throws BackingStoreException {
-        Preferences pref = Preferences
-        .userNodeForPackage(Preferences.class);
+        Preferences pref = Preferences.userNodeForPackage(Preferences.class);
         pref.remove("key");
 
         pref.put("key", "value");
@@ -606,8 +602,7 @@
     }
 
     public void testRemoveNode() throws BackingStoreException {
-        Preferences pref = Preferences
-        .userNodeForPackage(Preferences.class);
+        Preferences pref = Preferences.userNodeForPackage(Preferences.class);
         Preferences child = pref.node("child");
         Preferences child1 = pref.node("child1");
         Preferences grandchild = child.node("grandchild");
@@ -636,11 +631,12 @@
             nl = new MockNodeChangeListener();
             pref.addNodeChangeListener(nl);
             child1 = pref.node("mock1");
-            nl.waitForEvent();
+            nl.waitForEvent(1);
             assertEquals(1, nl.getAdded());
             nl.reset();
             pref.node("mock1");
-            nl.waitForEvent();
+            // There shouldn't be an event, but wait in case one arrives...
+            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
             assertEquals(0, nl.getAdded());
             nl.reset();
         } finally {
@@ -653,7 +649,7 @@
             pref.addNodeChangeListener(nl);
             pref.addNodeChangeListener(nl);
             child1 = pref.node("mock2");
-            nl.waitForEvent();
+            nl.waitForEvent(2);
             assertEquals(2, nl.getAdded());
             nl.reset();
         } finally {
@@ -667,7 +663,7 @@
             pref.addNodeChangeListener(nl);
             child1 = pref.node("mock3");
             child1.removeNode();
-            nl.waitForEvent();
+            nl.waitForEvent(2);
             assertEquals(1, nl.getRemoved());
             nl.reset();
         } finally {
@@ -680,7 +676,7 @@
             pref.addNodeChangeListener(nl);
             child1 = pref.node("mock6");
             child1.removeNode();
-            nl.waitForEvent();
+            nl.waitForEvent(4);
             assertEquals(2, nl.getRemoved());
             nl.reset();
         } finally {
@@ -695,27 +691,29 @@
             child1 = pref.node("mock4");
             child1.addNodeChangeListener(nl);
             pref.node("mock4/mock5");
-            nl.waitForEvent();
+            nl.waitForEvent(1);
             assertEquals(1, nl.getAdded());
             nl.reset();
             child3 = pref.node("mock4/mock5/mock6");
-            nl.waitForEvent();
+            // There shouldn't be an event, but wait in case one arrives...
+            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
             assertEquals(0, nl.getAdded());
             nl.reset();
 
             child3.removeNode();
-            nl.waitForEvent();
+            // There shouldn't be an event, but wait in case one arrives...
+            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
             assertEquals(0, nl.getRemoved());
             nl.reset();
 
             pref.node("mock4/mock7");
-            nl.waitForEvent();
+            nl.waitForEvent(1);
             assertEquals(1, nl.getAdded());
             nl.reset();
 
             child1.removeNode();
-            nl.waitForEvent();
-            assertEquals(2, nl.getRemoved()); // fail 1
+            nl.waitForEvent(2);
+            assertEquals(2, nl.getRemoved());
             nl.reset();
         } finally {
             try {
@@ -749,7 +747,7 @@
             try {
                 pref.clear();
                 pl.waitForEvent(2);
-                assertEquals(2, pl.getChanged()); // fail 1
+                assertEquals(2, pl.getChanged());
             } catch(BackingStoreException bse) {
                 pl.reset();
                 fail("BackingStoreException is thrown");
@@ -784,7 +782,7 @@
             try {
                 pref.clear();
                 pl.waitForEvent(3);
-                assertEquals(3, pl.getChanged()); // fails
+                assertEquals(3, pl.getChanged());
             } catch(BackingStoreException bse) {
                 fail("BackingStoreException is thrown");
             }
@@ -868,25 +866,38 @@
     static class MockNodeChangeListener implements NodeChangeListener {
         private int added = 0;
         private int removed = 0;
+        private int eventCount = 0;
 
-        public synchronized void waitForEvent() {
-            try {
-                wait(500);
-            } catch (InterruptedException ignored) {
+        public void waitForEvent(int count) {
+            synchronized (this) {
+                while (eventCount < count) {
+                    try {
+                        wait();
+                    } catch (InterruptedException ignored) {
+                    }
+                }
             }
         }
 
-        public synchronized void childAdded(NodeChangeEvent e) {
-            ++added;
-            notifyAll();
+        public void childAdded(NodeChangeEvent e) {
+            if (ENCOURAGE_RACES) try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
+            synchronized (this) {
+                ++eventCount;
+                ++added;
+                notifyAll();
+            }
         }
 
-        public synchronized void childRemoved(NodeChangeEvent e) {
-            removed++;
-            notifyAll();
+        public void childRemoved(NodeChangeEvent e) {
+            if (ENCOURAGE_RACES) try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
+            synchronized (this) {
+                ++eventCount;
+                ++removed;
+                notifyAll();
+            }
         }
 
-        public synchronized  int getAdded() {
+        public synchronized int getAdded() {
             return added;
         }
 
@@ -894,7 +905,8 @@
             return removed;
         }
 
-        public void reset() {
+        public synchronized void reset() {
+            eventCount = 0;
             added = 0;
             removed = 0;
         }
@@ -902,47 +914,35 @@
 
     private static class MockPreferenceChangeListener implements PreferenceChangeListener {
         private int changed = 0;
-        private boolean addDispatched = false;
-        private boolean result = false;
+        private int eventCount = 0;
 
         public void waitForEvent(int count) {
-            for (int i = 0; i < count; i++) {
-                try {
-                    synchronized (this) {
-                        wait(500);
+            synchronized (this) {
+                while (eventCount < count) {
+                    try {
+                        wait();
+                    } catch (InterruptedException ignored) {
                     }
-                } catch (InterruptedException ignored) {
                 }
             }
         }
 
-        public synchronized void preferenceChange(PreferenceChangeEvent pce) {
-            changed++;
-            addDispatched = true;
-            notifyAll();
+        public void preferenceChange(PreferenceChangeEvent pce) {
+            if (ENCOURAGE_RACES) try { Thread.sleep(1000); } catch (InterruptedException ignored) { }
+            synchronized (this) {
+                ++eventCount;
+                ++changed;
+                notifyAll();
+            }
         }
 
-        public boolean getResult() throws InterruptedException {
-            if (!addDispatched) {
-                // TODO: don't know why must add limitation
-                wait(100);
-            }
-            addDispatched = false;
-            return result;
-        }
-
-        public synchronized int getChanged() throws InterruptedException {
-            if (!addDispatched) {
-                // TODO: don't know why must add limitation
-                wait(1000);
-            }
-            addDispatched = false;
+        public synchronized int getChanged() {
             return changed;
         }
 
-        public void reset() {
+        public synchronized void reset() {
+            eventCount = 0;
             changed = 0;
-            result = false;
         }
     }
 
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index d31825a..68a46f3 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -17,37 +17,87 @@
 package libcore.javax.crypto;
 
 import com.android.org.bouncycastle.asn1.x509.KeyUsage;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
 import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
-import java.security.InvalidParameterException;
 import java.security.Key;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
-import java.security.Provider.Service;
 import java.security.Provider;
 import java.security.PublicKey;
+import java.security.SecureRandom;
 import java.security.Security;
 import java.security.cert.Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
 import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
 import libcore.java.security.TestKeyStore;
 
 public final class CipherTest extends TestCase {
 
+    private static final String[] RSA_PROVIDERS = ((StandardNames.IS_RI)
+                                                   ? new String[] { "SunJCE" }
+                                                   : new String[] { "BC" , "AndroidOpenSSL" });
+
+    private static final String[] AES_PROVIDERS = ((StandardNames.IS_RI)
+                                                   ? new String[] { "SunJCE" }
+                                                   : new String[] { "BC" }); // TOOD: , "AndroidOpenSSL"
+
+    private static final boolean IS_UNLIMITED;
+    static {
+        boolean is_unlimited;
+        if (StandardNames.IS_RI) {
+            try {
+                String algorithm = "PBEWITHMD5ANDTRIPLEDES";
+                Cipher.getInstance(algorithm).init(getEncryptMode(algorithm),
+                                                   getEncryptKey(algorithm),
+                                                   getAlgorithmParameterSpec(algorithm));
+                is_unlimited = true;
+            } catch (Exception e) {
+                is_unlimited = false;
+                System.out.println("WARNING: Some tests disabled due to lack of "
+                                   + "'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
+            }
+        } else {
+            is_unlimited = true;
+        }
+        IS_UNLIMITED = is_unlimited;
+    }
+
     private static boolean isUnsupported(String algorithm) {
+        if (algorithm.equals("RC2")) {
+            return true;
+        }
         if (algorithm.equals("PBEWITHMD5ANDRC2")) {
             return true;
         }
-        if (algorithm.equals("PBEWITHSHA1ANDRC2")) {
+        if (algorithm.startsWith("PBEWITHSHA1ANDRC2")) {
             return true;
         }
         if (algorithm.equals("PBEWITHSHAAND40BITRC2-CBC")) {
@@ -59,6 +109,11 @@
         if (algorithm.equals("PBEWITHSHAANDTWOFISH-CBC")) {
             return true;
         }
+        if (!IS_UNLIMITED) {
+            if (algorithm.equals("PBEWITHMD5ANDTRIPLEDES")) {
+                return true;
+            }
+        }
         return false;
     }
 
@@ -76,10 +131,13 @@
         return Cipher.DECRYPT_MODE;
     }
 
-    private static String getBaseAlgoritm(String algorithm) {
+    private static String getBaseAlgorithm(String algorithm) {
         if (algorithm.equals("AESWRAP")) {
             return "AES";
         }
+        if (algorithm.startsWith("AES/")) {
+            return "AES";
+        }
         if (algorithm.equals("PBEWITHMD5AND128BITAES-CBC-OPENSSL")) {
             return "AES";
         }
@@ -114,18 +172,24 @@
             return "DES";
         }
         if (algorithm.equals("DESEDEWRAP")) {
-            return "DESede";
+            return "DESEDE";
         }
         if (algorithm.equals("PBEWITHSHAAND2-KEYTRIPLEDES-CBC")) {
-            return "DESede";
+            return "DESEDE";
         }
         if (algorithm.equals("PBEWITHSHAAND3-KEYTRIPLEDES-CBC")) {
-            return "DESede";
+            return "DESEDE";
         }
-        if (algorithm.equals("RSA/ECB/NoPadding")) {
+        if (algorithm.equals("PBEWITHMD5ANDTRIPLEDES")) {
+            return "DESEDE";
+        }
+        if (algorithm.equals("PBEWITHSHA1ANDDESEDE")) {
+            return "DESEDE";
+        }
+        if (algorithm.equals("RSA/ECB/NOPADDING")) {
             return "RSA";
         }
-        if (algorithm.equals("RSA/ECB/PKCS1Padding")) {
+        if (algorithm.equals("RSA/ECB/PKCS1PADDING")) {
             return "RSA";
         }
         if (algorithm.equals("PBEWITHSHAAND40BITRC4")) {
@@ -138,27 +202,36 @@
     }
 
     private static boolean isAsymmetric(String algorithm) {
-        return getBaseAlgoritm(algorithm).equals("RSA");
+        return getBaseAlgorithm(algorithm).equals("RSA");
     }
 
     private static boolean isWrap(String algorithm) {
         return algorithm.endsWith("WRAP");
     }
 
+    private static boolean isPBE(String algorithm) {
+        return algorithm.startsWith("PBE");
+    }
+
     private static Map<String, Key> ENCRYPT_KEYS = new HashMap<String, Key>();
     private synchronized static Key getEncryptKey(String algorithm) throws Exception {
         Key key = ENCRYPT_KEYS.get(algorithm);
         if (key != null) {
             return key;
         }
-        algorithm = getBaseAlgoritm(algorithm);
-        if (algorithm.equals("RSA")) {
+        if (algorithm.startsWith("RSA")) {
             KeyFactory kf = KeyFactory.getInstance("RSA");
             RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                                                               RSA_2048_privateExponent);
             key = kf.generatePrivate(keySpec);
+        } else if (isPBE(algorithm)) {
+            SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
+            key = skf.generateSecret(new PBEKeySpec("secret".toCharArray()));
         } else {
-            KeyGenerator kg = KeyGenerator.getInstance(algorithm);
+            KeyGenerator kg = KeyGenerator.getInstance(getBaseAlgorithm(algorithm));
+            if (StandardNames.IS_RI && algorithm.equals("AES")) {
+                kg.init(128);
+            }
             key = kg.generateKey();
         }
         ENCRYPT_KEYS.put(algorithm, key);
@@ -171,8 +244,7 @@
         if (key != null) {
             return key;
         }
-        algorithm = getBaseAlgoritm(algorithm);
-        if (algorithm.equals("RSA")) {
+        if (algorithm.startsWith("RSA")) {
             KeyFactory kf = KeyFactory.getInstance("RSA");
             RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus,
                                                             RSA_2048_publicExponent);
@@ -187,84 +259,213 @@
 
     private static Map<String, Integer> EXPECTED_BLOCK_SIZE = new HashMap<String, Integer>();
     static {
-        EXPECTED_BLOCK_SIZE.put("AES", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHA256AND128BITAES-CBC-BC", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHA256AND192BITAES-CBC-BC", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHA256AND256BITAES-CBC-BC", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND128BITAES-CBC-BC", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND192BITAES-CBC-BC", 16);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND256BITAES-CBC-BC", 16);
+        setExpectedBlockSize("AES", 16);
+        setExpectedBlockSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
+        setExpectedBlockSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
+        setExpectedBlockSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
+        setExpectedBlockSize("PBEWITHSHA256AND128BITAES-CBC-BC", 16);
+        setExpectedBlockSize("PBEWITHSHA256AND192BITAES-CBC-BC", 16);
+        setExpectedBlockSize("PBEWITHSHA256AND256BITAES-CBC-BC", 16);
+        setExpectedBlockSize("PBEWITHSHAAND128BITAES-CBC-BC", 16);
+        setExpectedBlockSize("PBEWITHSHAAND192BITAES-CBC-BC", 16);
+        setExpectedBlockSize("PBEWITHSHAAND256BITAES-CBC-BC", 16);
 
-        EXPECTED_BLOCK_SIZE.put("AESWRAP", 0);
+        if (StandardNames.IS_RI) {
+            setExpectedBlockSize("AESWRAP", 16);
+        } else {
+            setExpectedBlockSize("AESWRAP", 0);
+        }
 
-        EXPECTED_BLOCK_SIZE.put("ARC4", 0);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND40BITRC4", 0);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND128BITRC4", 0);
+        setExpectedBlockSize("ARC4", 0);
+        setExpectedBlockSize("ARCFOUR", 0);
+        setExpectedBlockSize("PBEWITHSHAAND40BITRC4", 0);
+        setExpectedBlockSize("PBEWITHSHAAND128BITRC4", 0);
 
-        EXPECTED_BLOCK_SIZE.put("BLOWFISH", 8);
+        setExpectedBlockSize("BLOWFISH", 8);
 
-        EXPECTED_BLOCK_SIZE.put("DES", 8);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHMD5ANDDES", 8);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHA1ANDDES", 8);
+        setExpectedBlockSize("DES", 8);
+        setExpectedBlockSize("PBEWITHMD5ANDDES", 8);
+        setExpectedBlockSize("PBEWITHSHA1ANDDES", 8);
 
-        EXPECTED_BLOCK_SIZE.put("DESEDE", 8);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8);
-        EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8);
+        setExpectedBlockSize("DESEDE", 8);
+        setExpectedBlockSize("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8);
+        setExpectedBlockSize("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8);
+        setExpectedBlockSize("PBEWITHMD5ANDTRIPLEDES", 8);
+        setExpectedBlockSize("PBEWITHSHA1ANDDESEDE", 8);
 
-        EXPECTED_BLOCK_SIZE.put("DESEDEWRAP", 0);
 
-        EXPECTED_BLOCK_SIZE.put("RSA", 255);
-        EXPECTED_BLOCK_SIZE.put("RSA/ECB/NoPadding", 0);
-        EXPECTED_BLOCK_SIZE.put("RSA/ECB/PKCS1Padding", 0);
+        if (StandardNames.IS_RI) {
+            setExpectedBlockSize("DESEDEWRAP", 8);
+        } else {
+            setExpectedBlockSize("DESEDEWRAP", 0);
+        }
+
+        if (StandardNames.IS_RI) {
+            setExpectedBlockSize("RSA", 0);
+            setExpectedBlockSize("RSA/ECB/NoPadding", 0);
+            setExpectedBlockSize("RSA/ECB/PKCS1Padding", 0);
+        } else {
+            setExpectedBlockSize("RSA", Cipher.ENCRYPT_MODE, 256);
+            setExpectedBlockSize("RSA/ECB/NoPadding", Cipher.ENCRYPT_MODE, 256);
+            setExpectedBlockSize("RSA/ECB/PKCS1Padding", Cipher.ENCRYPT_MODE, 245);
+
+            // BC strips the leading 0 for us even when NoPadding is specified
+            setExpectedBlockSize("RSA", Cipher.ENCRYPT_MODE, "BC", 255);
+            setExpectedBlockSize("RSA/ECB/NoPadding", Cipher.ENCRYPT_MODE, "BC", 255);
+
+            setExpectedBlockSize("RSA", Cipher.DECRYPT_MODE, 256);
+            setExpectedBlockSize("RSA/ECB/NoPadding", Cipher.DECRYPT_MODE, 256);
+            setExpectedBlockSize("RSA/ECB/PKCS1Padding", Cipher.DECRYPT_MODE, 256);
+        }
     }
-    private static int getExpectedBlockSize(String algorithm) {
-        Integer expected = EXPECTED_BLOCK_SIZE.get(algorithm);
-        assertNotNull(algorithm, expected);
+
+    private static String modeKey(String algorithm, int mode) {
+        return algorithm + ":" + mode;
+    }
+
+    private static String modeProviderKey(String algorithm, int mode, String provider) {
+        return algorithm + ":" + mode + ":" + provider;
+    }
+
+    private static void setExpectedSize(Map<String, Integer> map,
+                                        String algorithm, int value) {
+        algorithm = algorithm.toUpperCase(Locale.US);
+        map.put(algorithm, value);
+    }
+
+    private static void setExpectedSize(Map<String, Integer> map,
+                                        String algorithm, int mode, int value) {
+        setExpectedSize(map, modeKey(algorithm, mode), value);
+    }
+
+    private static void setExpectedSize(Map<String, Integer> map,
+                                        String algorithm, int mode, String provider, int value) {
+        setExpectedSize(map, modeProviderKey(algorithm, mode, provider), value);
+    }
+
+    private static int getExpectedSize(Map<String, Integer> map, String algorithm, int mode, String provider) {
+        Integer expected = map.get(modeProviderKey(algorithm, mode, provider));
+        if (expected != null) {
+            return expected;
+        }
+        expected = map.get(modeKey(algorithm, mode));
+        if (expected != null) {
+            return expected;
+        }
+        expected = map.get(algorithm);
+        assertNotNull("Algorithm " + algorithm + " not found in " + map, expected);
         return expected;
     }
 
+    private static void setExpectedBlockSize(String algorithm, int value) {
+        setExpectedSize(EXPECTED_BLOCK_SIZE, algorithm, value);
+    }
+
+    private static void setExpectedBlockSize(String algorithm, int mode, int value) {
+        setExpectedSize(EXPECTED_BLOCK_SIZE, algorithm, mode, value);
+    }
+
+    private static void setExpectedBlockSize(String algorithm, int mode, String provider, int value) {
+        setExpectedSize(EXPECTED_BLOCK_SIZE, algorithm, mode, provider, value);
+    }
+
+    private static int getExpectedBlockSize(String algorithm, int mode, String provider) {
+        return getExpectedSize(EXPECTED_BLOCK_SIZE, algorithm, mode, provider);
+    }
+
     private static Map<String, Integer> EXPECTED_OUTPUT_SIZE = new HashMap<String, Integer>();
     static {
-        EXPECTED_OUTPUT_SIZE.put("AES", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHA256AND128BITAES-CBC-BC", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHA256AND192BITAES-CBC-BC", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHA256AND256BITAES-CBC-BC", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND128BITAES-CBC-BC", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND192BITAES-CBC-BC", 16);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND256BITAES-CBC-BC", 16);
+        setExpectedOutputSize("AES", Cipher.ENCRYPT_MODE, 16);
+        setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
+        setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
+        setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
+        setExpectedOutputSize("PBEWITHSHA256AND128BITAES-CBC-BC", 16);
+        setExpectedOutputSize("PBEWITHSHA256AND192BITAES-CBC-BC", 16);
+        setExpectedOutputSize("PBEWITHSHA256AND256BITAES-CBC-BC", 16);
+        setExpectedOutputSize("PBEWITHSHAAND128BITAES-CBC-BC", 16);
+        setExpectedOutputSize("PBEWITHSHAAND192BITAES-CBC-BC", 16);
+        setExpectedOutputSize("PBEWITHSHAAND256BITAES-CBC-BC", 16);
 
-        EXPECTED_OUTPUT_SIZE.put("AESWRAP", -1);
+        setExpectedOutputSize("AES", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHA256AND128BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHA256AND192BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHA256AND256BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHAAND128BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHAAND192BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHAAND256BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0);
 
-        EXPECTED_OUTPUT_SIZE.put("ARC4", 0);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND40BITRC4", 0);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND128BITRC4", 0);
+        if (StandardNames.IS_RI) {
+            setExpectedOutputSize("AESWRAP", Cipher.WRAP_MODE, 8);
+            setExpectedOutputSize("AESWRAP", Cipher.UNWRAP_MODE, 0);
+        } else {
+            setExpectedOutputSize("AESWRAP", -1);
+        }
 
-        EXPECTED_OUTPUT_SIZE.put("BLOWFISH", 8);
+        setExpectedOutputSize("ARC4", 0);
+        setExpectedOutputSize("ARCFOUR", 0);
+        setExpectedOutputSize("PBEWITHSHAAND40BITRC4", 0);
+        setExpectedOutputSize("PBEWITHSHAAND128BITRC4", 0);
 
-        EXPECTED_OUTPUT_SIZE.put("DES", 8);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHMD5ANDDES", 8);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHA1ANDDES", 8);
+        setExpectedOutputSize("BLOWFISH", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("BLOWFISH", Cipher.DECRYPT_MODE, 0);
 
-        EXPECTED_OUTPUT_SIZE.put("DESEDE", 8);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8);
-        EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8);
+        setExpectedOutputSize("DES", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("PBEWITHMD5ANDDES", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("PBEWITHSHA1ANDDES", Cipher.ENCRYPT_MODE, 8);
 
-        EXPECTED_OUTPUT_SIZE.put("DESEDEWRAP", -1);
+        setExpectedOutputSize("DES", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHMD5ANDDES", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHA1ANDDES", Cipher.DECRYPT_MODE, 0);
 
-        EXPECTED_OUTPUT_SIZE.put("RSA", 256);
-        EXPECTED_OUTPUT_SIZE.put("RSA/ECB/NoPadding", 256);
-        EXPECTED_OUTPUT_SIZE.put("RSA/ECB/PKCS1Padding", 256);
+        setExpectedOutputSize("DESEDE", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("PBEWITHMD5ANDTRIPLEDES", Cipher.ENCRYPT_MODE, 8);
+        setExpectedOutputSize("PBEWITHSHA1ANDDESEDE", Cipher.ENCRYPT_MODE, 8);
+
+        setExpectedOutputSize("DESEDE", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHMD5ANDTRIPLEDES", Cipher.DECRYPT_MODE, 0);
+        setExpectedOutputSize("PBEWITHSHA1ANDDESEDE", Cipher.DECRYPT_MODE, 0);
+
+        if (StandardNames.IS_RI) {
+            setExpectedOutputSize("DESEDEWRAP", Cipher.WRAP_MODE, 16);
+            setExpectedOutputSize("DESEDEWRAP", Cipher.UNWRAP_MODE, 0);
+        } else {
+            setExpectedOutputSize("DESEDEWRAP", -1);
+        }
+
+        setExpectedOutputSize("RSA", Cipher.ENCRYPT_MODE, 256);
+        setExpectedOutputSize("RSA/ECB/NoPadding", Cipher.ENCRYPT_MODE, 256);
+        setExpectedOutputSize("RSA/ECB/PKCS1Padding", Cipher.ENCRYPT_MODE, 256);
+
+        setExpectedOutputSize("RSA", Cipher.DECRYPT_MODE, 256);
+        setExpectedOutputSize("RSA/ECB/NoPadding", Cipher.DECRYPT_MODE, 256);
+        setExpectedOutputSize("RSA/ECB/PKCS1Padding", Cipher.DECRYPT_MODE, 245);
+
+        // BC strips the leading 0 for us even when NoPadding is specified
+        setExpectedOutputSize("RSA", Cipher.DECRYPT_MODE, "BC", 255);
+        setExpectedOutputSize("RSA/ECB/NoPadding", Cipher.DECRYPT_MODE, "BC", 255);
     }
-    private static int getExpectedOutputSize(String algorithm) {
-        Integer expected = EXPECTED_OUTPUT_SIZE.get(algorithm);
-        assertNotNull(algorithm, expected);
-        return expected;
+
+    private static void setExpectedOutputSize(String algorithm, int value) {
+        setExpectedSize(EXPECTED_OUTPUT_SIZE, algorithm, value);
+    }
+
+    private static void setExpectedOutputSize(String algorithm, int mode, int value) {
+        setExpectedSize(EXPECTED_OUTPUT_SIZE, algorithm, mode, value);
+    }
+
+    private static void setExpectedOutputSize(String algorithm, int mode, String provider, int value) {
+        setExpectedSize(EXPECTED_OUTPUT_SIZE, algorithm, mode, provider, value);
+    }
+
+    private static int getExpectedOutputSize(String algorithm, int mode, String provider) {
+        return getExpectedSize(EXPECTED_OUTPUT_SIZE, algorithm, mode, provider);
     }
 
     private static byte[] ORIGINAL_PLAIN_TEXT = new byte[] { 0x0a, 0x0b, 0x0c };
@@ -356,13 +557,28 @@
     };
 
     private static byte[] getExpectedPlainText(String algorithm) {
-        if (algorithm.equals("RSA/ECB/NoPadding")) {
+        if (algorithm.equals("RSA/ECB/NOPADDING")) {
             return PKCS1_BLOCK_TYPE_00_PADDED_PLAIN_TEXT;
         }
         return ORIGINAL_PLAIN_TEXT;
     }
 
+    private static AlgorithmParameterSpec getAlgorithmParameterSpec(String algorithm) {
+        if (!isPBE(algorithm)) {
+            return null;
+        }
+        final byte[] salt = new byte[8];
+        new SecureRandom().nextBytes(salt);
+        return new PBEParameterSpec(salt, 1024);
+    }
+
     public void test_getInstance() throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+
+        Set<String> seenBaseCipherNames = new HashSet<String>();
+        Set<String> seenCiphersWithModeAndPadding = new HashSet<String>();
+
         Provider[] providers = Security.getProviders();
         for (Provider provider : providers) {
             Set<Provider.Service> services = provider.getServices();
@@ -371,37 +587,91 @@
                 if (!type.equals("Cipher")) {
                     continue;
                 }
+
                 String algorithm = service.getAlgorithm();
+
+                /*
+                 * Any specific modes and paddings aren't tested directly here,
+                 * but we need to make sure we see the bare algorithm from some
+                 * provider. We will test each mode specifically when we get the
+                 * base cipher.
+                 */
+                final int firstSlash = algorithm.indexOf('/');
+                if (firstSlash == -1) {
+                    seenBaseCipherNames.add(algorithm);
+                } else {
+                    final String baseCipherName = algorithm.substring(0, firstSlash);
+                    if (!seenBaseCipherNames.contains(baseCipherName)) {
+                        seenCiphersWithModeAndPadding.add(baseCipherName);
+                    }
+                    continue;
+                }
+
                 try {
-                    // Cipher.getInstance(String)
-                    Cipher c1 = Cipher.getInstance(algorithm);
-                    assertEquals(algorithm, c1.getAlgorithm());
-                    test_Cipher(c1);
-
-                    // Cipher.getInstance(String, Provider)
-                    Cipher c2 = Cipher.getInstance(algorithm, provider);
-                    assertEquals(algorithm, c2.getAlgorithm());
-                    assertEquals(provider, c2.getProvider());
-                    test_Cipher(c2);
-
-                    // KeyGenerator.getInstance(String, String)
-                    Cipher c3 = Cipher.getInstance(algorithm, provider.getName());
-                    assertEquals(algorithm, c3.getAlgorithm());
-                    assertEquals(provider, c3.getProvider());
-                    test_Cipher(c3);
+                    test_Cipher_Algorithm(provider, algorithm);
                 } catch (Throwable e) {
-                    throw new Exception("Problem testing Cipher." + algorithm, e);
+                    out.append("Error encountered checking " + algorithm
+                               + " with provider " + provider.getName() + "\n");
+                    e.printStackTrace(out);
+                }
+
+                Set<String> modes = StandardNames.getModesForCipher(algorithm);
+                if (modes != null) {
+                    for (String mode : modes) {
+                        Set<String> paddings = StandardNames.getPaddingsForCipher(algorithm);
+                        if (paddings != null) {
+                            for (String padding : paddings) {
+                                final String algorithmName = algorithm + "/" + mode + "/" + padding;
+                                try {
+                                    test_Cipher_Algorithm(provider, algorithmName);
+                                } catch (Throwable e) {
+                                    out.append("Error encountered checking " + algorithmName
+                                               + " with provider " + provider.getName() + "\n");
+                                    e.printStackTrace(out);
+                                }
+                            }
+                        }
+                    }
                 }
             }
         }
+
+        seenCiphersWithModeAndPadding.removeAll(seenBaseCipherNames);
+        assertEquals("Ciphers seen with mode and padding but not base cipher",
+                Collections.EMPTY_SET, seenCiphersWithModeAndPadding);
+
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+
+    private void test_Cipher_Algorithm(Provider provider, String algorithm) throws Exception {
+        // Cipher.getInstance(String)
+        Cipher c1 = Cipher.getInstance(algorithm);
+        assertEquals(algorithm, c1.getAlgorithm());
+        test_Cipher(c1);
+
+        // Cipher.getInstance(String, Provider)
+        Cipher c2 = Cipher.getInstance(algorithm, provider);
+        assertEquals(algorithm, c2.getAlgorithm());
+        assertEquals(provider, c2.getProvider());
+        test_Cipher(c2);
+
+        // KeyGenerator.getInstance(String, String)
+        Cipher c3 = Cipher.getInstance(algorithm, provider.getName());
+        assertEquals(algorithm, c3.getAlgorithm());
+        assertEquals(provider, c3.getProvider());
+        test_Cipher(c3);
     }
 
     private void test_Cipher(Cipher c) throws Exception {
-        // TODO: test all supported modes and padding for a given algorithm
-        String algorithm = c.getAlgorithm();
+        String algorithm = c.getAlgorithm().toUpperCase(Locale.US);
         if (isUnsupported(algorithm)) {
             return;
         }
+        String providerName = c.getProvider().getName();
+        String cipherID = algorithm + ":" + providerName;
 
         try {
             c.getOutputSize(0);
@@ -410,21 +680,28 @@
 
         // TODO: test keys from different factories (e.g. OpenSSLRSAPrivateKey vs JCERSAPrivateKey)
         Key encryptKey = getEncryptKey(algorithm);
-        c.init(getEncryptMode(algorithm), encryptKey);
 
-        assertEquals(getExpectedBlockSize(algorithm), c.getBlockSize());
+        final AlgorithmParameterSpec spec = getAlgorithmParameterSpec(algorithm);
 
-        assertEquals(getExpectedOutputSize(algorithm), c.getOutputSize(0));
+        int encryptMode = getEncryptMode(algorithm);
+        c.init(encryptMode, encryptKey, spec);
+        assertEquals(cipherID, getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize());
+        assertEquals(cipherID, getExpectedOutputSize(algorithm, encryptMode, providerName), c.getOutputSize(0));
+        int decryptMode = getDecryptMode(algorithm);
+        c.init(decryptMode, encryptKey, spec);
+        assertEquals(cipherID, getExpectedBlockSize(algorithm, decryptMode, providerName), c.getBlockSize());
+        assertEquals(cipherID, getExpectedOutputSize(algorithm, decryptMode, providerName), c.getOutputSize(0));
 
         // TODO: test Cipher.getIV()
 
         // TODO: test Cipher.getParameters()
 
-        assertNull(c.getExemptionMechanism());
+        assertNull(cipherID, c.getExemptionMechanism());
 
+        c.init(getEncryptMode(algorithm), encryptKey, spec);
         if (isWrap(algorithm)) {
             byte[] cipherText = c.wrap(encryptKey);
-            c.init(getDecryptMode(algorithm), getDecryptKey(algorithm));
+            c.init(getDecryptMode(algorithm), getDecryptKey(algorithm), spec);
             int keyType = (isAsymmetric(algorithm)) ? Cipher.PRIVATE_KEY : Cipher.SECRET_KEY;
             Key decryptedKey = c.unwrap(cipherText, encryptKey.getAlgorithm(), keyType);
             assertEquals("encryptKey.getAlgorithm()=" + encryptKey.getAlgorithm()
@@ -434,33 +711,40 @@
                          encryptKey, decryptedKey);
         } else {
             byte[] cipherText = c.doFinal(ORIGINAL_PLAIN_TEXT);
-            c.init(getDecryptMode(algorithm), getDecryptKey(algorithm));
+            c.init(getDecryptMode(algorithm), getDecryptKey(algorithm), spec);
             byte[] decryptedPlainText = c.doFinal(cipherText);
-            assertEquals(Arrays.toString(getExpectedPlainText(algorithm)),
+            assertEquals(cipherID,
+                         Arrays.toString(getExpectedPlainText(algorithm)),
                          Arrays.toString(decryptedPlainText));
         }
     }
 
     public void testInputPKCS1Padding() throws Exception {
-        testInputPKCS1Padding(PKCS1_BLOCK_TYPE_01_PADDED_PLAIN_TEXT, getEncryptKey("RSA"), getDecryptKey("RSA"));
-        try {
-            testInputPKCS1Padding(PKCS1_BLOCK_TYPE_02_PADDED_PLAIN_TEXT, getEncryptKey("RSA"), getDecryptKey("RSA"));
-            fail();
-        } catch (BadPaddingException expected) {
+        for (String provider : RSA_PROVIDERS) {
+            testInputPKCS1Padding(provider);
         }
-        try {
-            testInputPKCS1Padding(PKCS1_BLOCK_TYPE_01_PADDED_PLAIN_TEXT, getDecryptKey("RSA"), getEncryptKey("RSA"));
-            fail();
-        } catch (BadPaddingException expected) {
-        }
-        testInputPKCS1Padding(PKCS1_BLOCK_TYPE_02_PADDED_PLAIN_TEXT, getDecryptKey("RSA"), getEncryptKey("RSA"));
     }
 
-    private void testInputPKCS1Padding(byte[] prePaddedPlainText, Key encryptKey, Key decryptKey) throws Exception {
-        Cipher encryptCipher = Cipher.getInstance("RSA/ECB/NoPadding");
+    private void testInputPKCS1Padding(String provider) throws Exception {
+        testInputPKCS1Padding(provider, PKCS1_BLOCK_TYPE_01_PADDED_PLAIN_TEXT, getEncryptKey("RSA"), getDecryptKey("RSA"));
+        try {
+            testInputPKCS1Padding(provider, PKCS1_BLOCK_TYPE_02_PADDED_PLAIN_TEXT, getEncryptKey("RSA"), getDecryptKey("RSA"));
+            fail();
+        } catch (BadPaddingException expected) {
+        }
+        try {
+            testInputPKCS1Padding(provider, PKCS1_BLOCK_TYPE_01_PADDED_PLAIN_TEXT, getDecryptKey("RSA"), getEncryptKey("RSA"));
+            fail();
+        } catch (BadPaddingException expected) {
+        }
+        testInputPKCS1Padding(provider, PKCS1_BLOCK_TYPE_02_PADDED_PLAIN_TEXT, getDecryptKey("RSA"), getEncryptKey("RSA"));
+    }
+
+    private void testInputPKCS1Padding(String provider, byte[] prePaddedPlainText, Key encryptKey, Key decryptKey) throws Exception {
+        Cipher encryptCipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
         encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey);
         byte[] cipherText = encryptCipher.doFinal(prePaddedPlainText);
-        Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+        Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
         decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey);
         byte[] plainText = decryptCipher.doFinal(cipherText);
         assertEquals(Arrays.toString(ORIGINAL_PLAIN_TEXT),
@@ -468,36 +752,51 @@
     }
 
     public void testOutputPKCS1Padding() throws Exception {
-       testOutputPKCS1Padding((byte) 1, getEncryptKey("RSA"), getDecryptKey("RSA"));
-       testOutputPKCS1Padding((byte) 2, getDecryptKey("RSA"), getEncryptKey("RSA"));
+        for (String provider : RSA_PROVIDERS) {
+            testOutputPKCS1Padding(provider);
+        }
     }
 
-    private void testOutputPKCS1Padding(byte expectedBlockType, Key encryptKey, Key decryptKey) throws Exception {
-        Cipher encryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+    private void testOutputPKCS1Padding(String provider) throws Exception {
+       testOutputPKCS1Padding(provider, (byte) 1, getEncryptKey("RSA"), getDecryptKey("RSA"));
+       testOutputPKCS1Padding(provider, (byte) 2, getDecryptKey("RSA"), getEncryptKey("RSA"));
+    }
+
+    private void testOutputPKCS1Padding(String provider, byte expectedBlockType, Key encryptKey, Key decryptKey) throws Exception {
+        Cipher encryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
         encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey);
         byte[] cipherText = encryptCipher.doFinal(ORIGINAL_PLAIN_TEXT);
-        Cipher decryptCipher = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher decryptCipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
         decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey);
         byte[] plainText = decryptCipher.doFinal(cipherText);
-        assertPadding(expectedBlockType, ORIGINAL_PLAIN_TEXT, plainText);
+        assertPadding(provider, expectedBlockType, ORIGINAL_PLAIN_TEXT, plainText);
     }
 
-    private void assertPadding(byte expectedBlockType, byte[] expectedData, byte[] actualDataWithPadding) {
-        assertNotNull(actualDataWithPadding);
-        assertEquals(getExpectedOutputSize("RSA"), actualDataWithPadding.length);
-        assertEquals(0, actualDataWithPadding[0]);
-        byte actualBlockType = actualDataWithPadding[1];
-        assertEquals(expectedBlockType, actualBlockType);
+    private void assertPadding(String provider, byte expectedBlockType, byte[] expectedData, byte[] actualDataWithPadding) {
+        assertNotNull(provider, actualDataWithPadding);
+        int expectedOutputSize = getExpectedOutputSize("RSA", Cipher.DECRYPT_MODE, provider);
+        assertEquals(provider, expectedOutputSize, actualDataWithPadding.length);
+        int expectedBlockTypeOffset;
+        if (provider.equals("BC")) {
+            // BC strips the leading 0 for us on decrypt even when NoPadding is specified...
+            expectedBlockTypeOffset = 0;
+        } else {
+            expectedBlockTypeOffset = 1;
+            assertEquals(provider, 0, actualDataWithPadding[0]);
+        }
+        byte actualBlockType = actualDataWithPadding[expectedBlockTypeOffset];
+        assertEquals(provider, expectedBlockType, actualBlockType);
         int actualDataOffset = actualDataWithPadding.length - expectedData.length;
         if (actualBlockType == 1) {
-            for (int i = 2; i < actualDataOffset - 1; i++) {
-                assertEquals((byte) 0xFF, actualDataWithPadding[i]);
+            int expectedDataOffset = expectedBlockTypeOffset + 1;
+            for (int i = expectedDataOffset; i < actualDataOffset - 1; i++) {
+                assertEquals(provider, (byte) 0xFF, actualDataWithPadding[i]);
             }
         }
-        assertEquals(0x00, actualDataWithPadding[actualDataOffset-1]);
+        assertEquals(provider, 0x00, actualDataWithPadding[actualDataOffset-1]);
         byte[] actualData = new byte[expectedData.length];
         System.arraycopy(actualDataWithPadding, actualDataOffset, actualData, 0, actualData.length);
-        assertEquals(Arrays.toString(expectedData), Arrays.toString(actualData));
+        assertEquals(provider, Arrays.toString(expectedData), Arrays.toString(actualData));
     }
 
     public void testCipherInitWithCertificate () throws Exception {
@@ -935,13 +1234,19 @@
     };
 
     public void testRSA_ECB_NoPadding_Private_OnlyDoFinal_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_OnlyDoFinal_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_OnlyDoFinal_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually decrypting with private keys, but there is no
@@ -960,13 +1265,19 @@
     }
 
     public void testRSA_ECB_NoPadding_Private_UpdateThenEmptyDoFinal_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_UpdateThenEmptyDoFinal_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_UpdateThenEmptyDoFinal_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually decrypting with private keys, but there is no
@@ -988,13 +1299,20 @@
 
     public void testRSA_ECB_NoPadding_Private_SingleByteUpdateThenEmptyDoFinal_Success()
             throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_SingleByteUpdateThenEmptyDoFinal_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_SingleByteUpdateThenEmptyDoFinal_Success(String provider)
+            throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually decrypting with private keys, but there is no
@@ -1020,12 +1338,18 @@
     }
 
     public void testRSA_ECB_NoPadding_Private_OnlyDoFinalWithOffset_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_OnlyDoFinalWithOffset_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_OnlyDoFinalWithOffset_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually decrypting with private keys, but there is no
@@ -1051,12 +1375,18 @@
     }
 
     public void testRSA_ECB_NoPadding_Public_OnlyDoFinal_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Public_OnlyDoFinal_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Public_OnlyDoFinal_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus, RSA_2048_publicExponent);
 
         final PublicKey privKey = kf.generatePublic(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1065,22 +1395,26 @@
          */
         c.init(Cipher.ENCRYPT_MODE, privKey);
         byte[] encrypted = c.doFinal(RSA_Vector1_Encrypt_Private);
-        assertTrue("Encrypted should match expected",
-                Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.ENCRYPT_MODE, RSA_2048_Vector1, encrypted);
 
         c.init(Cipher.DECRYPT_MODE, privKey);
         encrypted = c.doFinal(RSA_Vector1_Encrypt_Private);
-        assertTrue("Encrypted should match expected",
-                Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.DECRYPT_MODE, RSA_2048_Vector1, encrypted);
     }
 
     public void testRSA_ECB_NoPadding_Public_OnlyDoFinalWithOffset_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Public_OnlyDoFinalWithOffset_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Public_OnlyDoFinalWithOffset_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus, RSA_2048_publicExponent);
 
         final PublicKey pubKey = kf.generatePublic(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1092,22 +1426,33 @@
         final int encryptLen = c.doFinal(RSA_Vector1_Encrypt_Private, 0,
                 RSA_Vector1_Encrypt_Private.length, encrypted, 0);
         assertEquals("Encrypted size should match expected", RSA_2048_Vector1.length, encryptLen);
-        assertTrue("Encrypted should match expected", Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.ENCRYPT_MODE, RSA_2048_Vector1, encrypted);
 
         c.init(Cipher.DECRYPT_MODE, pubKey);
-        final int decryptLen = c.doFinal(RSA_Vector1_Encrypt_Private, 0,
+        int decryptLen = c.doFinal(RSA_Vector1_Encrypt_Private, 0,
                 RSA_Vector1_Encrypt_Private.length, encrypted, 0);
+        if (provider.equals("BC")) {
+            // BC strips the leading 0 for us on decrypt even when NoPadding is specified...
+            decryptLen++;
+            encrypted = Arrays.copyOf(encrypted, encrypted.length - 1);
+        }
         assertEquals("Encrypted size should match expected", RSA_2048_Vector1.length, decryptLen);
-        assertTrue("Encrypted should match expected", Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.DECRYPT_MODE, RSA_2048_Vector1, encrypted);
     }
 
     public void testRSA_ECB_NoPadding_Public_UpdateThenEmptyDoFinal_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Public_UpdateThenEmptyDoFinal_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Public_UpdateThenEmptyDoFinal_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus, RSA_2048_publicExponent);
 
         final PublicKey privKey = kf.generatePublic(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1117,22 +1462,29 @@
         c.init(Cipher.ENCRYPT_MODE, privKey);
         c.update(RSA_Vector1_Encrypt_Private);
         byte[] encrypted = c.doFinal();
-        assertTrue("Encrypted should match expected", Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.ENCRYPT_MODE, RSA_2048_Vector1, encrypted);
 
         c.init(Cipher.DECRYPT_MODE, privKey);
         c.update(RSA_Vector1_Encrypt_Private);
         encrypted = c.doFinal();
-        assertTrue("Encrypted should match expected", Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.DECRYPT_MODE, RSA_2048_Vector1, encrypted);
     }
 
     public void testRSA_ECB_NoPadding_Public_SingleByteUpdateThenEmptyDoFinal_Success()
             throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Public_SingleByteUpdateThenEmptyDoFinal_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Public_SingleByteUpdateThenEmptyDoFinal_Success(String provider)
+            throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus, RSA_2048_publicExponent);
 
         final PublicKey privKey = kf.generatePublic(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1145,23 +1497,29 @@
             c.update(RSA_Vector1_Encrypt_Private, i, 1);
         }
         byte[] encrypted = c.doFinal(RSA_Vector1_Encrypt_Private, i, RSA_2048_Vector1.length - i);
-        assertTrue("Encrypted should match expected", Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.ENCRYPT_MODE, RSA_2048_Vector1, encrypted);
 
         c.init(Cipher.DECRYPT_MODE, privKey);
         for (i = 0; i < RSA_Vector1_Encrypt_Private.length / 2; i++) {
             c.update(RSA_Vector1_Encrypt_Private, i, 1);
         }
         encrypted = c.doFinal(RSA_Vector1_Encrypt_Private, i, RSA_2048_Vector1.length - i);
-        assertTrue("Encrypted should match expected", Arrays.equals(RSA_2048_Vector1, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.DECRYPT_MODE, RSA_2048_Vector1, encrypted);
     }
 
     public void testRSA_ECB_NoPadding_Public_TooSmall_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Public_TooSmall_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Public_TooSmall_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus, RSA_2048_publicExponent);
 
         final PublicKey privKey = kf.generatePublic(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1180,13 +1538,19 @@
     }
 
     public void testRSA_ECB_NoPadding_Private_TooSmall_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_TooSmall_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_TooSmall_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1195,24 +1559,48 @@
          */
         c.init(Cipher.ENCRYPT_MODE, privKey);
         byte[] encrypted = c.doFinal(RSA_Vector1_ZeroPadded_Encrypted);
-        assertTrue("Encrypted should match expected",
-                Arrays.equals(TooShort_Vector_Zero_Padded, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.ENCRYPT_MODE,
+                                       TooShort_Vector_Zero_Padded, encrypted);
 
         c.init(Cipher.DECRYPT_MODE, privKey);
         encrypted = c.doFinal(RSA_Vector1_ZeroPadded_Encrypted);
-        assertTrue("Encrypted should match expected",
-                Arrays.equals(TooShort_Vector_Zero_Padded, encrypted));
+        assertEncryptedEqualsNoPadding(provider, Cipher.DECRYPT_MODE,
+                                       TooShort_Vector_Zero_Padded, encrypted);
+    }
+
+    private static void assertEncryptedEqualsNoPadding(String provider, int mode,
+                                                       byte[] expected, byte[] actual) {
+        if (provider.equals("BC") && mode == Cipher.DECRYPT_MODE) {
+            // BouncyCastle does us the favor of stripping leading zeroes in DECRYPT_MODE
+            int nonZeroOffset = 0;
+            for (byte b : expected) {
+                if (b != 0) {
+                    break;
+                }
+                nonZeroOffset++;
+            }
+            expected = Arrays.copyOfRange(expected, nonZeroOffset, expected.length);
+        }
+        assertEquals("Encrypted should match expected",
+                     Arrays.toString(expected), Arrays.toString(actual));
     }
 
     public void testRSA_ECB_NoPadding_Private_CombinedUpdateAndDoFinal_TooBig_Failure()
             throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_CombinedUpdateAndDoFinal_TooBig_Failure(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_CombinedUpdateAndDoFinal_TooBig_Failure(String provider)
+            throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1226,18 +1614,28 @@
             c.doFinal(RSA_Vector1_ZeroPadded_Encrypted);
             fail("Should have error when block size is too big.");
         } catch (IllegalBlockSizeException success) {
+            assertFalse(provider, "BC".equals(provider));
+        } catch (ArrayIndexOutOfBoundsException success) {
+            assertEquals("BC", provider);
         }
     }
 
     public void testRSA_ECB_NoPadding_Private_UpdateInAndOutPlusDoFinal_TooBig_Failure()
             throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_UpdateInAndOutPlusDoFinal_TooBig_Failure(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_UpdateInAndOutPlusDoFinal_TooBig_Failure(String provider)
+            throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1254,17 +1652,26 @@
             c.doFinal(RSA_Vector1_ZeroPadded_Encrypted);
             fail("Should have error when block size is too big.");
         } catch (IllegalBlockSizeException success) {
+            assertFalse(provider, "BC".equals(provider));
+        } catch (ArrayIndexOutOfBoundsException success) {
+            assertEquals("BC", provider);
         }
     }
 
     public void testRSA_ECB_NoPadding_Private_OnlyDoFinal_TooBig_Failure() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_Private_OnlyDoFinal_TooBig_Failure(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_Private_OnlyDoFinal_TooBig_Failure(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus,
                 RSA_2048_privateExponent);
 
         final PrivateKey privKey = kf.generatePrivate(keySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
 
         /*
          * You're actually encrypting with public keys, but there is no
@@ -1283,26 +1690,46 @@
             c.doFinal(tooBig_Vector);
             fail("Should have error when block size is too big.");
         } catch (IllegalBlockSizeException success) {
+            assertFalse(provider, "BC".equals(provider));
+        } catch (ArrayIndexOutOfBoundsException success) {
+            assertEquals("BC", provider);
         }
     }
 
     public void testRSA_ECB_NoPadding_GetBlockSize_Success() throws Exception {
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
-        assertEquals("RSA is not a block cipher and should return block size of 0",
-                0, c.getBlockSize());
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_GetBlockSize_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_GetBlockSize_Success(String provider) throws Exception {
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+        if (StandardNames.IS_RI) {
+            assertEquals(0, c.getBlockSize());
+        } else {
+            try {
+                c.getBlockSize();
+                fail();
+            } catch (IllegalStateException expected) {
+            }
+        }
 
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(RSA_2048_modulus,
                 RSA_2048_publicExponent);
         final PublicKey pubKey = kf.generatePublic(pubKeySpec);
         c.init(Cipher.ENCRYPT_MODE, pubKey);
-
-        assertEquals("RSA is not a block cipher and should return block size of 0",
-                0, c.getBlockSize());
+        assertEquals(getExpectedBlockSize("RSA", Cipher.ENCRYPT_MODE, provider), c.getBlockSize());
     }
 
     public void testRSA_ECB_NoPadding_GetOutputSize_NoInit_Failure() throws Exception {
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_GetOutputSize_NoInit_Failure(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_GetOutputSize_NoInit_Failure(String provider) throws Exception {
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
         try {
             c.getOutputSize(RSA_2048_Vector1.length);
             fail("Should throw IllegalStateException if getOutputSize is called before init");
@@ -1311,32 +1738,39 @@
     }
 
     public void testRSA_ECB_NoPadding_GetOutputSize_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_GetOutputSize_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_GetOutputSize_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(RSA_2048_modulus,
                 RSA_2048_publicExponent);
         final PublicKey pubKey = kf.generatePublic(pubKeySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
         c.init(Cipher.ENCRYPT_MODE, pubKey);
 
         final int modulusInBytes = RSA_2048_modulus.bitLength() / 8;
-        assertEquals("Output size should be equal to modulus size",
-                modulusInBytes, c.getOutputSize(RSA_2048_Vector1.length));
-
-        assertEquals("Output size should be equal to modulus size",
-                modulusInBytes, c.getOutputSize(RSA_2048_Vector1.length * 2));
-
-        assertEquals("Output size should be equal to modulus size",
-                modulusInBytes, c.getOutputSize(0));
+        assertEquals(modulusInBytes, c.getOutputSize(RSA_2048_Vector1.length));
+        assertEquals(modulusInBytes, c.getOutputSize(RSA_2048_Vector1.length * 2));
+        assertEquals(modulusInBytes, c.getOutputSize(0));
     }
 
     public void testRSA_ECB_NoPadding_GetIV_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_GetIV_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_GetIV_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(RSA_2048_modulus,
                 RSA_2048_publicExponent);
         final PublicKey pubKey = kf.generatePublic(pubKeySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
         assertNull("ECB mode has no IV and should be null", c.getIV());
 
         c.init(Cipher.ENCRYPT_MODE, pubKey);
@@ -1345,12 +1779,390 @@
     }
 
     public void testRSA_ECB_NoPadding_GetParameters_NoneProvided_Success() throws Exception {
+        for (String provider : RSA_PROVIDERS) {
+            testRSA_ECB_NoPadding_GetParameters_NoneProvided_Success(provider);
+        }
+    }
+
+    private void testRSA_ECB_NoPadding_GetParameters_NoneProvided_Success(String provider) throws Exception {
         KeyFactory kf = KeyFactory.getInstance("RSA");
         RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(RSA_2048_modulus,
                 RSA_2048_publicExponent);
         final PublicKey pubKey = kf.generatePublic(pubKeySpec);
 
-        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
+        Cipher c = Cipher.getInstance("RSA/ECB/NoPadding", provider);
         assertNull("Parameters should be null", c.getParameters());
     }
+
+    /*
+     * Test vector generation:
+     * openssl rand -hex 16
+     * echo '3d4f8970b1f27537f40a39298a41555f' | sed 's/\(..\)/(byte) 0x\1, /g'
+     */
+    private static final byte[] AES_128_KEY = new byte[] {
+            (byte) 0x3d, (byte) 0x4f, (byte) 0x89, (byte) 0x70, (byte) 0xb1, (byte) 0xf2,
+            (byte) 0x75, (byte) 0x37, (byte) 0xf4, (byte) 0x0a, (byte) 0x39, (byte) 0x29,
+            (byte) 0x8a, (byte) 0x41, (byte) 0x55, (byte) 0x5f,
+    };
+
+    /*
+     * Test key generation:
+     * openssl rand -hex 24
+     * echo '5a7a3d7e40b64ed996f7afa15f97fd595e27db6af428e342' | sed 's/\(..\)/(byte) 0x\1, /g'
+     */
+    private static final byte[] AES_192_KEY = new byte[] {
+            (byte) 0x5a, (byte) 0x7a, (byte) 0x3d, (byte) 0x7e, (byte) 0x40, (byte) 0xb6,
+            (byte) 0x4e, (byte) 0xd9, (byte) 0x96, (byte) 0xf7, (byte) 0xaf, (byte) 0xa1,
+            (byte) 0x5f, (byte) 0x97, (byte) 0xfd, (byte) 0x59, (byte) 0x5e, (byte) 0x27,
+            (byte) 0xdb, (byte) 0x6a, (byte) 0xf4, (byte) 0x28, (byte) 0xe3, (byte) 0x42,
+    };
+
+    /*
+     * Test key generation:
+     * openssl rand -hex 32
+     * echo 'ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935' | sed 's/\(..\)/(byte) 0x\1, /g'
+     */
+    private static final byte[] AES_256_KEY = new byte[] {
+            (byte) 0xec, (byte) 0x53, (byte) 0xc6, (byte) 0xd5, (byte) 0x1d, (byte) 0x2c,
+            (byte) 0x49, (byte) 0x73, (byte) 0x58, (byte) 0x5f, (byte) 0xb0, (byte) 0xb8,
+            (byte) 0xe5, (byte) 0x1c, (byte) 0xd2, (byte) 0xe3, (byte) 0x99, (byte) 0x15,
+            (byte) 0xff, (byte) 0x07, (byte) 0xa1, (byte) 0x83, (byte) 0x78, (byte) 0x72,
+            (byte) 0x71, (byte) 0x5d, (byte) 0x61, (byte) 0x21, (byte) 0xbf, (byte) 0x86,
+            (byte) 0x19, (byte) 0x35,
+    };
+
+    private static final byte[][] AES_KEYS = new byte[][] {
+            AES_128_KEY, AES_192_KEY, AES_256_KEY,
+    };
+
+    private static final String[] AES_MODES = new String[] {
+            "AES/ECB",
+            "AES/CBC",
+            "AES/CFB",
+            "AES/CTR",
+            "AES/OFB",
+    };
+
+    /*
+     * Test vector creation:
+     * echo -n 'Hello, world!' | recode ../x1 | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext = new byte[] {
+            (byte) 0x48, (byte) 0x65, (byte) 0x6C, (byte) 0x6C, (byte) 0x6F, (byte) 0x2C,
+            (byte) 0x20, (byte) 0x77, (byte) 0x6F, (byte) 0x72, (byte) 0x6C, (byte) 0x64,
+            (byte) 0x21,
+    };
+
+    /*
+     * Test vector creation:
+     * openssl enc -aes-128-ecb -K 3d4f8970b1f27537f40a39298a41555f -in blah|openssl enc -aes-128-ecb -K 3d4f8970b1f27537f40a39298a41555f -nopad -d|recode ../x1 | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded = new byte[] {
+            (byte) 0x48, (byte) 0x65, (byte) 0x6C, (byte) 0x6C, (byte) 0x6F, (byte) 0x2C,
+            (byte) 0x20, (byte) 0x77, (byte) 0x6F, (byte) 0x72, (byte) 0x6C, (byte) 0x64,
+            (byte) 0x21, (byte) 0x03, (byte) 0x03, (byte) 0x03
+    };
+
+    /*
+     * Test vector generation:
+     * openssl enc -aes-128-ecb -K 3d4f8970b1f27537f40a39298a41555f -in blah|recode ../x1 | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted = new byte[] {
+            (byte) 0x65, (byte) 0x3E, (byte) 0x86, (byte) 0xFB, (byte) 0x05, (byte) 0x5A,
+            (byte) 0x52, (byte) 0xEA, (byte) 0xDD, (byte) 0x08, (byte) 0xE7, (byte) 0x48,
+            (byte) 0x33, (byte) 0x01, (byte) 0xFC, (byte) 0x5A,
+    };
+
+    /*
+     * Test key generation:
+     * openssl rand -hex 16
+     * echo 'ceaa31952dfd3d0f5af4b2042ba06094' | sed 's/\(..\)/(byte) 0x\1, /g'
+     */
+    private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_IV = new byte[] {
+            (byte) 0xce, (byte) 0xaa, (byte) 0x31, (byte) 0x95, (byte) 0x2d, (byte) 0xfd,
+            (byte) 0x3d, (byte) 0x0f, (byte) 0x5a, (byte) 0xf4, (byte) 0xb2, (byte) 0x04,
+            (byte) 0x2b, (byte) 0xa0, (byte) 0x60, (byte) 0x94,
+    };
+
+    /*
+     * Test vector generation:
+     * echo -n 'I only regret that I have but one test to write.' | recode ../x1 | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext = new byte[] {
+            (byte) 0x49, (byte) 0x20, (byte) 0x6F, (byte) 0x6E, (byte) 0x6C, (byte) 0x79,
+            (byte) 0x20, (byte) 0x72, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x65,
+            (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x61, (byte) 0x74,
+            (byte) 0x20, (byte) 0x49, (byte) 0x20, (byte) 0x68, (byte) 0x61, (byte) 0x76,
+            (byte) 0x65, (byte) 0x20, (byte) 0x62, (byte) 0x75, (byte) 0x74, (byte) 0x20,
+            (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x20, (byte) 0x74, (byte) 0x65,
+            (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x6F, (byte) 0x20,
+            (byte) 0x77, (byte) 0x72, (byte) 0x69, (byte) 0x74, (byte) 0x65, (byte) 0x2E
+    };
+
+    /*
+     * Test vector generation:
+     * echo -n 'I only regret that I have but one test to write.' | openssl enc -aes-256-cbc -K ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935 -iv ceaa31952dfd3d0f5af4b2042ba06094 | openssl enc -aes-256-cbc -K ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935 -iv ceaa31952dfd3d0f5af4b2042ba06094 -d -nopad | recode ../x1 | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded = new byte[] {
+            (byte) 0x49, (byte) 0x20, (byte) 0x6F, (byte) 0x6E, (byte) 0x6C, (byte) 0x79,
+            (byte) 0x20, (byte) 0x72, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x65,
+            (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x61, (byte) 0x74,
+            (byte) 0x20, (byte) 0x49, (byte) 0x20, (byte) 0x68, (byte) 0x61, (byte) 0x76,
+            (byte) 0x65, (byte) 0x20, (byte) 0x62, (byte) 0x75, (byte) 0x74, (byte) 0x20,
+            (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x20, (byte) 0x74, (byte) 0x65,
+            (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x6F, (byte) 0x20,
+            (byte) 0x77, (byte) 0x72, (byte) 0x69, (byte) 0x74, (byte) 0x65, (byte) 0x2E,
+            (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10,
+            (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10,
+            (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10
+    };
+
+    /*
+     * Test vector generation:
+     * echo -n 'I only regret that I have but one test to write.' | openssl enc -aes-256-cbc -K ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935 -iv ceaa31952dfd3d0f5af4b2042ba06094 | recode ../x1 | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext = new byte[] {
+            (byte) 0x90, (byte) 0x65, (byte) 0xDD, (byte) 0xAF, (byte) 0x7A, (byte) 0xCE,
+            (byte) 0xAE, (byte) 0xBF, (byte) 0xE8, (byte) 0xF6, (byte) 0x9E, (byte) 0xDB,
+            (byte) 0xEA, (byte) 0x65, (byte) 0x28, (byte) 0xC4, (byte) 0x9A, (byte) 0x28,
+            (byte) 0xEA, (byte) 0xA3, (byte) 0x95, (byte) 0x2E, (byte) 0xFF, (byte) 0xF1,
+            (byte) 0xA0, (byte) 0xCA, (byte) 0xC2, (byte) 0xA4, (byte) 0x65, (byte) 0xCD,
+            (byte) 0xBF, (byte) 0xCE, (byte) 0x9E, (byte) 0xF1, (byte) 0x57, (byte) 0xF6,
+            (byte) 0x32, (byte) 0x2E, (byte) 0x8F, (byte) 0x93, (byte) 0x2E, (byte) 0xAE,
+            (byte) 0x41, (byte) 0x33, (byte) 0x54, (byte) 0xD0, (byte) 0xEF, (byte) 0x8C,
+            (byte) 0x52, (byte) 0x14, (byte) 0xAC, (byte) 0x2D, (byte) 0xD5, (byte) 0xA4,
+            (byte) 0xF9, (byte) 0x20, (byte) 0x77, (byte) 0x25, (byte) 0x91, (byte) 0x3F,
+            (byte) 0xD1, (byte) 0xB9, (byte) 0x00, (byte) 0x3E
+    };
+
+    private static class CipherTestParam {
+        public final String mode;
+
+        public final byte[] key;
+
+        public final byte[] iv;
+
+        public final byte[] plaintext;
+
+        public final byte[] ciphertext;
+
+        public final byte[] plaintextPadded;
+
+        public CipherTestParam(String mode, byte[] key, byte[] iv, byte[] plaintext,
+                byte[] plaintextPadded, byte[] ciphertext) {
+            this.mode = mode;
+            this.key = key;
+            this.iv = iv;
+            this.plaintext = plaintext;
+            this.plaintextPadded = plaintextPadded;
+            this.ciphertext = ciphertext;
+        }
+    }
+
+    private static List<CipherTestParam> CIPHER_TEST_PARAMS = new ArrayList<CipherTestParam>();
+    static {
+        CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB", AES_128_KEY,
+                null,
+                AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext,
+                AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded,
+                AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted));
+        if (IS_UNLIMITED) {
+            CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC", AES_256_KEY,
+                    AES_256_CBC_PKCS5Padding_TestVector_1_IV,
+                    AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext,
+                    AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded,
+                    AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext));
+        }
+    }
+
+    public void testCipher_Success() throws Exception {
+        for (String provider : AES_PROVIDERS) {
+            testCipher_Success(provider);
+        }
+    }
+
+    private void testCipher_Success(String provider) throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+        for (CipherTestParam p : CIPHER_TEST_PARAMS) {
+            try {
+                checkCipher(p, provider);
+            } catch (Exception e) {
+                out.append("Error encountered checking " + p.mode + ", keySize="
+                        + (p.key.length * 8) + "\n");
+                e.printStackTrace(out);
+            }
+        }
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+
+    private void checkCipher(CipherTestParam p, String provider) throws Exception {
+        SecretKey key = new SecretKeySpec(p.key, "AES");
+        Cipher c = Cipher.getInstance(p.mode + "/PKCS5Padding", provider);
+        AlgorithmParameterSpec spec = null;
+        if (p.iv != null) {
+            spec = new IvParameterSpec(p.iv);
+        }
+        c.init(Cipher.ENCRYPT_MODE, key, spec);
+
+        final byte[] actualCiphertext = c.doFinal(p.plaintext);
+        assertTrue(Arrays.equals(p.ciphertext, actualCiphertext));
+
+        c.init(Cipher.DECRYPT_MODE, key, spec);
+
+        final byte[] actualPlaintext = c.doFinal(p.ciphertext);
+        assertTrue(Arrays.equals(p.plaintext, actualPlaintext));
+
+        Cipher cNoPad = Cipher.getInstance(p.mode + "/NoPadding", provider);
+        cNoPad.init(Cipher.DECRYPT_MODE, key, spec);
+
+        final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext);
+        assertTrue(Arrays.equals(p.plaintextPadded, actualPlaintextPadded));
+    }
+
+    public void testCipher_ShortBlock_Failure() throws Exception {
+        for (String provider : AES_PROVIDERS) {
+            testCipher_ShortBlock_Failure(provider);
+        }
+    }
+
+    private void testCipher_ShortBlock_Failure(String provider) throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+        for (CipherTestParam p : CIPHER_TEST_PARAMS) {
+            try {
+                checkCipher_ShortBlock_Failure(p, provider);
+            } catch (Exception e) {
+                out.append("Error encountered checking " + p.mode + ", keySize="
+                        + (p.key.length * 8) + "\n");
+                e.printStackTrace(out);
+            }
+        }
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+
+    private void checkCipher_ShortBlock_Failure(CipherTestParam p, String provider) throws Exception {
+        SecretKey key = new SecretKeySpec(p.key, "AES");
+        Cipher c = Cipher.getInstance(p.mode + "/NoPadding", provider);
+        if (c.getBlockSize() == 0) {
+            return;
+        }
+
+        c.init(Cipher.ENCRYPT_MODE, key);
+        try {
+            c.doFinal(new byte[] { 0x01, 0x02, 0x03 });
+            fail("Should throw IllegalBlockSizeException on wrong-sized block");
+        } catch (IllegalBlockSizeException expected) {
+        }
+    }
+
+    public void testAES_ECB_PKCS5Padding_ShortBuffer_Failure() throws Exception {
+        for (String provider : AES_PROVIDERS) {
+            testAES_ECB_PKCS5Padding_ShortBuffer_Failure(provider);
+        }
+    }
+
+    private void testAES_ECB_PKCS5Padding_ShortBuffer_Failure(String provider) throws Exception {
+        SecretKey key = new SecretKeySpec(AES_128_KEY, "AES");
+        Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding", provider);
+        c.init(Cipher.ENCRYPT_MODE, key);
+
+        final byte[] fragmentOutput = c.update(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext);
+        if (fragmentOutput != null) {
+            assertEquals(0, fragmentOutput.length);
+        }
+
+        // Provide null buffer.
+        {
+            try {
+                c.doFinal(null, 0);
+                fail("Should throw NullPointerException on null output buffer");
+            } catch (NullPointerException expected) {
+            } catch (IllegalArgumentException expected) {
+            }
+        }
+
+        // Provide short buffer.
+        {
+            final byte[] output = new byte[c.getBlockSize() - 1];
+            try {
+                c.doFinal(output, 0);
+                fail("Should throw ShortBufferException on short output buffer");
+            } catch (ShortBufferException expected) {
+            }
+        }
+
+        // Start 1 byte into output buffer.
+        {
+            final byte[] output = new byte[c.getBlockSize()];
+            try {
+                c.doFinal(output, 1);
+                fail("Should throw ShortBufferException on short output buffer");
+            } catch (ShortBufferException expected) {
+            }
+        }
+
+        // Should keep data for real output buffer
+        {
+            final byte[] output = new byte[c.getBlockSize()];
+            assertEquals(AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted.length, c.doFinal(output, 0));
+            assertTrue(Arrays.equals(AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted, output));
+        }
+    }
+
+    public void testAES_ECB_NoPadding_IncrementalUpdate_Success() throws Exception {
+        for (String provider : AES_PROVIDERS) {
+            testAES_ECB_NoPadding_IncrementalUpdate_Success(provider);
+        }
+    }
+
+    private void testAES_ECB_NoPadding_IncrementalUpdate_Success(String provider) throws Exception {
+        SecretKey key = new SecretKeySpec(AES_128_KEY, "AES");
+        Cipher c = Cipher.getInstance("AES/ECB/NoPadding", provider);
+        c.init(Cipher.ENCRYPT_MODE, key);
+
+        for (int i = 0; i < AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded.length - 1; i++) {
+            final byte[] outputFragment = c.update(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded, i, 1);
+            if (outputFragment != null) {
+                assertEquals(0, outputFragment.length);
+            }
+        }
+
+        final byte[] output = c.doFinal(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded,
+                AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded.length - 1, 1);
+        assertNotNull(output);
+        assertEquals(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded.length, output.length);
+
+        assertTrue(Arrays.equals(AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted, output));
+    }
+
+    private static final byte[] AES_IV_ZEROES = new byte[] {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+    };
+
+    public void testAES_ECB_NoPadding_IvParameters_Failure() throws Exception {
+        for (String provider : AES_PROVIDERS) {
+            testAES_ECB_NoPadding_IvParameters_Failure(provider);
+        }
+    }
+
+    private void testAES_ECB_NoPadding_IvParameters_Failure(String provider) throws Exception {
+        SecretKey key = new SecretKeySpec(AES_128_KEY, "AES");
+        Cipher c = Cipher.getInstance("AES/ECB/NoPadding", provider);
+
+        AlgorithmParameterSpec spec = new IvParameterSpec(AES_IV_ZEROES);
+        try {
+            c.init(Cipher.ENCRYPT_MODE, key, spec);
+            fail("Should not accept an IV in ECB mode");
+        } catch (InvalidAlgorithmParameterException expected) {
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
index 8c9239e..4095081 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
@@ -19,6 +19,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.Method;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketException;
@@ -1012,7 +1013,7 @@
         assertEquals(0, wrapping.getSoTimeout());
 
         // setting wrapper sets underlying and ...
-        int expectedTimeoutMillis = 1000;  // Using a small value such as 10 was affected by rounding
+        int expectedTimeoutMillis = 1000;  // 10 was too small because it was affected by rounding
         wrapping.setSoTimeout(expectedTimeoutMillis);
         assertEquals(expectedTimeoutMillis, wrapping.getSoTimeout());
         assertEquals(expectedTimeoutMillis, underlying.getSoTimeout());
@@ -1050,6 +1051,52 @@
         listening.close();
     }
 
+    public void test_SSLSocket_setSoWriteTimeout() throws Exception {
+        if (StandardNames.IS_RI) {
+            // RI does not support write timeout on sockets
+            return;
+        }
+
+        final TestSSLContext c = TestSSLContext.create();
+        SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host,
+                                                                                       c.port);
+        final SSLSocket server = (SSLSocket) c.serverSocket.accept();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Future<Void> future = executor.submit(new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                server.startHandshake();
+                return null;
+            }
+        });
+        executor.shutdown();
+        client.startHandshake();
+
+        // Reflection is used so this can compile on the RI
+        String expectedClassName = "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl";
+        Class actualClass = client.getClass();
+        assertEquals(expectedClassName, actualClass.getName());
+        Method setSoWriteTimeout = actualClass.getMethod("setSoWriteTimeout",
+                                                         new Class[] { Integer.TYPE });
+        setSoWriteTimeout.invoke(client, 1);
+
+        // Try to make the size smaller (it can be 512k or even megabytes).
+        // Note that it may not respect your request, so read back the actual value.
+        int sendBufferSize = 1024;
+        client.setSendBufferSize(sendBufferSize);
+        sendBufferSize = client.getSendBufferSize();
+
+        try {
+            client.getOutputStream().write(new byte[sendBufferSize + 1]);
+            fail();
+        } catch (SocketTimeoutException expected) {
+        }
+
+        future.get();
+        client.close();
+        server.close();
+        c.close();
+    }
+
     public void test_SSLSocket_interrupt() throws Exception {
         ServerSocket listening = new ServerSocket(0);
 
diff --git a/luni/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java b/luni/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java
index 17d251b..a423f22 100644
--- a/luni/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java
+++ b/luni/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java
@@ -32,6 +32,7 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
+import libcore.java.lang.ref.FinalizationTester;
 
 public class ZipFileTest extends junit.framework.TestCase {
 
@@ -150,11 +151,8 @@
          * entry1); entry1 = null; zip = null;
          */
 
-        assertNotNull("Did not find entry",
-                test_finalize1(test_finalize2(file)));
-        System.gc();
-        System.gc();
-        System.runFinalization();
+        assertNotNull("Did not find entry", test_finalize1(test_finalize2(file)));
+        FinalizationTester.induceFinalization();
         file.delete();
         assertTrue("Zip should not exist", !file.exists());
     }
diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java
index 7442210..c07b180 100644
--- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java
@@ -47,6 +47,7 @@
 import javax.crypto.spec.DESedeKeySpec;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
+import libcore.java.security.StandardNames;
 import org.apache.harmony.crypto.tests.support.MyCipher;
 import tests.support.resource.Support_Resources;
 
@@ -545,7 +546,10 @@
         try {
             c.doFinal(b, 3, 6, b1, 5);
             fail();
-        } catch (ShortBufferException expected) {
+        } catch (IllegalBlockSizeException maybeExpected) {
+            assertTrue(StandardNames.IS_RI);
+        } catch (ShortBufferException maybeExpected) {
+            assertFalse(StandardNames.IS_RI);
         }
     }
 
diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java
index 0dad827..a6529e8 100644
--- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java
+++ b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java
@@ -26,199 +26,146 @@
 import java.io.ObjectStreamException;
 import java.security.KeyRep;
 import java.security.Security;
-import java.util.Iterator;
 import java.util.Set;
-
 import junit.framework.TestCase;
 
-/**
- *
- *
- */
 public class KeyRepTest extends TestCase {
 
-    private static final Set<String> keyFactoryAlgorithm;
+    private static final Set<String> keyFactoryAlgorithms = Security.getAlgorithms("KeyFactory");
     static {
-        keyFactoryAlgorithm = Security.getAlgorithms("KeyFactory");
+        assertFalse(keyFactoryAlgorithms.isEmpty());
     }
 
     public final void testKeyRep01() {
-        try {
-            assertNotNull(new KeyRep(KeyRep.Type.SECRET, "", "", new byte[] {}));
-        } catch (Exception e) {
-            fail("Unexpected exception " + e.getMessage());
-        }
-
-        try {
-            assertNotNull(new KeyRep(KeyRep.Type.PUBLIC, "", "", new byte[] {}));
-        } catch (Exception e) {
-            fail("Unexpected exception " + e.getMessage());
-        }
-
-        try {
-            assertNotNull(new KeyRep(KeyRep.Type.PRIVATE, "", "", new byte[] {}));
-        } catch (Exception e) {
-            fail("Unexpected exception " + e.getMessage());
-        }
+        assertNotNull(new KeyRep(KeyRep.Type.SECRET, "", "", new byte[] {}));
+        assertNotNull(new KeyRep(KeyRep.Type.PUBLIC, "", "", new byte[] {}));
+        assertNotNull(new KeyRep(KeyRep.Type.PRIVATE, "", "", new byte[] {}));
     }
 
     public final void testKeyRep02() {
         try {
             new KeyRep(null, "", "", new byte[] {});
             fail("NullPointerException has not been thrown (type)");
-        } catch (NullPointerException ok) {
-
+        } catch (NullPointerException expected) {
         }
         try {
             new KeyRep(KeyRep.Type.SECRET, null, "", new byte[] {});
             fail("NullPointerException has not been thrown (alg)");
-        } catch (NullPointerException ok) {
-
+        } catch (NullPointerException expected) {
         }
         try {
             new KeyRep(KeyRep.Type.PRIVATE, "", null, new byte[] {});
             fail("NullPointerException has not been thrown (format)");
-        } catch (NullPointerException ok) {
-
+        } catch (NullPointerException expected) {
         }
         try {
             new KeyRep(KeyRep.Type.PUBLIC, "", "", null);
             fail("NullPointerException has not been thrown (encoding)");
-        } catch (NullPointerException ok) {
-
+        } catch (NullPointerException expected) {
         }
     }
 
-    public final void testReadResolve01() throws ObjectStreamException {
-        KeyRepChild kr = new KeyRepChild(KeyRep.Type.SECRET, "", "",
-                new byte[] {});
+    public final void testReadResolve01() throws Exception {
+        KeyRepChild kr = new KeyRepChild(KeyRep.Type.SECRET, "", "", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (no format)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
 
         kr = new KeyRepChild(KeyRep.Type.SECRET, "", "X.509", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (unacceptable format)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
 
         kr = new KeyRepChild(KeyRep.Type.SECRET, "", "RAW", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (empty key)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
     }
 
-    public final void testReadResolve02() throws ObjectStreamException {
-        KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, "", "",
-                new byte[] {});
+    public final void testReadResolve02() throws Exception {
+        KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, "", "", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (no format)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
 
         kr = new KeyRepChild(KeyRep.Type.PUBLIC, "", "RAW", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (unacceptable format)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
 
-        kr = new KeyRepChild(KeyRep.Type.PUBLIC, "bla-bla", "X.509",
-                new byte[] {});
+        kr = new KeyRepChild(KeyRep.Type.PUBLIC, "bla-bla", "X.509", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (unknown KeyFactory algorithm)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
     }
 
-    public final void testReadResolve03() throws ObjectStreamException {
-        KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, "", "",
-                new byte[] {});
+    public final void testReadResolve03() throws Exception {
+        KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, "", "", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (no format)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
 
         kr = new KeyRepChild(KeyRep.Type.PRIVATE, "", "RAW", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (unacceptable format)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
 
-        kr = new KeyRepChild(KeyRep.Type.PRIVATE, "bla-bla", "PKCS#8",
-                new byte[] {});
+        kr = new KeyRepChild(KeyRep.Type.PRIVATE, "bla-bla", "PKCS#8", new byte[] {});
         try {
             kr.readResolve();
             fail("NotSerializableException has not been thrown (unknown KeyFactory algorithm)");
-        } catch (NotSerializableException ok) {
-
+        } catch (NotSerializableException expected) {
         }
     }
 
-    public final void testReadResolve04() throws ObjectStreamException {
-        if (keyFactoryAlgorithm.isEmpty()) {
-            System.err.println(getName()
-                    + ": skipped - no KeyFactory algorithms available");
-            return;
-        } else {
-        }
-        for (Iterator<String> i = keyFactoryAlgorithm.iterator(); i.hasNext();) {
-            KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, i.next(),
-                    "X.509", new byte[] { 1, 2, 3 });
+    public final void testReadResolve04() throws Exception {
+        for (String algorithm : keyFactoryAlgorithms) {
+            KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, algorithm, "X.509",
+                                             new byte[] { 1, 2, 3 });
             try {
                 kr.readResolve();
-                fail("NotSerializableException has not been thrown (no format)");
-            } catch (NotSerializableException ok) {
-
+                fail("NotSerializableException has not been thrown (no format) " + algorithm);
+            } catch (NotSerializableException expected) {
             }
         }
     }
 
-    public final void testReadResolve05() throws ObjectStreamException {
-        if (keyFactoryAlgorithm.isEmpty()) {
-            System.err.println(getName()
-                    + ": skipped - no KeyFactory algorithms available");
-            return;
-        } else {
-        }
-        for (Iterator<String> i = keyFactoryAlgorithm.iterator(); i.hasNext();) {
-            KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, i.next(),
-                    "PKCS#8", new byte[] { 1, 2, 3 });
+    public final void testReadResolve05() throws Exception {
+        for (String algorithm : keyFactoryAlgorithms) {
+            KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, algorithm, "PKCS#8",
+                                             new byte[] { 1, 2, 3 });
             try {
                 kr.readResolve();
-                fail("NotSerializableException has not been thrown (no format)");
-            } catch (NotSerializableException ok) {
-
+                fail("NotSerializableException has not been thrown (no format) " + algorithm);
+            } catch (NotSerializableException expected) {
             }
         }
     }
 
     class KeyRepChild extends KeyRep {
-        public KeyRepChild(KeyRep.Type type, String algorithm, String format,
-                byte[] encoded) {
+        public KeyRepChild(KeyRep.Type type, String algorithm, String format, byte[] encoded) {
             super(type, algorithm, format, encoded);
         }
 
-        public Object readResolve() throws ObjectStreamException {
+        // Overriden to make public for testing
+        @Override public Object readResolve() throws ObjectStreamException {
             return super.readResolve();
         }
-
     }
 }
diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyStoreLoadStoreParameterTest.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyStoreLoadStoreParameterTest.java
deleted file mode 100644
index 81ec30d..0000000
--- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyStoreLoadStoreParameterTest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.apache.harmony.security.tests.java.security;
-
-import java.security.KeyStore;
-
-public class KeyStoreLoadStoreParameterTest {
-
-    class MyLoadStoreParameter implements KeyStore.LoadStoreParameter {
-       public KeyStore.ProtectionParameter getProtectionParameter() {
-            return null;
-       }
-    }
-
-
-
-}
diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java
index d9f4dd7..68e7cbc 100644
--- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java
+++ b/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java
@@ -20,14 +20,14 @@
 import java.security.InvalidParameterException;
 import java.security.Provider;
 import java.security.Security;
-import java.util.Hashtable;
-import java.util.Iterator;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import junit.framework.TestCase;
 import tests.support.Support_ProviderTrust;
 import tests.support.Support_TestProvider;
 
-public class Security2Test extends junit.framework.TestCase {
+public class Security2Test extends TestCase {
 
     /**
      * java.security.Security#getProviders(java.lang.String)
@@ -36,16 +36,13 @@
         // Test for method void
         // java.security.Security.getProviders(java.lang.String)
 
-        Hashtable<String, Integer> allSupported = new Hashtable<String, Integer>();
+        Map<String, Integer> allSupported = new HashMap<String, Integer>();
         Provider[] allProviders = Security.getProviders();
 
         // Add all non-alias entries to allSupported
-        for (int i = 0; i < allProviders.length; i++) {
-            Provider provider = allProviders[i];
-            Iterator it = provider.entrySet().iterator();
-            while (it.hasNext()) {
-                Map.Entry entry = (Map.Entry) it.next();
-                String key = (String) entry.getKey();
+        for (Provider provider : allProviders) {
+            for (Object k : provider.keySet()) {
+                String key = (String) k;
                 // No aliases and no provider data
                 if (!isAlias(key) && !isProviderData(key)) {
                     addOrIncrementTable(allSupported, key);
@@ -56,22 +53,18 @@
         // Now walk through aliases. If an alias has actually been added
         // to the allSupported table then increment the count of the
         // entry that is being aliased.
-        for (int i = 0; i < allProviders.length; i++) {
-            Provider provider = allProviders[i];
-            Iterator it = provider.entrySet().iterator();
-            while (it.hasNext()) {
-                Map.Entry entry = (Map.Entry) it.next();
+        for (Provider provider : allProviders) {
+            for (Map.Entry entry : provider.entrySet()) {
                 String key = (String) entry.getKey();
                 if (isAlias(key)) {
                     String aliasVal = key.substring("ALG.ALIAS.".length());
-                    String aliasKey = aliasVal.substring(0, aliasVal
-                            .indexOf(".") + 1)
+                    String aliasKey = aliasVal.substring(0, aliasVal.indexOf(".") + 1)
                             + entry.getValue();
                     // Skip over nonsense alias declarations where alias and
                     // aliased are identical. Such entries can occur.
-                    if (!aliasVal.equals(aliasKey)) {
+                    if (!aliasVal.equalsIgnoreCase(aliasKey)) {
                         // Has a real entry been added for aliasValue ?
-                        if (allSupported.containsKey(aliasVal)) {
+                        if (allSupported.containsKey(aliasVal.toUpperCase())) {
                             // Add 1 to the provider count of the thing being
                             // aliased
                             addOrIncrementTable(allSupported, aliasKey);
@@ -81,17 +74,13 @@
             }// end while more entries
         }// end for all providers
 
-        Provider provTest[] = null;
-        Iterator it = allSupported.keySet().iterator();
-        while (it.hasNext()) {
-            String filterString = (String) it.next();
+        for (String filterString : allSupported.keySet()) {
             try {
-                provTest = Security.getProviders(filterString);
-                int expected = ((Integer) allSupported.get(filterString))
-                        .intValue();
-                assertEquals(
-                        "Unexpected number of providers returned for filter "
-                                + filterString, expected, provTest.length);
+                Provider[] provTest = Security.getProviders(filterString);
+                int expected = allSupported.get(filterString);
+                assertEquals("Unexpected number of providers returned for filter " + filterString
+                             + ":\n" + allSupported,
+                             expected, provTest.length);
             } catch (InvalidParameterException e) {
                 // NO OP
             }
@@ -99,62 +88,43 @@
 
         // exception
         try {
-            provTest = Security.getProviders("Signature.SHA1withDSA :512");
+            Security.getProviders("Signature.SHA1withDSA :512");
             fail("InvalidParameterException should be thrown <Signature.SHA1withDSA :512>");
         } catch (InvalidParameterException e) {
             // Expected
         }
     }
 
-    /**
-     * @param key
-     * @return
-     */
     private boolean isProviderData(String key) {
         return key.toUpperCase().startsWith("PROVIDER.");
     }
 
-    /**
-     * @param key
-     * @return
-     */
     private boolean isAlias(String key) {
         return key.toUpperCase().startsWith("ALG.ALIAS.");
     }
 
-    /**
-     * @param table
-     * @param key
-     */
-    private void addOrIncrementTable(Hashtable<String, Integer> table, String key) {
+    private void addOrIncrementTable(Map<String, Integer> table, String k) {
+        String key = k.toUpperCase(); 
         if (table.containsKey(key)) {
-            Integer before = (Integer) table.get(key);
-            table.put(key, new Integer(before.intValue() + 1));
+            int before = table.get(key);
+            table.put(key, before + 1);
         } else {
-            table.put(key, new Integer(1));
+            table.put(key, 1);
         }
     }
 
-    /**
-     * @param filterMap
-     * @return
-     */
     private int getProvidersCount(Map filterMap) {
         int result = 0;
         Provider[] allProviders = Security.getProviders();
 
         // for each provider
-        for (int i = 0; i < allProviders.length; i++) {
-            Provider provider = allProviders[i];
+        for (Provider provider : allProviders) {
             Set allProviderKeys = provider.keySet();
             boolean noMatchFoundForFilterEntry = false;
 
             // for each filter item
-            Set allFilterKeys = filterMap.keySet();
-            Iterator fkIter = allFilterKeys.iterator();
-            while (fkIter.hasNext()) {
-                String filterString = ((String) fkIter.next()).trim();
-
+            for (Object filter : filterMap.keySet()) {
+                String filterString = (String) filter;
                 // Remove any "=" characters that may be on the end of the
                 // map keys (no, I don't know why they might be there either
                 // but I have seen them)
@@ -211,10 +181,10 @@
         // Test for method void
         // java.security.Security.getProviders(java.util.Map)
 
-        Map<String, String> filter = new Hashtable<String, String>();
+        Map<String, String> filter = new HashMap<String, String>();
         filter.put("KeyStore.BKS", "");
         filter.put("Signature.SHA1withDSA", "");
-        Provider provTest[] = Security.getProviders(filter);
+        Provider[] provTest = Security.getProviders(filter);
         if (provTest == null) {
             assertEquals("Filter : <KeyStore.BKS>,<Signature.SHA1withDSA>",
                     0, getProvidersCount(filter));
@@ -223,7 +193,7 @@
                     getProvidersCount(filter), provTest.length);
         }
 
-        filter = new Hashtable<String, String>();
+        filter = new HashMap<String, String>();
         filter.put("MessageDigest.SHA-384", "");
         filter.put("CertificateFactory.X.509", "");
         filter.put("KeyFactory.RSA", "");
@@ -237,7 +207,7 @@
                     getProvidersCount(filter), provTest.length);
         }
 
-        filter = new Hashtable<String, String>();
+        filter = new HashMap<String, String>();
         filter.put("MessageDigest.SHA1", "");
         filter.put("TrustManagerFactory.X509", "");
         provTest = Security.getProviders(filter);
@@ -250,7 +220,7 @@
                     getProvidersCount(filter), provTest.length);
         }
 
-        filter = new Hashtable<String, String>();
+        filter = new HashMap<String, String>();
         filter.put("CertificateFactory.X509", "");
         provTest = Security.getProviders(filter);
         if (provTest == null) {
@@ -261,7 +231,7 @@
                     getProvidersCount(filter), provTest.length);
         }
 
-        filter = new Hashtable<String, String>();
+        filter = new HashMap<String, String>();
         filter.put("Provider.id name", "DRLCertFactory");
         provTest = Security.getProviders(filter);
         assertNull("Filter : <Provider.id name, DRLCertFactory >",
@@ -270,7 +240,7 @@
         // exception - no attribute name after the service.algorithm yet we
         // still supply an expected value. This is not valid.
         try {
-            filter = new Hashtable<String, String>();
+            filter = new HashMap<String, String>();
             filter.put("Signature.SHA1withDSA", "512");
             provTest = Security.getProviders(filter);
             fail("InvalidParameterException should be thrown <Signature.SHA1withDSA><512>");
@@ -280,7 +250,7 @@
 
         // exception - space character in the service.algorithm pair. Not valid.
         try {
-            filter = new Hashtable<String, String>();
+            filter = new HashMap<String, String>();
             filter.put("Signature. KeySize", "512");
             provTest = Security.getProviders(filter);
             fail("InvalidParameterException should be thrown <Signature. KeySize><512>");
@@ -320,11 +290,9 @@
             assertTrue("Failed to add provider", addResult != -1);
 
             Security.removeProvider(entrust.getName());
-            Provider provTest[] = Security.getProviders();
-            for (int i = 0; i < provTest.length; i++) {
-                assertTrue(
-                        "the provider entrust is found after it was removed",
-                        provTest[i].getName() != entrust.getName());
+            for (Provider provider : Security.getProviders()) {
+                assertTrue("the provider entrust is found after it was removed",
+                           provider.getName() != entrust.getName());
             }
         } finally {
             // Tidy up - the following calls do nothing if the providers were
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java
new file mode 100644
index 0000000..8359c99
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.security.cert.X509Certificate;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.TestCase;
+import libcore.java.security.TestKeyStore;
+
+public class CertPinManagerTest extends TestCase {
+
+    private X509Certificate[] chain;
+    private List<X509Certificate> shortChain;
+    private List<X509Certificate> longChain;
+    private String shortPin;
+    private String longPin;
+    private List<File> tmpFiles = new ArrayList<File>();
+
+    private String writeTmpPinFile(String text) throws Exception {
+        File tmp = File.createTempFile("pins", null);
+        FileWriter fstream = new FileWriter(tmp);
+        fstream.write(text);
+        fstream.close();
+        tmpFiles.add(tmp);
+        return tmp.getPath();
+    }
+
+    private static String getFingerprint(X509Certificate cert) throws NoSuchAlgorithmException {
+        MessageDigest dgst = MessageDigest.getInstance("SHA512");
+        byte[] encoded = cert.getPublicKey().getEncoded();
+        byte[] fingerprint = dgst.digest(encoded);
+        return IntegralToString.bytesToHexString(fingerprint, false);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        // build some valid chains
+        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+        chain = (X509Certificate[]) pke.getCertificateChain();
+        X509Certificate root = chain[2];
+        X509Certificate server = chain[0];
+
+        // build the short and long chains
+        shortChain = new ArrayList<X509Certificate>();
+        shortChain.add(root);
+        longChain = new ArrayList<X509Certificate>();
+        longChain.add(server);
+
+        // we'll use the root as the pin for the short entry and the server as the pin for the long
+        shortPin = getFingerprint(root);
+        longPin = getFingerprint(server);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            for (File f : tmpFiles) {
+                f.delete();
+            }
+            tmpFiles.clear();
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    public void testPinFileMaximumLookup() throws Exception {
+
+        // write a pinfile with two entries, one longer than the other
+        String shortEntry = "*.google.com=true|" + shortPin;
+        String longEntry = "*.clients.google.com=true|" + longPin;
+
+        // create the pinFile
+        String path = writeTmpPinFile(shortEntry + "\n" + longEntry);
+        CertPinManager pf = new CertPinManager(path, new TrustedCertificateStore());
+
+        // verify that the shorter chain doesn't work for a name matching the longer
+        assertTrue("short chain long uri failed",
+                   pf.chainIsNotPinned("android.clients.google.com", shortChain));
+        // verify that the longer chain doesn't work for a name matching the shorter
+        assertTrue("long chain short uri failed",
+                   pf.chainIsNotPinned("android.google.com", longChain));
+        // verify that the shorter chain works for the shorter domain
+        assertTrue("short chain short uri failed",
+                   !pf.chainIsNotPinned("android.google.com", shortChain));
+        // and the same for the longer
+        assertTrue("long chain long uri failed",
+                   !pf.chainIsNotPinned("android.clients.google.com", longChain));
+    }
+
+    public void testPinEntryMalformedEntry() throws Exception {
+        // set up the pinEntry with a bogus entry
+        String entry = "*.google.com=";
+        try {
+            new PinListEntry(entry, new TrustedCertificateStore());
+            fail("Accepted an empty pin list entry.");
+        } catch (PinEntryException expected) {
+        }
+    }
+
+    public void testPinEntryNull() throws Exception {
+        // set up the pinEntry with a bogus entry
+        String entry = null;
+        try {
+            new PinListEntry(entry, new TrustedCertificateStore());
+            fail("Accepted a basically wholly bogus entry.");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    public void testPinEntryEmpty() throws Exception {
+        // set up the pinEntry with a bogus entry
+        try {
+            new PinListEntry("", new TrustedCertificateStore());
+            fail("Accepted an empty entry.");
+        } catch (PinEntryException expected) {
+        }
+    }
+
+    public void testPinEntryPinFailure() throws Exception {
+        // write a pinfile with two entries, one longer than the other
+        String shortEntry = "*.google.com=true|" + shortPin;
+
+        // set up the pinEntry with a pinlist that doesn't match what we'll give it
+        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
+        assertTrue("Not enforcing!", e.getEnforcing());
+        // verify that it doesn't accept
+        boolean retval = e.chainIsNotPinned(longChain);
+        assertTrue("Accepted an incorrect pinning, this is very bad", retval);
+    }
+
+    public void testPinEntryPinSuccess() throws Exception {
+        // write a pinfile with two entries, one longer than the other
+        String shortEntry = "*.google.com=true|" + shortPin;
+
+        // set up the pinEntry with a pinlist that matches what we'll give it
+        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
+        assertTrue("Not enforcing!", e.getEnforcing());
+        // verify that it accepts
+        boolean retval = e.chainIsNotPinned(shortChain);
+        assertTrue("Failed on a correct pinning, this is very bad", !retval);
+    }
+
+    public void testPinEntryNonEnforcing() throws Exception {
+        // write a pinfile with two entries, one longer than the other
+        String shortEntry = "*.google.com=false|" + shortPin;
+
+        // set up the pinEntry with a pinlist that matches what we'll give it
+        PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
+        assertFalse("Enforcing!", e.getEnforcing());
+        // verify that it accepts
+        boolean retval = e.chainIsNotPinned(shortChain);
+        assertTrue("Failed on an unenforced pinning, this is bad-ish", !retval);
+    }
+}
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java
index 15b175c..303c234 100644
--- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java
@@ -21,10 +21,14 @@
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
 import java.security.KeyStore;
 import java.security.KeyStore.PrivateKeyEntry;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -38,6 +42,7 @@
 import javax.net.ssl.SSLProtocolException;
 import javax.security.auth.x500.X500Principal;
 import junit.framework.TestCase;
+import libcore.io.IoUtils;
 import libcore.java.security.StandardNames;
 import libcore.java.security.TestKeyStore;
 import org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSLHandshakeCallbacks;
@@ -47,7 +52,8 @@
 
     private static final int NULL = 0;
     private static final FileDescriptor INVALID_FD = new FileDescriptor();
-    private static final SSLHandshakeCallbacks DUMMY_CB = new TestSSLHandshakeCallbacks(-1, null);
+    private static final SSLHandshakeCallbacks DUMMY_CB
+            = new TestSSLHandshakeCallbacks(null, 0, null);
 
     private static final long TIMEOUT_SECONDS = 5;
 
@@ -131,6 +137,87 @@
         assertEquals(Arrays.deepToString(expected), Arrays.deepToString(actual));
     }
 
+    public void test_EVP_PKEY_cmp() throws Exception {
+        try {
+            NativeCrypto.EVP_PKEY_cmp(NULL, NULL);
+            fail("Should throw NullPointerException when arguments are NULL");
+        } catch (NullPointerException expected) {
+        }
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        kpg.initialize(512);
+
+        KeyPair kp1 = kpg.generateKeyPair();
+        RSAPrivateCrtKey privKey1 = (RSAPrivateCrtKey) kp1.getPrivate();
+
+        KeyPair kp2 = kpg.generateKeyPair();
+        RSAPrivateCrtKey privKey2 = (RSAPrivateCrtKey) kp2.getPrivate();
+
+        int pkey1 = 0, pkey1_copy = 0, pkey2 = 0;
+        try {
+            pkey1 = NativeCrypto.EVP_PKEY_new_RSA(privKey1.getModulus().toByteArray(),
+                        privKey1.getPublicExponent().toByteArray(),
+                        privKey1.getPrivateExponent().toByteArray(),
+                        privKey1.getPrimeP().toByteArray(),
+                        privKey1.getPrimeQ().toByteArray(),
+                        privKey1.getPrimeExponentP().toByteArray(),
+                        privKey1.getPrimeExponentQ().toByteArray(),
+                        privKey1.getCrtCoefficient().toByteArray());
+            assertNotSame(NULL, pkey1);
+
+            pkey1_copy = NativeCrypto.EVP_PKEY_new_RSA(privKey1.getModulus().toByteArray(),
+                    privKey1.getPublicExponent().toByteArray(),
+                    privKey1.getPrivateExponent().toByteArray(),
+                    privKey1.getPrimeP().toByteArray(),
+                    privKey1.getPrimeQ().toByteArray(),
+                    privKey1.getPrimeExponentP().toByteArray(),
+                    privKey1.getPrimeExponentQ().toByteArray(),
+                    privKey1.getCrtCoefficient().toByteArray());
+            assertNotSame(NULL, pkey1_copy);
+
+            pkey2 = NativeCrypto.EVP_PKEY_new_RSA(privKey2.getModulus().toByteArray(),
+                    privKey2.getPublicExponent().toByteArray(),
+                    privKey2.getPrivateExponent().toByteArray(),
+                    privKey2.getPrimeP().toByteArray(),
+                    privKey2.getPrimeQ().toByteArray(),
+                    privKey2.getPrimeExponentP().toByteArray(),
+                    privKey2.getPrimeExponentQ().toByteArray(),
+                    privKey2.getCrtCoefficient().toByteArray());
+            assertNotSame(NULL, pkey2);
+
+            try {
+                NativeCrypto.EVP_PKEY_cmp(pkey1, NULL);
+                fail("Should throw NullPointerException when arguments are NULL");
+            } catch (NullPointerException expected) {
+            }
+
+            try {
+                NativeCrypto.EVP_PKEY_cmp(NULL, pkey1);
+                fail("Should throw NullPointerException when arguments are NULL");
+            } catch (NullPointerException expected) {
+            }
+
+            assertEquals("Same keys should be the equal", 1,
+                    NativeCrypto.EVP_PKEY_cmp(pkey1, pkey1));
+
+            assertEquals("Same keys should be the equal", 1,
+                    NativeCrypto.EVP_PKEY_cmp(pkey1, pkey1_copy));
+
+            assertEquals("Different keys should not be equal", 0,
+                    NativeCrypto.EVP_PKEY_cmp(pkey1, pkey2));
+        } finally {
+            if (pkey1 != 0) {
+                NativeCrypto.EVP_PKEY_free(pkey1);
+            }
+            if (pkey1_copy != 0) {
+                NativeCrypto.EVP_PKEY_free(pkey1_copy);
+            }
+            if (pkey2 != 0) {
+                NativeCrypto.EVP_PKEY_free(pkey2);
+            }
+        }
+    }
+
     public void test_SSL_CTX_new() throws Exception {
         int c = NativeCrypto.SSL_CTX_new();
         assertTrue(c != NULL);
@@ -490,11 +577,14 @@
     }
 
     public static class TestSSLHandshakeCallbacks implements SSLHandshakeCallbacks {
+        private final Socket socket;
         private final int sslNativePointer;
         private final Hooks hooks;
 
-        public TestSSLHandshakeCallbacks(int sslNativePointer,
+        public TestSSLHandshakeCallbacks(Socket socket,
+                                         int sslNativePointer,
                                          Hooks hooks) {
+            this.socket = socket;
             this.sslNativePointer = sslNativePointer;
             this.hooks = hooks;
         }
@@ -546,6 +636,10 @@
             }
             this.handshakeCompletedCalled = true;
         }
+
+        public Socket getSocket() {
+            return socket;
+        }
     }
 
     public static class ServerHooks extends Hooks {
@@ -583,12 +677,13 @@
                                               listener.getLocalPort())
                                  : listener.accept());
                 if (timeout == -1) {
-                    return null;
+                    return new TestSSLHandshakeCallbacks(socket, 0, null);
                 }
                 FileDescriptor fd = socket.getFileDescriptor$();
                 int c = hooks.getContext();
                 int s = hooks.beforeHandshake(c);
-                TestSSLHandshakeCallbacks callback = new TestSSLHandshakeCallbacks(s, hooks);
+                TestSSLHandshakeCallbacks callback
+                        = new TestSSLHandshakeCallbacks(socket, s, hooks);
                 if (DEBUG) {
                     System.out.println("ssl=0x" + Integer.toString(s, 16)
                                        + " handshake"
@@ -598,14 +693,19 @@
                                        + " timeout=" + timeout
                                        + " client=" + client);
                 }
-                int session = NativeCrypto.SSL_do_handshake(s, fd, callback, timeout, client,
-                        npnProtocols);
-                if (DEBUG) {
-                    System.out.println("ssl=0x" + Integer.toString(s, 16)
-                                       + " handshake"
-                                       + " session=0x" + Integer.toString(session, 16));
+                int session = NULL;
+                try {
+                    session = NativeCrypto.SSL_do_handshake(s, fd, callback, timeout, client,
+                                                            npnProtocols);
+                    if (DEBUG) {
+                        System.out.println("ssl=0x" + Integer.toString(s, 16)
+                                           + " handshake"
+                                           + " session=0x" + Integer.toString(session, 16));
+                    }
+                } finally {
+                    // Ensure afterHandshake is called to free resources
+                    hooks.afterHandshake(session, s, c, socket, fd, callback);
                 }
-                hooks.afterHandshake(session, s, c, socket, fd, callback);
                 return callback;
             }
         });
@@ -777,17 +877,21 @@
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
-                NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_PEER);
-                NativeCrypto.SSL_set_options(
-                        s, NativeCrypto.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
-                NativeCrypto.SSL_renegotiate(s);
-                NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1);
-                super.afterHandshake(session, s, c, sock, fd, callback);
+                try {
+                    NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_PEER);
+                    NativeCrypto.SSL_set_options(
+                            s, NativeCrypto.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+                    NativeCrypto.SSL_renegotiate(s);
+                    NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1,
+                                           (int) ((TIMEOUT_SECONDS * 1000) / 2));
+                } catch (IOException expected) {
+                } finally {
+                    super.afterHandshake(session, s, c, sock, fd, callback);
+                }
             }
         };
         Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null);
         Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null);
-        server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
         try {
             client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (ExecutionException e) {
@@ -795,35 +899,49 @@
                 throw e;
             }
         }
+        server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
     public void test_SSL_do_handshake_client_timeout() throws Exception {
         // client timeout
         final ServerSocket listener = new ServerSocket(0);
+        Socket serverSocket = null;
         try {
             Hooks cHooks = new Hooks();
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
             Future<TestSSLHandshakeCallbacks> client = handshake(listener, 1, true, cHooks, null);
             Future<TestSSLHandshakeCallbacks> server = handshake(listener, -1, false, sHooks, null);
+            serverSocket = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).getSocket();
             client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
             fail();
         } catch (ExecutionException expected) {
+            if (SocketTimeoutException.class != expected.getCause().getClass()) {
+                expected.printStackTrace();
+            }
             assertEquals(SocketTimeoutException.class, expected.getCause().getClass());
+        } finally {
+            // Manually close peer socket when testing timeout
+            IoUtils.closeQuietly(serverSocket);
         }
     }
 
     public void test_SSL_do_handshake_server_timeout() throws Exception {
         // server timeout
         final ServerSocket listener = new ServerSocket(0);
+        Socket clientSocket = null;
         try {
             Hooks cHooks = new Hooks();
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
             Future<TestSSLHandshakeCallbacks> client = handshake(listener, -1, true, cHooks, null);
             Future<TestSSLHandshakeCallbacks> server = handshake(listener, 1, false, sHooks, null);
+            clientSocket = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).getSocket();
             server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
             fail();
         } catch (ExecutionException expected) {
             assertEquals(SocketTimeoutException.class, expected.getCause().getClass());
+        } finally {
+            // Manually close peer socket when testing timeout
+            IoUtils.closeQuietly(clientSocket);
         }
     }
 
@@ -1150,7 +1268,7 @@
                                        SSLHandshakeCallbacks callback)
                 throws Exception {
                 NativeCrypto.SSL_renegotiate(s);
-                NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1);
+                NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1, 0);
                 super.afterHandshake(session, s, c, sock, fd, callback);
             }
         };
@@ -1317,7 +1435,7 @@
                                            Socket sock, FileDescriptor fd,
                                            SSLHandshakeCallbacks callback)
                         throws Exception {
-                    NativeCrypto.SSL_write(s, fd, callback, BYTES, 0, BYTES.length);
+                    NativeCrypto.SSL_write(s, fd, callback, BYTES, 0, BYTES.length, 0);
                     super.afterHandshake(session, s, c, sock, fd, callback);
                 }
             };
@@ -1360,7 +1478,7 @@
 
     public void test_SSL_write() throws Exception {
         try {
-            NativeCrypto.SSL_write(NULL, null, null, null, 0, 0);
+            NativeCrypto.SSL_write(NULL, null, null, null, 0, 0, 0);
             fail();
         } catch (NullPointerException expected) {
         }
@@ -1370,7 +1488,7 @@
             int c = NativeCrypto.SSL_CTX_new();
             int s = NativeCrypto.SSL_new(c);
             try {
-                NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1);
+                NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1, 0);
                 fail();
             } catch (NullPointerException expected) {
             }
@@ -1383,7 +1501,7 @@
             int c = NativeCrypto.SSL_CTX_new();
             int s = NativeCrypto.SSL_new(c);
             try {
-                NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1);
+                NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1, 0);
                 fail();
             } catch (NullPointerException expected) {
             }
@@ -1396,7 +1514,7 @@
             int c = NativeCrypto.SSL_CTX_new();
             int s = NativeCrypto.SSL_new(c);
             try {
-                NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1);
+                NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1, 0);
                 fail();
             } catch (NullPointerException expected) {
             }
@@ -1409,7 +1527,7 @@
             int c = NativeCrypto.SSL_CTX_new();
             int s = NativeCrypto.SSL_new(c);
             try {
-                NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1);
+                NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
                 fail();
             } catch (SSLException expected) {
             }
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java
index 26ebc85..fe5f4f0 100644
--- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java
@@ -16,9 +16,15 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
-import java.security.KeyStore;
+import java.io.File;
+import java.io.FileWriter;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
@@ -27,12 +33,41 @@
 
 public class TrustManagerImplTest extends TestCase {
 
+    private List<File> tmpFiles = new ArrayList<File>();
+
+    private String getFingerprint(X509Certificate cert) throws Exception {
+        MessageDigest dgst = MessageDigest.getInstance("SHA512");
+        byte[] encoded = cert.getPublicKey().getEncoded();
+        byte[] fingerprint = dgst.digest(encoded);
+        return IntegralToString.bytesToHexString(fingerprint, false);
+    }
+
+    private String writeTmpPinFile(String text) throws Exception {
+        File tmp = File.createTempFile("pins", null);
+        FileWriter fstream = new FileWriter(tmp);
+        fstream.write(text);
+        fstream.close();
+        tmpFiles.add(tmp);
+        return tmp.getPath();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        try {
+            for (File f : tmpFiles) {
+                f.delete();
+            }
+            tmpFiles.clear();
+        } finally {
+            super.tearDown();
+        }
+    }
+
     /**
      * Ensure that our non-standard behavior of learning to trust new
      * intermediate CAs does not regress. http://b/3404902
      */
     public void testLearnIntermediate() throws Exception {
-
         // chain3 should be server/intermediate/root
         KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
         X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
@@ -63,6 +98,52 @@
         assertValid(chain1, tm);
     }
 
+    public void testGetFullChain() throws Exception {
+        // build the trust manager
+        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+        X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
+        X509Certificate root = chain3[2];
+        X509TrustManager tm = trustManager(root);
+
+        // build the chains we'll use for testing
+        X509Certificate intermediate = chain3[1];
+        X509Certificate server = chain3[0];
+        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
+        X509Certificate[] chain1 =  new X509Certificate[] { server };
+
+        assertTrue(tm instanceof TrustManagerImpl);
+        TrustManagerImpl tmi = (TrustManagerImpl) tm;
+        List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", "purple.com");
+        assertEquals(Arrays.asList(chain3), certs);
+        certs = tmi.checkServerTrusted(chain1, "RSA", "purple.com");
+        assertEquals(Arrays.asList(chain3), certs);
+    }
+
+    public void testCertPinning() throws Exception {
+        // chain3 should be server/intermediate/root
+        KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+        X509Certificate[] chain3 = (X509Certificate[]) pke.getCertificateChain();
+        X509Certificate root = chain3[2];
+        X509Certificate intermediate = chain3[1];
+        X509Certificate server = chain3[0];
+        X509Certificate[] chain2 =  new X509Certificate[] { server, intermediate };
+        X509Certificate[] chain1 =  new X509Certificate[] { server };
+
+        // test without a hostname, expecting failure
+        assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), null);
+        // test without a hostname, expecting success
+        assertValidPinned(chain3, trustManager(root, "gugle.com", root), null, chain3);
+        // test an unpinned hostname that should fail
+        assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), "purple.com");
+        // test an unpinned hostname that should succeed
+        assertValidPinned(chain3, trustManager(root, "gugle.com", root), "purple.com", chain3);
+        // test a pinned hostname that should fail
+        assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com");
+        // test a pinned hostname that should succeed
+        assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com",
+                                                                                            chain2);
+    }
+
     private X509TrustManager trustManager(X509Certificate ca) throws Exception {
         KeyStore keyStore = TestKeyStore.createKeyStore();
         keyStore.setCertificateEntry("alias", ca);
@@ -73,10 +154,45 @@
         return (X509TrustManager) tmf.getTrustManagers()[0];
     }
 
+    private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin)
+                                          throws Exception {
+        // build the cert pin manager
+        CertPinManager cm = certManager(hostname, pin);
+        // insert it into the trust manager
+        KeyStore keyStore = TestKeyStore.createKeyStore();
+        keyStore.setCertificateEntry("alias", ca);
+        return new TrustManagerImpl(keyStore, cm);
+    }
+
+    private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception {
+        String pinString = "";
+        if (pin != null) {
+            pinString = hostname + "=true|" + getFingerprint(pin);
+        }
+        // write it to a pinfile
+        String path = writeTmpPinFile(pinString);
+        // build the certpinmanager
+        return new CertPinManager(path, new TrustedCertificateStore());
+    }
+
     private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception {
-        tm.checkClientTrusted(chain, "RSA");
+        if (tm instanceof TrustManagerImpl) {
+            TrustManagerImpl tmi = (TrustManagerImpl) tm;
+            tmi.checkServerTrusted(chain, "RSA");
+        }
         tm.checkServerTrusted(chain, "RSA");
     }
+
+    private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname,
+                                   X509Certificate[] fullChain) throws Exception {
+        if (tm instanceof TrustManagerImpl) {
+            TrustManagerImpl tmi = (TrustManagerImpl) tm;
+            List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", hostname);
+            assertEquals(checkedChain, Arrays.asList(fullChain));
+        }
+        tm.checkServerTrusted(chain, "RSA");
+    }
+
     private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) {
         try {
             tm.checkClientTrusted(chain, "RSA");
@@ -89,4 +205,15 @@
         } catch (CertificateException expected) {
         }
     }
+
+    private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname)
+                                     throws Exception {
+        assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl);
+        try {
+            TrustManagerImpl tmi = (TrustManagerImpl) tm;
+            tmi.checkServerTrusted(chain, "RSA", hostname);
+            fail();
+        } catch (CertificateException expected) {
+        }
+    }
 }
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java
index 52880df..8f9b7fa 100644
--- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java
@@ -530,6 +530,26 @@
         assertDeleted(getCa1(), getAliasSystemCa1());
     }
 
+    public void testIsUserAddedCertificate() throws Exception {
+        assertFalse(store.isUserAddedCertificate(getCa1()));
+        assertFalse(store.isUserAddedCertificate(getCa2()));
+        install(getCa1(), getAliasSystemCa1());
+        assertFalse(store.isUserAddedCertificate(getCa1()));
+        assertFalse(store.isUserAddedCertificate(getCa2()));
+        install(getCa1(), getAliasUserCa1());
+        assertTrue(store.isUserAddedCertificate(getCa1()));
+        assertFalse(store.isUserAddedCertificate(getCa2()));
+        install(getCa2(), getAliasUserCa2());
+        assertTrue(store.isUserAddedCertificate(getCa1()));
+        assertTrue(store.isUserAddedCertificate(getCa2()));
+        store.deleteCertificateEntry(getAliasUserCa1());
+        assertFalse(store.isUserAddedCertificate(getCa1()));
+        assertTrue(store.isUserAddedCertificate(getCa2()));
+        store.deleteCertificateEntry(getAliasUserCa2());
+        assertFalse(store.isUserAddedCertificate(getCa1()));
+        assertFalse(store.isUserAddedCertificate(getCa2()));
+    }
+
     private void assertRootCa(X509Certificate x, String alias) {
         assertIntermediateCa(x, alias);
         assertEquals(x, store.findIssuer(x));
diff --git a/luni/src/test/java/tests/api/java/lang/ref/PhantomReferenceTest.java b/luni/src/test/java/tests/api/java/lang/ref/PhantomReferenceTest.java
index 06221c9..6470579 100644
--- a/luni/src/test/java/tests/api/java/lang/ref/PhantomReferenceTest.java
+++ b/luni/src/test/java/tests/api/java/lang/ref/PhantomReferenceTest.java
@@ -20,6 +20,7 @@
 import java.lang.ref.PhantomReference;
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
+import libcore.java.lang.ref.FinalizationTester;
 
 //TODO: write a test to verify that the referent's finalize() happens
 //      before the PhantomReference is enqueued.
@@ -81,8 +82,8 @@
             Thread t = new TestThread();
             t.start();
             t.join();
-            System.gc();
-            System.runFinalization();
+
+            FinalizationTester.induceFinalization();
 
             assertNull("get() should return null.", tprs[0].get());
             assertNull("get() should return null.", tprs[1].get());
diff --git a/luni/src/test/java/tests/api/java/lang/ref/ReferenceQueueTest.java b/luni/src/test/java/tests/api/java/lang/ref/ReferenceQueueTest.java
index dc7e738..cad61b3 100644
--- a/luni/src/test/java/tests/api/java/lang/ref/ReferenceQueueTest.java
+++ b/luni/src/test/java/tests/api/java/lang/ref/ReferenceQueueTest.java
@@ -22,6 +22,7 @@
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
+import libcore.java.lang.ref.FinalizationTester;
 
 public class ReferenceQueueTest extends junit.framework.TestCase {
     static Boolean b;
@@ -97,8 +98,7 @@
         sr.enqueue();
         wr.enqueue();
 
-        System.gc();
-        System.runFinalization();
+        FinalizationTester.induceFinalization();
 
         assertNull(rq.poll());
     }
diff --git a/luni/src/test/java/tests/api/java/lang/ref/ReferenceTest.java b/luni/src/test/java/tests/api/java/lang/ref/ReferenceTest.java
index a1a7a8c..7461b47 100644
--- a/luni/src/test/java/tests/api/java/lang/ref/ReferenceTest.java
+++ b/luni/src/test/java/tests/api/java/lang/ref/ReferenceTest.java
@@ -22,6 +22,7 @@
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import junit.framework.AssertionFailedError;
+import libcore.java.lang.ref.FinalizationTester;
 
 public class ReferenceTest extends junit.framework.TestCase {
     Object tmpA, tmpB, tmpC, obj;
@@ -146,16 +147,14 @@
         ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
 
         r = newWeakReference(queue);
-        System.gc();
-        System.runFinalization();
+        FinalizationTester.induceFinalization();
         Reference ref = queue.remove();
         assertNotNull("Object not enqueued.", ref);
         assertSame("Unexpected ref1", ref, r);
         assertNull("Object could not be reclaimed1.", r.get());
 
         r = newWeakReference(queue);
-        System.gc();
-        System.runFinalization();
+        FinalizationTester.induceFinalization();
 
         // wait for the reference queue thread to enqueue the newly-finalized object
         Thread.yield();
@@ -213,8 +212,7 @@
             Thread t = new TestThread();
             t.start();
             t.join();
-            System.gc();
-            System.runFinalization();
+            FinalizationTester.induceFinalization();
             ref = rq.remove(5000L);    // Give up after five seconds.
 
             assertNotNull("Object not garbage collected.", ref);
@@ -238,8 +236,7 @@
     public void test_get() {
         WeakReference ref = newWeakReference(null);
 
-        System.gc();
-        System.runFinalization();
+        FinalizationTester.induceFinalization();
         assertNull("get() doesn't return null after gc for WeakReference", ref.get());
 
         obj = new Object();
@@ -322,8 +319,7 @@
             Thread t = new TestThread();
             t.start();
             t.join();
-            System.gc();
-            System.runFinalization();
+            FinalizationTester.induceFinalization();
             Thread.sleep(1000);
             if (error != null) {
                 throw error;
diff --git a/luni/src/test/java/tests/api/java/lang/ref/SoftReferenceTest.java b/luni/src/test/java/tests/api/java/lang/ref/SoftReferenceTest.java
index 77c6536..197d829 100644
--- a/luni/src/test/java/tests/api/java/lang/ref/SoftReferenceTest.java
+++ b/luni/src/test/java/tests/api/java/lang/ref/SoftReferenceTest.java
@@ -22,6 +22,7 @@
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.util.Vector;
+import libcore.java.lang.ref.FinalizationTester;
 
 public class SoftReferenceTest extends junit.framework.TestCase {
     static Boolean bool;
@@ -124,8 +125,7 @@
             TestThread t = new TestThread();
             t.start();
             t.join();
-            System.gc();
-            System.runFinalization();
+            FinalizationTester.induceFinalization();
             ref = rq.poll();
             assertNotNull("Object not garbage collected.", ref);
             assertNull("Object is not null.", ref.get());
diff --git a/luni/src/test/java/tests/api/java/util/WeakHashMapTest.java b/luni/src/test/java/tests/api/java/util/WeakHashMapTest.java
index 0e43bf6..d1a43e5 100644
--- a/luni/src/test/java/tests/api/java/util/WeakHashMapTest.java
+++ b/luni/src/test/java/tests/api/java/util/WeakHashMapTest.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.WeakHashMap;
+import libcore.java.lang.ref.FinalizationTester;
 
 import tests.support.Support_MapTest2;
 
@@ -208,7 +209,7 @@
         do {
             System.gc();
             System.gc();
-            Runtime.getRuntime().runFinalization();
+            FinalizationTester.induceFinalization();
             count++;
         } while (count <= 5 && entrySet.size() == 100);
 
@@ -240,7 +241,8 @@
         WeakHashMap map = new WeakHashMap();
         map.put(null, "value"); // add null key
         System.gc();
-        System.runFinalization();
+        System.gc();
+        FinalizationTester.induceFinalization();
         map.remove("nothing"); // Cause objects in queue to be removed
         assertEquals("null key was removed", 1, map.size());
     }
@@ -315,7 +317,7 @@
         do {
             System.gc();
             System.gc();
-            Runtime.getRuntime().runFinalization();
+            FinalizationTester.induceFinalization();
             count++;
         } while (count <= 5 && keySet.size() == 100);
 
@@ -352,7 +354,7 @@
         do {
             System.gc();
             System.gc();
-            Runtime.getRuntime().runFinalization();
+            FinalizationTester.induceFinalization();
             count++;
         } while (count <= 5 && valuesCollection.size() == 100);
 
diff --git a/luni/src/test/java/tests/api/org/apache/harmony/kernel/dalvik/ThreadsTest.java b/luni/src/test/java/tests/api/org/apache/harmony/kernel/dalvik/ThreadsTest.java
index bdc580d..19c6229 100644
--- a/luni/src/test/java/tests/api/org/apache/harmony/kernel/dalvik/ThreadsTest.java
+++ b/luni/src/test/java/tests/api/org/apache/harmony/kernel/dalvik/ThreadsTest.java
@@ -17,7 +17,8 @@
 package tests.api.org.apache.harmony.kernel.dalvik;
 
 import java.lang.reflect.Field;
-
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
 import junit.framework.Assert;
 import junit.framework.TestCase;
 import sun.misc.Unsafe;
@@ -27,8 +28,6 @@
  */
 public class ThreadsTest extends TestCase {
     private static Unsafe UNSAFE = null;
-    private static RuntimeException INITIALIZEFAILED = null;
-
     static {
         /*
          * Set up {@link #UNSAFE}. This subverts the access check to
@@ -42,78 +41,94 @@
 
             UNSAFE = (Unsafe) field.get(null);
         } catch (NoSuchFieldException ex) {
-            INITIALIZEFAILED = new RuntimeException(ex);
+            throw new RuntimeException(ex);
         } catch (IllegalAccessException ex) {
-            INITIALIZEFAILED = new RuntimeException(ex);
+            throw new RuntimeException(ex);
         }
     }
 
     /** Test the case where the park times out. */
-    public void test_parkFor_1() {
-        Parker parker = new Parker(false, 500);
+    public void test_parkFor_1() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(2);
+        Parker parker = new Parker(barrier, false, 500);
         Thread parkerThread = new Thread(parker);
         Thread waiterThread =
-            new Thread(new WaitAndUnpark(1000, parkerThread));
+            new Thread(new WaitAndUnpark(barrier, 1000, parkerThread));
 
         parkerThread.start();
         waiterThread.start();
         parker.assertDurationIsInRange(500);
+        waiterThread.join();
+        parkerThread.join();
     }
 
     /** Test the case where the unpark happens before the timeout. */
-    public void test_parkFor_2() {
-        Parker parker = new Parker(false, 1000);
+    public void test_parkFor_2() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(2);
+        Parker parker = new Parker(barrier, false, 1000);
         Thread parkerThread = new Thread(parker);
         Thread waiterThread =
-            new Thread(new WaitAndUnpark(300, parkerThread));
+            new Thread(new WaitAndUnpark(barrier, 300, parkerThread));
 
         parkerThread.start();
         waiterThread.start();
         parker.assertDurationIsInRange(300);
+        waiterThread.join();
+        parkerThread.join();
     }
 
     /** Test the case where the thread is preemptively unparked. */
-    public void test_parkFor_3() {
-        Parker parker = new Parker(false, 1000);
+    public void test_parkFor_3() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(1);
+        Parker parker = new Parker(barrier, false, 1000);
         Thread parkerThread = new Thread(parker);
 
         UNSAFE.unpark(parkerThread);
         parkerThread.start();
         parker.assertDurationIsInRange(0);
+        parkerThread.join();
     }
 
     /** Test the case where the park times out. */
-    public void test_parkUntil_1() {
-        Parker parker = new Parker(true, 500);
+    public void test_parkUntil_1() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(2);
+        Parker parker = new Parker(barrier, true, 500);
         Thread parkerThread = new Thread(parker);
         Thread waiterThread =
-            new Thread(new WaitAndUnpark(1000, parkerThread));
+            new Thread(new WaitAndUnpark(barrier, 1000, parkerThread));
 
         parkerThread.start();
         waiterThread.start();
         parker.assertDurationIsInRange(500);
+        waiterThread.join();
+        parkerThread.join();
     }
 
     /** Test the case where the unpark happens before the timeout. */
-    public void test_parkUntil_2() {
-        Parker parker = new Parker(true, 1000);
+    public void test_parkUntil_2() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(2);
+        Parker parker = new Parker(barrier, true, 1000);
         Thread parkerThread = new Thread(parker);
         Thread waiterThread =
-            new Thread(new WaitAndUnpark(300, parkerThread));
+            new Thread(new WaitAndUnpark(barrier, 300, parkerThread));
 
         parkerThread.start();
         waiterThread.start();
         parker.assertDurationIsInRange(300);
+        waiterThread.join();
+        parkerThread.join();
     }
 
     /** Test the case where the thread is preemptively unparked. */
-    public void test_parkUntil_3() {
-        Parker parker = new Parker(true, 1000);
+    public void test_parkUntil_3() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(1);
+        Parker parker = new Parker(barrier, true, 1000);
         Thread parkerThread = new Thread(parker);
 
         UNSAFE.unpark(parkerThread);
         parkerThread.start();
         parker.assertDurationIsInRange(0);
+        parkerThread.join();
     }
 
     // TODO: Add more tests.
@@ -123,6 +138,9 @@
      * the indicated value, noting the duration of time actually parked.
      */
     private static class Parker implements Runnable {
+
+        private final CyclicBarrier barrier;
+
         /** whether {@link #amount} is milliseconds to wait in an
          * absolute fashion (<code>true</code>) or nanoseconds to wait
          * in a relative fashion (<code>false</code>) */
@@ -147,7 +165,8 @@
          * either case, this constructor takes a duration to park for
          * @param parkMillis the number of milliseconds to be parked
          */
-        public Parker(boolean absolute, long parkMillis) {
+        public Parker(CyclicBarrier barrier, boolean absolute, long parkMillis) {
+            this.barrier = barrier;
             this.absolute = absolute;
 
             // Multiply by 1000000 because parkFor() takes nanoseconds.
@@ -155,8 +174,14 @@
         }
 
         public void run() {
+            try {
+                barrier.await(60, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                throw new AssertionError(e);
+            }
             boolean absolute = this.absolute;
             long amount = this.amount;
+            long startNanos = System.nanoTime();
             long start = System.currentTimeMillis();
 
             if (absolute) {
@@ -165,11 +190,11 @@
                 UNSAFE.park(false, amount);
             }
 
-            long end = System.currentTimeMillis();
+            long endNanos = System.nanoTime();
 
             synchronized (this) {
-                startMillis = start;
-                endMillis = end;
+                startMillis = startNanos / 1000000;
+                endMillis = endNanos / 1000000;
                 completed = true;
                 notifyAll();
             }
@@ -199,7 +224,7 @@
         }
 
         /**
-         * Asserts that the actual duration is within 5% of the
+         * Asserts that the actual duration is within 10% of the
          * given expected time.
          *
          * @param expectedMillis the expected duration, in milliseconds
@@ -209,18 +234,20 @@
              * Allow a bit more slop for the maximum on "expected
              * instantaneous" results.
              */
-            long minimum = (long) ((double) expectedMillis * 0.95);
+            long minimum = (long) ((double) expectedMillis * 0.90);
             long maximum =
-                Math.max((long) ((double) expectedMillis * 1.05), 10);
+                Math.max((long) ((double) expectedMillis * 1.10), 10);
             long waitMillis = Math.max(expectedMillis * 10, 10);
             long duration = getDurationMillis(waitMillis);
 
             if (duration < minimum) {
                 Assert.fail("expected duration: " + expectedMillis +
-                        "; actual too short: " + duration);
+                            " minimum duration: " + minimum +
+                            " actual duration too short: " + duration);
             } else if (duration > maximum) {
                 Assert.fail("expected duration: " + expectedMillis +
-                        "; actual too long: " + duration);
+                            " maximum duration: " + maximum +
+                            " actual duration too long: " + duration);
             }
         }
     }
@@ -230,16 +257,23 @@
      * specified amount of time and then unparks an indicated thread.
      */
     private static class WaitAndUnpark implements Runnable {
+        private final CyclicBarrier barrier;
         private final long waitMillis;
         private final Thread thread;
 
-        public WaitAndUnpark(long waitMillis, Thread thread) {
+        public WaitAndUnpark(CyclicBarrier barrier, long waitMillis, Thread thread) {
+            this.barrier = barrier;
             this.waitMillis = waitMillis;
             this.thread = thread;
         }
 
         public void run() {
             try {
+                barrier.await(60, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                throw new AssertionError(e);
+            }
+            try {
                 Thread.sleep(waitMillis);
             } catch (InterruptedException ex) {
                 throw new RuntimeException("shouldn't happen", ex);
@@ -248,11 +282,4 @@
             UNSAFE.unpark(thread);
         }
     }
-
-    @Override
-    protected void setUp() throws Exception {
-        if (INITIALIZEFAILED != null) {
-            throw INITIALIZEFAILED;
-        }
-    }
 }
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index eb53ba5..be880d3 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -85,6 +85,13 @@
      */
     public static final Map<String,Set<String>> PROVIDER_ALGORITHMS
             = new HashMap<String,Set<String>>();
+
+    public static final Map<String,Set<String>> CIPHER_MODES
+            = new HashMap<String,Set<String>>();
+
+    public static final Map<String,Set<String>> CIPHER_PADDINGS
+            = new HashMap<String,Set<String>>();
+
     private static void provide(String type, String algorithm) {
         Set<String> algorithms = PROVIDER_ALGORITHMS.get(type);
         if (algorithms == null) {
@@ -102,6 +109,22 @@
             assertNotNull(PROVIDER_ALGORITHMS.remove(type));
         }
     }
+    private static void provideCipherModes(String algorithm, String newModes[]) {
+        Set<String> modes = CIPHER_MODES.get(algorithm);
+        if (modes == null) {
+            modes = new HashSet<String>();
+            CIPHER_MODES.put(algorithm, modes);
+        }
+        modes.addAll(Arrays.asList(newModes));
+    }
+    private static void provideCipherPaddings(String algorithm, String newPaddings[]) {
+        Set<String> paddings = CIPHER_MODES.get(algorithm);
+        if (paddings == null) {
+            paddings = new HashSet<String>();
+            CIPHER_MODES.put(algorithm, paddings);
+        }
+        paddings.addAll(Arrays.asList(newPaddings));
+    }
     static {
         provide("AlgorithmParameterGenerator", "DSA");
         provide("AlgorithmParameterGenerator", "DiffieHellman");
@@ -122,7 +145,10 @@
         provide("CertStore", "Collection");
         provide("CertStore", "LDAP");
         provide("CertificateFactory", "X.509");
+        // TODO: provideCipherModes and provideCipherPaddings for other Ciphers
         provide("Cipher", "AES");
+        provideCipherModes("AES", new String[] { "CBC", "CFB", "CTR", "CTS", "ECB", "OFB" });
+        provideCipherPaddings("AES", new String[] { "NoPadding", "PKCS5Padding" });
         provide("Cipher", "AESWrap");
         provide("Cipher", "ARCFOUR");
         provide("Cipher", "Blowfish");
@@ -322,18 +348,6 @@
             unprovide("MessageDigest", "SHA");
             provide("MessageDigest", "SHA-1");
 
-            // different names: added "Encryption" suffix
-            unprovide("Signature", "MD5withRSA");
-            provide("Signature", "MD5WithRSAEncryption");
-            unprovide("Signature", "SHA1withRSA");
-            provide("Signature", "SHA1WithRSAEncryption");
-            unprovide("Signature", "SHA256WithRSA");
-            provide("Signature", "SHA256WithRSAEncryption");
-            unprovide("Signature", "SHA384WithRSA");
-            provide("Signature", "SHA384WithRSAEncryption");
-            unprovide("Signature", "SHA512WithRSA");
-            provide("Signature", "SHA512WithRSAEncryption");
-
             // Added to support Android KeyStore operations
             provide("Signature", "NONEwithRSA");
             provide("Cipher", "RSA/ECB/NOPADDING");
@@ -451,6 +465,12 @@
 
             // Android's CA store
             provide("KeyStore", "AndroidCAStore");
+
+            // Android's KeyStore provider
+            if (Security.getProvider("AndroidKeyStoreProvider") != null) {
+                provide("KeyStore", "AndroidKeyStore");
+                provide("KeyPairGenerator", "AndroidKeyPairGenerator");
+            }
         }
     }
 
@@ -850,4 +870,18 @@
         assertValidCipherSuites(CIPHER_SUITES, cipherSuites);
         assertEquals(CIPHER_SUITES_DEFAULT, Arrays.asList(cipherSuites));
     }
+
+    /**
+     * Get all supported mode names for the given cipher.
+     */
+    public static Set<String> getModesForCipher(String cipher) {
+        return CIPHER_MODES.get(cipher);
+    }
+
+    /**
+     * Get all supported padding names for the given cipher.
+     */
+    public static Set<String> getPaddingsForCipher(String cipher) {
+        return CIPHER_PADDINGS.get(cipher);
+    }
 }
diff --git a/support/src/test/java/tests/net/StuckServer.java b/support/src/test/java/tests/net/StuckServer.java
index eababce..f7a3118 100644
--- a/support/src/test/java/tests/net/StuckServer.java
+++ b/support/src/test/java/tests/net/StuckServer.java
@@ -17,6 +17,7 @@
 package tests.net;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
@@ -26,48 +27,57 @@
  * A test ServerSocket that you can't connect to --- connects will time out.
  */
 public final class StuckServer {
+    private static final boolean DEBUG = false;
+
     private ServerSocket serverSocket;
+    private InetSocketAddress address;
     private ArrayList<Socket> clients = new ArrayList<Socket>();
 
-    public StuckServer() throws IOException {
+    public StuckServer(boolean useBacklog) throws IOException {
         // Set a backlog and use it up so that we can expect the
-        // connection to time out. According to Steven's
+        // connection to time out. According to Stevens
         // 4.5 "listen function", Linux adds 3 to the specified
         // backlog, so we need to connect 4 times before it will hang.
-        serverSocket = new ServerSocket(0, 1);
-        for (int i = 0; i < 4; i++) {
-            clients.add(new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()));
+        // The trouble with this is that it won't hang forever.
+        // After 10s or so, the kernel allows a couple more connections.
+        // This mode is ony useful if you actually want to continue eventually; we use it to
+        // test non-blocking connects, for example, where you want to test every part of the code.
+        if (useBacklog) {
+            this.serverSocket = new ServerSocket(0, 1);
+            this.address = (InetSocketAddress) serverSocket.getLocalSocketAddress();
+            if (DEBUG) {
+                System.err.println("StuckServer: " + serverSocket);
+            }
+            for (int i = 0; i < 4; ++i) {
+                Socket client = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort());
+                clients.add(client);
+                if (DEBUG) {
+                    System.err.println("StuckServer client " + i + " - " + client);
+                }
+            }
+        } else {
+            // In general, though, you don't want to rely on listen(2) backlog. http://b/6971145.
+            // RFC 5737 implies this network will be unreachable. (There are two other networks
+            // to try if we have trouble with this one.)
+            // We've had trouble with 10.* in the past (because test labs running CTS often use
+            // net 10!) but hopefully this network will be better.
+            InetAddress testNet1 = InetAddress.getByAddress(new byte[] { (byte) 192, 0, 2, 0 });
+            this.address = new InetSocketAddress(testNet1, 80);
         }
     }
 
-    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();
+        return address;
     }
 
     public int getLocalPort() {
-        return serverSocket.getLocalPort();
+        return address.getPort();
     }
 
     public void close() throws IOException {
-        serverSocket.close();
+        if (serverSocket != null) {
+            serverSocket.close();
+        }
         for (Socket client : clients) {
             client.close();
         }