am b458aaf6: am 9709ed21: am 3ec381ea: am c0033066: am 97452bf6: Merge "Test fallback SHA1PRNG uses full width seeds" into jb-dev

* commit 'b458aaf60e8279f4207307ccf582526f2b9bd159':
  Test fallback SHA1PRNG uses full width seeds
diff --git a/Android.mk b/Android.mk
index 0532402..18cf576 100644
--- a/Android.mk
+++ b/Android.mk
@@ -36,15 +36,13 @@
 include $(LOCAL_PATH)/CaCerts.mk
 
 #
-# Disable test modules if LIBCORE_SKIP_TESTS envar is set
+# Disable test modules if LIBCORE_SKIP_TESTS environment variable is set.
 #
 
 ifneq ($(LIBCORE_SKIP_TESTS),)
 $(info ********************************************************************************)
 $(info * libcore tests are skipped because environment variable LIBCORE_SKIP_TESTS=$(LIBCORE_SKIP_TESTS))
 $(info ********************************************************************************)
-ALL_MODULE_TAGS := $(filter-out tests,$(ALL_MODULE_TAGS))
-ALL_MODULES := $(filter-out $(ALL_MODULE_NAME_TAGS.tests),$(ALL_MODULES))
 endif
 
 
@@ -60,9 +58,6 @@
         $(HOST_OUT)/bin/dexopt \
         $(HOST_OUT)/lib/libjavacore.so \
         cacerts-host \
-        $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.dat \
-        $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.idx \
-        $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.version \
         core-hostdex \
         bouncycastle-hostdex \
         apache-xml-hostdex \
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 26a586a..98de1f2 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -83,6 +83,7 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := core
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
+LOCAL_REQUIRED_MODULES := tzdata
 
 include $(BUILD_JAVA_LIBRARY)
 
@@ -90,17 +91,18 @@
 
 
 # Make the core-tests library.
+ifeq ($(LIBCORE_SKIP_TESTS),)
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-test-java-files-under,dalvik dom json luni support xml)
 LOCAL_JAVA_RESOURCE_DIRS := $(test_resource_dirs)
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := bouncycastle core core-junit
-LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc mockwebserver
+LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc mockwebserver nist-pkix-tests
 LOCAL_JAVACFLAGS := $(local_javac_flags)
-LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := core-tests
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
 include $(BUILD_STATIC_JAVA_LIBRARY)
+endif
 
 # This one's tricky. One of our tests needs to have a
 # resource with a "#" in its name, but Perforce doesn't
@@ -139,22 +141,25 @@
     LOCAL_MODULE_TAGS := optional
     LOCAL_MODULE := core-hostdex
     LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
+    LOCAL_REQUIRED_MODULES := tzdata-host
 
     include $(BUILD_HOST_JAVA_LIBRARY)
 
     # Make the core-tests library.
+    ifeq ($(LIBCORE_SKIP_TESTS),)
     include $(CLEAR_VARS)
     LOCAL_SRC_FILES := $(call all-test-java-files-under,dalvik dom json luni support xml)
     LOCAL_JAVA_RESOURCE_DIRS := $(test_resource_dirs)
     LOCAL_NO_STANDARD_LIBRARIES := true
     LOCAL_JAVA_LIBRARIES := bouncycastle-hostdex core-hostdex core-junit-hostdex
-    LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc-host mockwebserver-hostdex
+    LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc-host mockwebserver-host nist-pkix-tests-host
     LOCAL_JAVACFLAGS := $(local_javac_flags)
     LOCAL_MODULE_TAGS := optional
     LOCAL_MODULE := core-tests-hostdex
     LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
     LOCAL_BUILD_HOST_DEX := true
     include $(BUILD_HOST_JAVA_LIBRARY)
+    endif
 endif
 
 #
@@ -162,19 +167,19 @@
 #
 #
 # Run with:
-#     m libcore-docs
+#     mm -j32 libcore-docs
 #
 # Main output:
-#     out/target/common/docs/libcore/reference/packages.html
+#     ../out/target/common/docs/libcore/reference/packages.html
 #
 # All text for proofreading (or running tools over):
-#     out/target/common/docs/libcore-proofread.txt
+#     ../out/target/common/docs/libcore-proofread.txt
 #
 # TODO list of missing javadoc, etc:
-#     out/target/common/docs/libcore-docs-todo.html
+#     ../out/target/common/docs/libcore-docs-todo.html
 #
 # Rerun:
-#     rm -rf out/target/common/docs/libcore-timestamp && m libcore-docs
+#     rm -rf ../out/target/common/docs/libcore-timestamp && mm -j32 libcore-docs
 #
 include $(CLEAR_VARS)
 
@@ -190,7 +195,7 @@
 LOCAL_MODULE := libcore
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk
 
-LOCAL_DROIDDOC_OPTIONS:= \
+LOCAL_DROIDDOC_OPTIONS := \
  -offlinemode \
  -title "libcore" \
  -proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
diff --git a/NativeCode.mk b/NativeCode.mk
index 1aab901..b7c8b0e 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -51,6 +51,30 @@
 # we aren't quite defining a new rule yet, to make sure that the
 # sub.mk files don't see anything stray from the last rule that was
 # set up.
+
+# Set up the test library first
+ifeq ($(LIBCORE_SKIP_TESTS),)
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(core_magic_local_target)
+core_src_files :=
+
+# Include the sub.mk files.
+$(foreach dir, \
+    luni/src/test/native, \
+    $(eval $(call include-core-native-dir,$(dir))))
+
+# This is for the test library, so rename the variable.
+test_src_files := $(core_src_files)
+core_src_files :=
+
+# Extract out the allowed LOCAL_* variables. Note: $(sort) also
+# removes duplicates.
+test_c_includes := $(sort libcore/include $(LOCAL_C_INCLUDES) $(JNI_H_INCLUDE))
+test_shared_libraries := $(sort $(LOCAL_SHARED_LIBRARIES))
+test_static_libraries := $(sort $(LOCAL_STATIC_LIBRARIES))
+endif # LIBCORE_SKIP_TESTS
+
+
 include $(CLEAR_VARS)
 LOCAL_MODULE := $(core_magic_local_target)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
@@ -96,6 +120,32 @@
 
 include $(BUILD_SHARED_LIBRARY)
 
+
+# Test library
+ifeq ($(LIBCORE_SKIP_TESTS),)
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS += -Wall -Wextra -Werror
+LOCAL_CFLAGS += $(core_cflags)
+LOCAL_CPPFLAGS += $(core_cppflags)
+ifeq ($(TARGET_ARCH),arm)
+# Ignore "note: the mangling of 'va_list' has changed in GCC 4.4"
+LOCAL_CFLAGS += -Wno-psabi
+endif
+
+# Define the rules.
+LOCAL_SRC_FILES := $(test_src_files)
+LOCAL_C_INCLUDES := $(test_c_includes)
+LOCAL_SHARED_LIBRARIES := $(test_shared_libraries)
+LOCAL_STATIC_LIBRARIES := $(test_static_libraries)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := libjavacoretests
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
+
+include $(BUILD_SHARED_LIBRARY)
+endif # LIBCORE_SKIP_TESTS
+
+
 #
 # Build for the host.
 #
@@ -114,4 +164,20 @@
     LOCAL_SHARED_LIBRARIES := $(core_shared_libraries) libexpat libicuuc libicui18n libssl libcrypto libz-host
     LOCAL_STATIC_LIBRARIES := $(core_static_libraries)
     include $(BUILD_HOST_SHARED_LIBRARY)
+
+    ifeq ($(LIBCORE_SKIP_TESTS),)
+    include $(CLEAR_VARS)
+    # Define the rules.
+    LOCAL_SRC_FILES := $(test_src_files)
+    LOCAL_CFLAGS += $(core_cflags)
+    LOCAL_C_INCLUDES := $(test_c_includes)
+    LOCAL_CPPFLAGS += $(core_cppflags)
+    LOCAL_LDLIBS += -ldl -lpthread
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE := libjavacoretests
+    LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
+    LOCAL_SHARED_LIBRARIES := $(test_shared_libraries)
+    LOCAL_STATIC_LIBRARIES := $(test_static_libraries)
+    include $(BUILD_HOST_SHARED_LIBRARY)
+    endif # LIBCORE_SKIP_TESTS
 endif
diff --git a/benchmarks/AdditionBenchmark.java b/benchmarks/AdditionBenchmark.java
new file mode 100644
index 0000000..a18856e
--- /dev/null
+++ b/benchmarks/AdditionBenchmark.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * What do various kinds of addition cost?
+ */
+public class AdditionBenchmark extends SimpleBenchmark {
+    public int timeAddConstantToLocalInt(int reps) {
+        int result = 0;
+        for (int i = 0; i < reps; ++i) {
+            result += 123;
+        }
+        return result;
+    }
+    public int timeAddTwoLocalInts(int reps) {
+        int result = 0;
+        int constant = 123;
+        for (int i = 0; i < reps; ++i) {
+            result += constant;
+        }
+        return result;
+    }
+    public long timeAddConstantToLocalLong(int reps) {
+        long result = 0;
+        for (int i = 0; i < reps; ++i) {
+            result += 123L;
+        }
+        return result;
+    }
+    public long timeAddTwoLocalLongs(int reps) {
+        long result = 0;
+        long constant = 123L;
+        for (int i = 0; i < reps; ++i) {
+            result += constant;
+        }
+        return result;
+    }
+    public float timeAddConstantToLocalFloat(int reps) {
+        float result = 0.0f;
+        for (int i = 0; i < reps; ++i) {
+            result += 123.0f;
+        }
+        return result;
+    }
+    public float timeAddTwoLocalFloats(int reps) {
+        float result = 0.0f;
+        float constant = 123.0f;
+        for (int i = 0; i < reps; ++i) {
+            result += constant;
+        }
+        return result;
+    }
+    public double timeAddConstantToLocalDouble(int reps) {
+        double result = 0.0;
+        for (int i = 0; i < reps; ++i) {
+            result += 123.0;
+        }
+        return result;
+    }
+    public double timeAddTwoLocalDoubles(int reps) {
+        double result = 0.0;
+        double constant = 123.0;
+        for (int i = 0; i < reps; ++i) {
+            result += constant;
+        }
+        return result;
+    }
+}
diff --git a/benchmarks/ArrayCopyBenchmark.java b/benchmarks/ArrayCopyBenchmark.java
new file mode 100644
index 0000000..75ad243
--- /dev/null
+++ b/benchmarks/ArrayCopyBenchmark.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Arrays;
+
+public class ArrayCopyBenchmark extends SimpleBenchmark {
+    public void timeManualArrayCopy(int reps) {
+        char[] src = new char[8192];
+        for (int rep = 0; rep < reps; ++rep) {
+            char[] dst = new char[8192];
+            for (int i = 0; i < 8192; ++i) {
+                dst[i] = src[i];
+            }
+        }
+    }
+
+    public void time_System_arrayCopy(int reps) {
+        char[] src = new char[8192];
+        for (int rep = 0; rep < reps; ++rep) {
+            char[] dst = new char[8192];
+            System.arraycopy(src, 0, dst, 0, 8192);
+        }
+    }
+
+    public void time_Arrays_copyOf(int reps) {
+        char[] src = new char[8192];
+        for (int rep = 0; rep < reps; ++rep) {
+            char[] dst = Arrays.copyOf(src, 8192);
+        }
+    }
+
+    public void time_Arrays_copyOfRange(int reps) {
+        char[] src = new char[8192];
+        for (int rep = 0; rep < reps; ++rep) {
+            char[] dst = Arrays.copyOfRange(src, 0, 8192);
+        }
+    }
+}
diff --git a/benchmarks/ArrayIterationBenchmark.java b/benchmarks/ArrayIterationBenchmark.java
new file mode 100644
index 0000000..bdc255b
--- /dev/null
+++ b/benchmarks/ArrayIterationBenchmark.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * How do various ways of iterating through an array compare?
+ */
+public class ArrayIterationBenchmark extends SimpleBenchmark {
+    Foo[] mArray = new Foo[27];
+    {
+        for (int i = 0; i < mArray.length; ++i) mArray[i] = new Foo();
+    }
+    public void timeArrayIteration(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            int sum = 0;
+            for (int i = 0; i < mArray.length; i++) {
+                sum += mArray[i].mSplat;
+            }
+        }
+    }
+    public void timeArrayIterationCached(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            int sum = 0;
+            Foo[] localArray = mArray;
+            int len = localArray.length;
+            
+            for (int i = 0; i < len; i++) {
+                sum += localArray[i].mSplat;
+            }
+        }
+    }
+    public void timeArrayIterationForEach(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            int sum = 0;
+            for (Foo a: mArray) {
+                sum += a.mSplat;
+            }
+        }
+    }
+}
diff --git a/benchmarks/ArrayListIterationBenchmark.java b/benchmarks/ArrayListIterationBenchmark.java
new file mode 100644
index 0000000..4e5f145
--- /dev/null
+++ b/benchmarks/ArrayListIterationBenchmark.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.ArrayList;
+
+/**
+ * Is a hand-coded counted loop through an ArrayList cheaper than enhanced for?
+ */
+public class ArrayListIterationBenchmark extends SimpleBenchmark {
+    ArrayList<Foo> mList = new ArrayList<Foo>();
+    {
+        for (int i = 0; i < 27; ++i) mList.add(new Foo());
+    }
+    public void timeArrayListIterationIndexed(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            int sum = 0;
+            ArrayList<Foo> list = mList;
+            int len = list.size();
+            for (int i = 0; i < len; ++i) {
+                sum += list.get(i).mSplat;
+            }
+        }
+    }
+    public void timeArrayListIterationForEach(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            int sum = 0;
+            for (Foo a : mList) {
+                sum += a.mSplat;
+            }
+        }
+    }
+}
diff --git a/benchmarks/BufferedZipFileBenchmark.java b/benchmarks/BufferedZipFileBenchmark.java
new file mode 100644
index 0000000..f4d3822
--- /dev/null
+++ b/benchmarks/BufferedZipFileBenchmark.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public final class BufferedZipFileBenchmark extends SimpleBenchmark {
+    @Param({"128", "1024", "8192", "65536"}) int compressedSize;
+    @Param({"4", "32", "128"}) int readSize;
+
+    private File file;
+
+    @Override protected void setUp() throws Exception {
+        file = File.createTempFile(getClass().getName(), ".zip");
+        file.deleteOnExit();
+
+        Random random = new Random(0);
+        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file));
+        byte[] data = new byte[8192];
+        out.putNextEntry(new ZipEntry("entry.data"));
+        int written = 0;
+        while (written < compressedSize) {
+            random.nextBytes(data);
+            int toWrite = Math.min(compressedSize - written, data.length);
+            out.write(data, 0, toWrite);
+            written += toWrite;
+        }
+        out.close();
+    }
+
+    public void timeUnbufferedRead(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            ZipFile zipFile = new ZipFile(file);
+            ZipEntry entry = zipFile.getEntry("entry.data");
+            InputStream in = zipFile.getInputStream(entry);
+            byte[] buffer = new byte[readSize];
+            while (in.read(buffer) != -1) {
+            }
+            in.close();
+            zipFile.close();
+        }
+    }
+
+    public void timeBufferedRead(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            ZipFile zipFile = new ZipFile(file);
+            ZipEntry entry = zipFile.getEntry("entry.data");
+            InputStream in = new BufferedInputStream(zipFile.getInputStream(entry));
+            byte[] buffer = new byte[readSize];
+            while (in.read(buffer) != -1) {
+            }
+            in.close();
+            zipFile.close();
+        }
+    }
+}
diff --git a/benchmarks/FieldAccessBenchmark.java b/benchmarks/FieldAccessBenchmark.java
new file mode 100644
index 0000000..19cb060
--- /dev/null
+++ b/benchmarks/FieldAccessBenchmark.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * What does field access cost?
+ */
+public class FieldAccessBenchmark extends SimpleBenchmark {
+    private static class Inner {
+        public int publicInnerIntVal;
+        protected int protectedInnerIntVal;
+        private int privateInnerIntVal;
+        int packageInnerIntVal;
+    }
+    int intVal = 42;
+    final int finalIntVal = 42;
+    static int staticIntVal = 42;
+    static final int staticFinalIntVal = 42;
+    public int timeField(int reps) {
+        int result = 0;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = intVal;
+        }
+        return result;
+    }
+    public int timeFieldFinal(int reps) {
+        int result = 0;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = finalIntVal;
+        }
+        return result;
+    }
+    public int timeFieldStatic(int reps) {
+        int result = 0;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = staticIntVal;
+        }
+        return result;
+    }
+    public int timeFieldStaticFinal(int reps) {
+        int result = 0;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = staticFinalIntVal;
+        }
+        return result;
+    }
+    public int timeFieldCached(int reps) {
+        int result = 0;
+        int cachedIntVal = this.intVal;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = cachedIntVal;
+        }
+        return result;
+    }
+    public int timeFieldPrivateInnerClassPublicField(int reps) {
+        int result = 0;
+        Inner inner = new Inner();
+        for (int rep = 0; rep < reps; ++rep) {
+            result = inner.publicInnerIntVal;
+        }
+        return result;
+    }
+    public int timeFieldPrivateInnerClassProtectedField(int reps) {
+        int result = 0;
+        Inner inner = new Inner();
+        for (int rep = 0; rep < reps; ++rep) {
+            result = inner.protectedInnerIntVal;
+        }
+        return result;
+    }
+    public int timeFieldPrivateInnerClassPrivateField(int reps) {
+        int result = 0;
+        Inner inner = new Inner();
+        for (int rep = 0; rep < reps; ++rep) {
+            result = inner.privateInnerIntVal;
+        }
+        return result;
+    }
+    public int timeFieldPrivateInnerClassPackageField(int reps) {
+        int result = 0;
+        Inner inner = new Inner();
+        for (int rep = 0; rep < reps; ++rep) {
+            result = inner.packageInnerIntVal;
+        }
+        return result;
+    }
+}
diff --git a/benchmarks/Foo.java b/benchmarks/Foo.java
new file mode 100644
index 0000000..d288dd6
--- /dev/null
+++ b/benchmarks/Foo.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+/**
+ * A trivial class used by several benchmarks.
+ */
+public class Foo {
+    int mSplat;
+}
diff --git a/benchmarks/HashedCollectionsBenchmark.java b/benchmarks/HashedCollectionsBenchmark.java
new file mode 100644
index 0000000..a826271
--- /dev/null
+++ b/benchmarks/HashedCollectionsBenchmark.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+
+/**
+ * How do the various hash maps compare?
+ */
+public class HashedCollectionsBenchmark extends SimpleBenchmark {
+    public void timeHashMapGet(int reps) {
+        HashMap<String, String> map = new HashMap<String, String>();
+        map.put("hello", "world");
+        for (int i = 0; i < reps; ++i) {
+            map.get("hello");
+        }
+    }
+    public void timeHashMapGet_Synchronized(int reps) {
+        HashMap<String, String> map = new HashMap<String, String>();
+        synchronized (map) {
+            map.put("hello", "world");
+        }
+        for (int i = 0; i < reps; ++i) {
+            synchronized (map) {
+                map.get("hello");
+            }
+        }
+    }
+    public void timeHashtableGet(int reps) {
+        Hashtable<String, String> map = new Hashtable<String, String>();
+        map.put("hello", "world");
+        for (int i = 0; i < reps; ++i) {
+            map.get("hello");
+        }
+    }
+    public void timeLinkedHashMapGet(int reps) {
+        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
+        map.put("hello", "world");
+        for (int i = 0; i < reps; ++i) {
+            map.get("hello");
+        }
+    }
+    public void timeConcurrentHashMapGet(int reps) {
+        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
+        map.put("hello", "world");
+        for (int i = 0; i < reps; ++i) {
+            map.get("hello");
+        }
+    }
+}
diff --git a/benchmarks/MethodInvocationBenchmark.java b/benchmarks/MethodInvocationBenchmark.java
new file mode 100644
index 0000000..7a5e1b6
--- /dev/null
+++ b/benchmarks/MethodInvocationBenchmark.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Compares various kinds of method invocation.
+ */
+public class MethodInvocationBenchmark extends SimpleBenchmark {
+    interface I {
+        void emptyInterface();
+    }
+
+    static class C implements I {
+        private int field;
+
+        private int getField() {
+            return field;
+        }
+
+        public int timeInternalGetter(int reps) {
+            int result = 0;
+            for (int i = 0; i < reps; ++i) {
+                result = getField();
+            }
+            return result;
+        }
+
+        public int timeInternalFieldAccess(int reps) {
+            int result = 0;
+            for (int i = 0; i < reps; ++i) {
+                result = field;
+            }
+            return result;
+        }
+
+        public static void emptyStatic() {
+        }
+
+        public void emptyVirtual() {
+        }
+
+        public void emptyInterface() {
+        }
+    }
+
+    public void timeInternalGetter(int reps) {
+        new C().timeInternalGetter(reps);
+    }
+
+    public void timeInternalFieldAccess(int reps) {
+        new C().timeInternalFieldAccess(reps);
+    }
+
+    // Test an intrinsic.
+    public int timeStringLength(int reps) {
+        int result = 0;
+        for (int i = 0; i < reps; ++i) {
+            result = "hello, world!".length();
+        }
+        return result;
+    }
+
+    public void timeEmptyStatic(int reps) {
+        C c = new C();
+        for (int i = 0; i < reps; ++i) {
+            c.emptyStatic();
+        }
+    }
+
+    public void timeEmptyVirtual(int reps) {
+        C c = new C();
+        for (int i = 0; i < reps; ++i) {
+            c.emptyVirtual();
+        }
+    }
+
+    public void timeEmptyInterface(int reps) {
+        I c = new C();
+        for (int i = 0; i < reps; ++i) {
+            c.emptyInterface();
+        }
+    }
+
+    public static class Inner {
+        private int i;
+        private void privateMethod() { ++i; }
+        protected void protectedMethod() { ++i; }
+        public void publicMethod() { ++i; }
+        void packageMethod() { ++i; }
+        final void finalPackageMethod() { ++i; }
+    }
+
+    public void timePrivateInnerPublicMethod(int reps) {
+        Inner inner = new Inner();
+        for (int i = 0; i < reps; ++i) {
+            inner.publicMethod();
+        }
+    }
+
+    public void timePrivateInnerProtectedMethod(int reps) {
+        Inner inner = new Inner();
+        for (int i = 0; i < reps; ++i) {
+            inner.protectedMethod();
+        }
+    }
+
+    public void timePrivateInnerPrivateMethod(int reps) {
+        Inner inner = new Inner();
+        for (int i = 0; i < reps; ++i) {
+            inner.privateMethod();
+        }
+    }
+
+    public void timePrivateInnerPackageMethod(int reps) {
+        Inner inner = new Inner();
+        for (int i = 0; i < reps; ++i) {
+            inner.packageMethod();
+        }
+    }
+
+    public void timePrivateInnerFinalPackageMethod(int reps) {
+        Inner inner = new Inner();
+        for (int i = 0; i < reps; ++i) {
+          inner.finalPackageMethod();
+        }
+    }
+}
diff --git a/benchmarks/MultiplicationBenchmark.java b/benchmarks/MultiplicationBenchmark.java
new file mode 100644
index 0000000..b2f945b
--- /dev/null
+++ b/benchmarks/MultiplicationBenchmark.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * How much do various kinds of multiplication cost?
+ */
+public class MultiplicationBenchmark extends SimpleBenchmark {
+    public int timeMultiplyIntByConstant10(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 10;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant8(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 8;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByVariable10(int reps) {
+        int result = 1;
+        int factor = 10;
+        for (int i = 0; i < reps; ++i) {
+            result *= factor;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByVariable8(int reps) {
+        int result = 1;
+        int factor = 8;
+        for (int i = 0; i < reps; ++i) {
+            result *= factor;
+        }
+        return result;
+    }
+}
diff --git a/benchmarks/StringIterationBenchmark.java b/benchmarks/StringIterationBenchmark.java
new file mode 100644
index 0000000..22c6ae2
--- /dev/null
+++ b/benchmarks/StringIterationBenchmark.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * How do the various schemes for iterating through a string compare?
+ */
+public class StringIterationBenchmark extends SimpleBenchmark {
+    public void timeStringIteration0(int reps) {
+        String s = "hello, world!";
+        for (int rep = 0; rep < reps; ++rep) {
+            char ch;
+            for (int i = 0; i < s.length(); ++i) {
+                ch = s.charAt(i);
+            }
+        }
+    }
+    public void timeStringIteration1(int reps) {
+        String s = "hello, world!";
+        for (int rep = 0; rep < reps; ++rep) {
+            char ch;
+            for (int i = 0, length = s.length(); i < length; ++i) {
+                ch = s.charAt(i);
+            }
+        }
+    }
+    public void timeStringIteration2(int reps) {
+        String s = "hello, world!";
+        for (int rep = 0; rep < reps; ++rep) {
+            char ch;
+            char[] chars = s.toCharArray();
+            for (int i = 0, length = chars.length; i < length; ++i) {
+                ch = chars[i];
+            }
+        }
+    }
+    public void timeStringToCharArray(int reps) {
+        String s = "hello, world!";
+        for (int rep = 0; rep < reps; ++rep) {
+            char[] chars = s.toCharArray();
+        }
+    }
+}
diff --git a/benchmarks/VirtualVersusInterfaceBenchmark.java b/benchmarks/VirtualVersusInterfaceBenchmark.java
new file mode 100644
index 0000000..f029c81
--- /dev/null
+++ b/benchmarks/VirtualVersusInterfaceBenchmark.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Is there a performance reason to "Prefer virtual over interface", as the
+ * Android documentation once claimed?
+ */
+public class VirtualVersusInterfaceBenchmark extends SimpleBenchmark {
+    public void timeMapPut(int reps) {
+        Map<String, String> map = new HashMap<String, String>();
+        for (int i = 0; i < reps; ++i) {
+            map.put("hello", "world");
+        }
+    }
+    public void timeHashMapPut(int reps) {
+        HashMap<String, String> map = new HashMap<String, String>();
+        for (int i = 0; i < reps; ++i) {
+            map.put("hello", "world");
+        }
+    }
+}
diff --git a/benchmarks/XmlParseBenchmark.java b/benchmarks/XmlParseBenchmark.java
new file mode 100644
index 0000000..b5e7b93
--- /dev/null
+++ b/benchmarks/XmlParseBenchmark.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xmlpull.v1.XmlPullParser;
+
+public class XmlParseBenchmark extends SimpleBenchmark {
+
+    @Param String xmlFile;
+    ByteArrayInputStream inputStream;
+
+    static List<String> xmlFileValues = Arrays.asList(
+            "/etc/apns-conf.xml",
+            "/etc/media_profiles.xml",
+            "/etc/permissions/features.xml"
+    );
+
+    private SAXParser saxParser;
+    private DocumentBuilder documentBuilder;
+    private Constructor<? extends XmlPullParser> kxmlConstructor;
+    private Constructor<? extends XmlPullParser> expatConstructor;
+
+    @SuppressWarnings("unchecked")
+    @Override protected void setUp() throws Exception {
+        byte[] xmlBytes = getXmlBytes();
+        inputStream = new ByteArrayInputStream(xmlBytes);
+        inputStream.mark(xmlBytes.length);
+
+        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
+        saxParser = saxParserFactory.newSAXParser();
+
+        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+        documentBuilder = builderFactory.newDocumentBuilder();
+
+        kxmlConstructor = (Constructor) Class.forName("org.kxml2.io.KXmlParser").getConstructor();
+        expatConstructor = (Constructor) Class.forName("org.apache.harmony.xml.ExpatPullParser")
+                .getConstructor();
+    }
+
+    private byte[] getXmlBytes() throws IOException {
+        FileInputStream fileIn = new FileInputStream(xmlFile);
+        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+        int count;
+        byte[] buffer = new byte[1024];
+        while ((count = fileIn.read(buffer)) != -1) {
+            bytesOut.write(buffer, 0, count);
+        }
+        fileIn.close();
+        return bytesOut.toByteArray();
+    }
+
+    public int timeSax(int reps) throws IOException, SAXException {
+        int elementCount = 0;
+        for (int i = 0; i < reps; i++) {
+            inputStream.reset();
+            ElementCounterSaxHandler elementCounterSaxHandler = new ElementCounterSaxHandler();
+            saxParser.parse(inputStream, elementCounterSaxHandler);
+            elementCount += elementCounterSaxHandler.elementCount;
+        }
+        return elementCount;
+    }
+
+    private static class ElementCounterSaxHandler extends DefaultHandler {
+        int elementCount = 0;
+        @Override public void startElement(String uri, String localName,
+                String qName, Attributes attributes) {
+            elementCount++;
+        }
+    }
+
+    public int timeDom(int reps) throws IOException, SAXException {
+        int elementCount = 0;
+        for (int i = 0; i < reps; i++) {
+            inputStream.reset();
+            Document document = documentBuilder.parse(inputStream);
+            elementCount += countDomElements(document.getDocumentElement());
+        }
+        return elementCount;
+    }
+
+    private int countDomElements(Node node) {
+        int result = 0;
+        for (; node != null; node = node.getNextSibling()) {
+            if (node.getNodeType() == Node.ELEMENT_NODE) {
+                result++;
+            }
+            result += countDomElements(node.getFirstChild());
+        }
+        return result;
+    }
+
+    public int timeExpat(int reps) throws Exception {
+        return testXmlPull(expatConstructor, reps);
+    }
+
+    public int timeKxml(int reps) throws Exception {
+        return testXmlPull(kxmlConstructor, reps);
+    }
+
+    private int testXmlPull(Constructor<? extends XmlPullParser> constructor, int reps)
+            throws Exception {
+        int elementCount = 0;
+        for (int i = 0; i < reps; i++) {
+            inputStream.reset();
+            XmlPullParser xmlPullParser = constructor.newInstance();
+            xmlPullParser.setInput(inputStream, "UTF-8");
+            int type;
+            while ((type = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG) {
+                    elementCount++;
+                }
+            }
+        }
+        return elementCount;
+    }
+
+    public static void main(String[] args) {
+        Runner.main(XmlParseBenchmark.class, args);
+    }
+}
diff --git a/benchmarks/regression/AnnotatedElementBenchmark.java b/benchmarks/regression/AnnotatedElementBenchmark.java
new file mode 100644
index 0000000..6c33968
--- /dev/null
+++ b/benchmarks/regression/AnnotatedElementBenchmark.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class AnnotatedElementBenchmark extends SimpleBenchmark {
+
+    private Class<?> type;
+    private Field field;
+    private Method method;
+
+    @Override protected void setUp() throws Exception {
+        type = Type.class;
+        field = Type.class.getField("field");
+        method = Type.class.getMethod("method", String.class);
+    }
+
+
+    // get annotations by member type and method
+
+    public void timeGetTypeAnnotations(int reps) {
+        for (int i = 0; i < reps; i++) {
+            type.getAnnotations();
+        }
+    }
+
+    public void timeGetFieldAnnotations(int reps) {
+        for (int i = 0; i < reps; i++) {
+            field.getAnnotations();
+        }
+    }
+
+    public void timeGetMethodAnnotations(int reps) {
+        for (int i = 0; i < reps; i++) {
+            method.getAnnotations();
+        }
+    }
+
+    public void timeGetParameterAnnotations(int reps) {
+        for (int i = 0; i < reps; i++) {
+            method.getParameterAnnotations();
+        }
+    }
+
+    public void timeGetTypeAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            type.getAnnotation(Marker.class);
+        }
+    }
+
+    public void timeGetFieldAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            field.getAnnotation(Marker.class);
+        }
+    }
+
+    public void timeGetMethodAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            method.getAnnotation(Marker.class);
+        }
+    }
+
+    public void timeIsTypeAnnotationPresent(int reps) {
+        for (int i = 0; i < reps; i++) {
+            type.isAnnotationPresent(Marker.class);
+        }
+    }
+
+    public void timeIsFieldAnnotationPresent(int reps) {
+        for (int i = 0; i < reps; i++) {
+            field.isAnnotationPresent(Marker.class);
+        }
+    }
+
+    public void timeIsMethodAnnotationPresent(int reps) {
+        for (int i = 0; i < reps; i++) {
+            method.isAnnotationPresent(Marker.class);
+        }
+    }
+
+    // get annotations by result size
+
+    public void timeGetAllReturnsLargeAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            HasLargeAnnotation.class.getAnnotations();
+        }
+    }
+
+    public void timeGetAllReturnsSmallAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            HasSmallAnnotation.class.getAnnotations();
+        }
+    }
+
+    public void timeGetAllReturnsMarkerAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            HasMarkerAnnotation.class.getAnnotations();
+        }
+    }
+
+    public void timeGetAllReturnsNoAnnotation(int reps) {
+        for (int i = 0; i < reps; i++) {
+            HasNoAnnotations.class.getAnnotations();
+        }
+    }
+
+    public void timeGetAllReturnsThreeAnnotations(int reps) {
+        for (int i = 0; i < reps; i++) {
+            HasThreeAnnotations.class.getAnnotations();
+        }
+    }
+
+
+    // get annotations with inheritance
+
+    public void timeGetAnnotationsOnSubclass(int reps) {
+        for (int i = 0; i < reps; i++) {
+            ExtendsHasThreeAnnotations.class.getAnnotations();
+        }
+    }
+
+    public void timeGetDeclaredAnnotationsOnSubclass(int reps) {
+        for (int i = 0; i < reps; i++) {
+            ExtendsHasThreeAnnotations.class.getAnnotations();
+        }
+    }
+
+
+    // the annotated elements
+
+    @Marker
+    public class Type {
+        @Marker public String field;
+        @Marker public void method(@Marker String parameter) {}
+    }
+
+    @Large(a = "on class", b = {"A", "B", "C" },
+            c = @Small(e="E1", f=1695938256, g=7264081114510713000L),
+            d = { @Small(e="E2", f=1695938256, g=7264081114510713000L) })
+    public class HasLargeAnnotation {}
+
+    @Small(e="E1", f=1695938256, g=7264081114510713000L)
+    public class HasSmallAnnotation {}
+
+    @Marker
+    public class HasMarkerAnnotation {}
+
+    public class HasNoAnnotations {}
+
+    @Large(a = "on class", b = {"A", "B", "C" },
+            c = @Small(e="E1", f=1695938256, g=7264081114510713000L),
+            d = { @Small(e="E2", f=1695938256, g=7264081114510713000L) })
+    @Small(e="E1", f=1695938256, g=7264081114510713000L)
+    @Marker
+    public class HasThreeAnnotations {}
+
+    public class ExtendsHasThreeAnnotations {}
+
+
+    // the annotations
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Marker {}
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Large {
+        String a() default "";
+        String[] b() default {};
+        Small c() default @Small;
+        Small[] d() default {};
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Small {
+        String e() default "";
+        int f() default 0;
+        long g() default 0L;
+    }
+
+    public static void main(String[] args) throws Exception {
+        Runner.main(AnnotatedElementBenchmark.class, args);
+    }
+}
diff --git a/benchmarks/regression/BigIntegerBenchmark.java b/benchmarks/regression/BigIntegerBenchmark.java
new file mode 100644
index 0000000..841b901
--- /dev/null
+++ b/benchmarks/regression/BigIntegerBenchmark.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.math.BigInteger;
+import java.util.Random;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class BigIntegerBenchmark extends SimpleBenchmark {
+    public void timeRandomDivision(int reps) throws Exception {
+        Random r = new Random();
+        BigInteger x = new BigInteger(1024, r);
+        BigInteger y = new BigInteger(1024, r);
+        for (int i = 0; i < reps; ++i) {
+            x.divide(y);
+        }
+    }
+
+    public void timeRandomGcd(int reps) throws Exception {
+        Random r = new Random();
+        BigInteger x = new BigInteger(1024, r);
+        BigInteger y = new BigInteger(1024, r);
+        for (int i = 0; i < reps; ++i) {
+            x.gcd(y);
+        }
+    }
+
+    public void timeRandomMultiplication(int reps) throws Exception {
+        Random r = new Random();
+        BigInteger x = new BigInteger(1024, r);
+        BigInteger y = new BigInteger(1024, r);
+        for (int i = 0; i < reps; ++i) {
+            x.multiply(y);
+        }
+    }
+}
diff --git a/benchmarks/regression/BitSetBenchmark.java b/benchmarks/regression/BitSetBenchmark.java
new file mode 100644
index 0000000..ee91993
--- /dev/null
+++ b/benchmarks/regression/BitSetBenchmark.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.util.BitSet;
+
+public class BitSetBenchmark extends SimpleBenchmark {
+    @Param({ "1000", "10000" })
+    private int size;
+
+    private BitSet bs;
+
+    @Override protected void setUp() throws Exception {
+        bs = new BitSet(size);
+    }
+
+    public void timeIsEmptyTrue(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            if (!bs.isEmpty()) throw new RuntimeException();
+        }
+    }
+
+    public void timeIsEmptyFalse(int reps) {
+        bs.set(bs.size() - 1);
+        for (int i = 0; i < reps; ++i) {
+            if (bs.isEmpty()) throw new RuntimeException();
+        }
+    }
+
+    public void timeGet(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            bs.get(i % size);
+        }
+    }
+
+    public void timeClear(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            bs.clear(i % size);
+        }
+    }
+
+    public void timeSet(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            bs.set(i % size);
+        }
+    }
+
+    public void timeSetOn(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            bs.set(i % size, true);
+        }
+    }
+
+    public void timeSetOff(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            bs.set(i % size, false);
+        }
+    }
+}
diff --git a/benchmarks/regression/ByteBufferBenchmark.java b/benchmarks/regression/ByteBufferBenchmark.java
new file mode 100644
index 0000000..7812013
--- /dev/null
+++ b/benchmarks/regression/ByteBufferBenchmark.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class ByteBufferBenchmark extends SimpleBenchmark {
+    public enum MyByteOrder {
+        BIG(ByteOrder.BIG_ENDIAN), LITTLE(ByteOrder.LITTLE_ENDIAN);
+        final ByteOrder byteOrder;
+        MyByteOrder(ByteOrder byteOrder) {
+            this.byteOrder = byteOrder;
+        }
+    }
+
+    @Param private MyByteOrder byteOrder;
+
+    @Param({"true", "false"}) private boolean aligned;
+
+    enum MyBufferType {
+        DIRECT, HEAP, MAPPED;
+    }
+    @Param private MyBufferType bufferType;
+
+    public static ByteBuffer newBuffer(MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws IOException {
+        int size = aligned ? 8192 : 8192 + 8 + 1;
+        ByteBuffer result = null;
+        switch (bufferType) {
+        case DIRECT:
+            result = ByteBuffer.allocateDirect(size);
+            break;
+        case HEAP:
+            result = ByteBuffer.allocate(size);
+            break;
+        case MAPPED:
+            File tmpFile = new File("/sdcard/bm.tmp");
+            if (new File("/tmp").isDirectory()) {
+                // We're running on the desktop.
+                tmpFile = File.createTempFile("MappedByteBufferTest", ".tmp");
+            }
+            tmpFile.createNewFile();
+            tmpFile.deleteOnExit();
+            RandomAccessFile raf = new RandomAccessFile(tmpFile, "rw");
+            raf.setLength(8192*8);
+            FileChannel fc = raf.getChannel();
+            result = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
+            break;
+        }
+        result.order(byteOrder.byteOrder);
+        result.position(aligned ? 0 : 1);
+        return result;
+    }
+
+    //
+    // peeking
+    //
+
+    public void timeByteBuffer_getByte(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.get();
+            }
+        }
+    }
+
+    public void timeByteBuffer_getByteArray(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        byte[] dst = new byte[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(aligned ? 0 : 1);
+                src.get(dst);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getByte_indexed(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.get(i);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getChar(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getChar();
+            }
+        }
+    }
+
+    public void timeCharBuffer_getCharArray(int reps) throws Exception {
+        CharBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
+        char[] dst = new char[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(0);
+                src.get(dst);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getChar_indexed(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getChar(i * 2);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getDouble(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getDouble();
+            }
+        }
+    }
+
+    public void timeDoubleBuffer_getDoubleArray(int reps) throws Exception {
+        DoubleBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
+        double[] dst = new double[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(0);
+                src.get(dst);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getFloat(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getFloat();
+            }
+        }
+    }
+
+    public void timeFloatBuffer_getFloatArray(int reps) throws Exception {
+        FloatBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
+        float[] dst = new float[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(0);
+                src.get(dst);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getInt(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getInt();
+            }
+        }
+    }
+
+    public void timeIntBuffer_getIntArray(int reps) throws Exception {
+        IntBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
+        int[] dst = new int[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(0);
+                src.get(dst);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getLong(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getLong();
+            }
+        }
+    }
+
+    public void timeLongBuffer_getLongArray(int reps) throws Exception {
+        LongBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
+        long[] dst = new long[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(0);
+                src.get(dst);
+            }
+        }
+    }
+
+    public void timeByteBuffer_getShort(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.getShort();
+            }
+        }
+    }
+
+    public void timeShortBuffer_getShortArray(int reps) throws Exception {
+        ShortBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
+        short[] dst = new short[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                src.position(0);
+                src.get(dst);
+            }
+        }
+    }
+
+    //
+    // poking
+    //
+
+    public void timeByteBuffer_putByte(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(0);
+            for (int i = 0; i < 1024; ++i) {
+                src.put((byte) 0);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putByteArray(int reps) throws Exception {
+        ByteBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        byte[] src = new byte[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(aligned ? 0 : 1);
+                dst.put(src);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putChar(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.putChar(' ');
+            }
+        }
+    }
+
+    public void timeCharBuffer_putCharArray(int reps) throws Exception {
+        CharBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
+        char[] src = new char[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(0);
+                dst.put(src);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putDouble(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.putDouble(0.0);
+            }
+        }
+    }
+
+    public void timeDoubleBuffer_putDoubleArray(int reps) throws Exception {
+        DoubleBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
+        double[] src = new double[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(0);
+                dst.put(src);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putFloat(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.putFloat(0.0f);
+            }
+        }
+    }
+
+    public void timeFloatBuffer_putFloatArray(int reps) throws Exception {
+        FloatBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
+        float[] src = new float[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(0);
+                dst.put(src);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putInt(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.putInt(0);
+            }
+        }
+    }
+
+    public void timeIntBuffer_putIntArray(int reps) throws Exception {
+        IntBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
+        int[] src = new int[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(0);
+                dst.put(src);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putLong(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.putLong(0L);
+            }
+        }
+    }
+
+    public void timeLongBuffer_putLongArray(int reps) throws Exception {
+        LongBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
+        long[] src = new long[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(0);
+                dst.put(src);
+            }
+        }
+    }
+
+    public void timeByteBuffer_putShort(int reps) throws Exception {
+        ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+        for (int rep = 0; rep < reps; ++rep) {
+            src.position(aligned ? 0 : 1);
+            for (int i = 0; i < 1024; ++i) {
+                src.putShort((short) 0);
+            }
+        }
+    }
+
+    public void timeShortBuffer_putShortArray(int reps) throws Exception {
+        ShortBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
+        short[] src = new short[1024];
+        for (int rep = 0; rep < reps; ++rep) {
+            for (int i = 0; i < 1024; ++i) {
+                dst.position(0);
+                dst.put(src);
+            }
+        }
+    }
+
+/*
+    public void time_new_byteArray(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            byte[] bs = new byte[8192];
+        }
+    }
+
+    public void time_ByteBuffer_allocate(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            ByteBuffer bs = ByteBuffer.allocate(8192);
+        }
+    }
+    */
+}
diff --git a/benchmarks/regression/ByteBufferScalarVersusVectorBenchmark.java b/benchmarks/regression/ByteBufferScalarVersusVectorBenchmark.java
new file mode 100644
index 0000000..7c75deb
--- /dev/null
+++ b/benchmarks/regression/ByteBufferScalarVersusVectorBenchmark.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class ByteBufferScalarVersusVectorBenchmark extends SimpleBenchmark {
+  @Param private ByteBufferBenchmark.MyByteOrder byteOrder;
+  @Param({"true", "false"}) private boolean aligned;
+  @Param private ByteBufferBenchmark.MyBufferType bufferType;
+
+  public void timeManualByteBufferCopy(int reps) throws Exception {
+    ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+    ByteBuffer dst = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
+    for (int rep = 0; rep < reps; ++rep) {
+      src.position(0);
+      dst.position(0);
+      for (int i = 0; i < 8192; ++i) {
+        dst.put(src.get());
+      }
+    }
+  }
+
+  public void timeByteBufferBulkGet(int reps) throws Exception {
+    ByteBuffer src = ByteBuffer.allocate(aligned ? 8192 : 8192 + 1);
+    byte[] dst = new byte[8192];
+    for (int rep = 0; rep < reps; ++rep) {
+      src.position(aligned ? 0 : 1);
+      src.get(dst, 0, dst.length);
+    }
+  }
+
+  public void timeDirectByteBufferBulkGet(int reps) throws Exception {
+    ByteBuffer src = ByteBuffer.allocateDirect(aligned ? 8192 : 8192 + 1);
+    byte[] dst = new byte[8192];
+    for (int rep = 0; rep < reps; ++rep) {
+      src.position(aligned ? 0 : 1);
+      src.get(dst, 0, dst.length);
+    }
+  }
+}
diff --git a/benchmarks/regression/CharacterBenchmark.java b/benchmarks/regression/CharacterBenchmark.java
new file mode 100644
index 0000000..953513b
--- /dev/null
+++ b/benchmarks/regression/CharacterBenchmark.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Tests various Character methods, intended for testing multiple
+ * implementations against each other.
+ */
+public class CharacterBenchmark extends SimpleBenchmark {
+
+    @Param private CharacterSet characterSet;
+
+    @Param private Overload overload;
+
+    private char[] chars;
+
+    @Override protected void setUp() throws Exception {
+        this.chars = characterSet.chars;
+    }
+
+    public enum Overload { CHAR, INT }
+
+    @Override public double nanosToUnits(double nanos) {
+        return nanos / 65536;
+    }
+
+    public enum CharacterSet {
+        ASCII(128),
+        UNICODE(65536);
+        final char[] chars;
+        CharacterSet(int size) {
+            this.chars = new char[65536];
+            for (int i = 0; i < 65536; ++i) {
+                chars[i] = (char) (i % size);
+            }
+        }
+    }
+
+    // A fake benchmark to give us a baseline.
+    public boolean timeIsSpace(int reps) {
+        boolean dummy = false;
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    dummy ^= ((char) ch == ' ');
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    dummy ^= (ch == ' ');
+                }
+            }
+        }
+        return dummy;
+    }
+
+    public void timeDigit(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.digit(chars[ch], 10);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.digit((int) chars[ch], 10);
+                }
+            }
+        }
+    }
+
+    public void timeGetNumericValue(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.getNumericValue(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.getNumericValue((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsDigit(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isDigit(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isDigit((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsIdentifierIgnorable(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isIdentifierIgnorable(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isIdentifierIgnorable((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsJavaIdentifierPart(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierPart(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierPart((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsJavaIdentifierStart(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierStart(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isJavaIdentifierStart((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsLetter(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetter(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetter((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsLetterOrDigit(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetterOrDigit(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLetterOrDigit((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsLowerCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLowerCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isLowerCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsSpaceChar(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isSpaceChar(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isSpaceChar((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsUpperCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isUpperCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isUpperCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeIsWhitespace(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isWhitespace(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.isWhitespace((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeToLowerCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toLowerCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toLowerCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+
+    public void timeToUpperCase(int reps) {
+        if (overload == Overload.CHAR) {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toUpperCase(chars[ch]);
+                }
+            }
+        } else {
+            for (int i = 0; i < reps; ++i) {
+                for (int ch = 0; ch < 65536; ++ch) {
+                    Character.toUpperCase((int) chars[ch]);
+                }
+            }
+        }
+    }
+}
diff --git a/benchmarks/regression/CharsetBenchmark.java b/benchmarks/regression/CharsetBenchmark.java
new file mode 100644
index 0000000..6ecada7
--- /dev/null
+++ b/benchmarks/regression/CharsetBenchmark.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.nio.charset.Charset;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class CharsetBenchmark extends SimpleBenchmark {
+    @Param({ "1", "10", "100", "1000", "10000" })
+    private int length;
+
+    // canonical    => canonical charset name
+    // built-in     => guaranteed-present charset
+    // special-case => libcore treats this charset specially for performance
+    @Param({
+        "UTF-16",     //     canonical,     built-in, non-special-case
+        "UTF-8",      //     canonical,     built-in,     special-case
+        "UTF8",       // non-canonical,     built-in,     special-case
+        "ISO-8859-1", //     canonical,     built-in,     special-case
+        "8859_1",     // non-canonical,     built-in,     special-case
+        "ISO-8859-2", //     canonical, non-built-in, non-special-case
+        "8859_2",     // non-canonical, non-built-in, non-special-case
+        "US-ASCII",   //     canonical,     built-in,     special-case
+        "ASCII"       // non-canonical,     built-in,     special-case
+    })
+    private String name;
+
+    public void time_new_String_BString(int reps) throws Exception {
+        byte[] bytes = makeBytes(makeString(length));
+        for (int i = 0; i < reps; ++i) {
+            new String(bytes, name);
+        }
+    }
+
+    public void time_new_String_BII(int reps) throws Exception {
+      byte[] bytes = makeBytes(makeString(length));
+      for (int i = 0; i < reps; ++i) {
+        new String(bytes, 0, bytes.length);
+      }
+    }
+
+    public void time_new_String_BIIString(int reps) throws Exception {
+      byte[] bytes = makeBytes(makeString(length));
+      for (int i = 0; i < reps; ++i) {
+        new String(bytes, 0, bytes.length, name);
+      }
+    }
+
+    public void time_String_getBytes(int reps) throws Exception {
+        String string = makeString(length);
+        for (int i = 0; i < reps; ++i) {
+            string.getBytes(name);
+        }
+    }
+
+    // FIXME: benchmark this pure-java implementation for US-ASCII and ISO-8859-1 too!
+
+    /**
+     * Translates the given characters to US-ASCII or ISO-8859-1 bytes, using the fact that
+     * Unicode code points between U+0000 and U+007f inclusive are identical to US-ASCII, while
+     * U+0000 to U+00ff inclusive are identical to ISO-8859-1.
+     */
+    private static byte[] toDirectMappedBytes(char[] chars, int offset, int length, int maxValidChar) {
+        byte[] result = new byte[length];
+        int o = offset;
+        for (int i = 0; i < length; ++i) {
+            int ch = chars[o++];
+            result[i] = (byte) ((ch <= maxValidChar) ? ch : '?');
+        }
+        return result;
+    }
+
+    private static byte[] toUtf8Bytes(char[] chars, int offset, int length) {
+        UnsafeByteSequence result = new UnsafeByteSequence(length);
+        toUtf8Bytes(chars, offset, length, result);
+        return result.toByteArray();
+    }
+
+    private static void toUtf8Bytes(char[] chars, int offset, int length, UnsafeByteSequence out) {
+        final int end = offset + length;
+        for (int i = offset; i < end; ++i) {
+            int ch = chars[i];
+            if (ch < 0x80) {
+                // One byte.
+                out.write(ch);
+            } else if (ch < 0x800) {
+                // Two bytes.
+                out.write((ch >> 6) | 0xc0);
+                out.write((ch & 0x3f) | 0x80);
+            } else if (ch >= Character.MIN_SURROGATE && ch <= Character.MAX_SURROGATE) {
+                // A supplementary character.
+                char high = (char) ch;
+                char low = (i + 1 != end) ? chars[i + 1] : '\u0000';
+                if (!Character.isSurrogatePair(high, low)) {
+                    out.write('?');
+                    continue;
+                }
+                // Now we know we have a *valid* surrogate pair, we can consume the low surrogate.
+                ++i;
+                ch = Character.toCodePoint(high, low);
+                // Four bytes.
+                out.write((ch >> 18) | 0xf0);
+                out.write(((ch >> 12) & 0x3f) | 0x80);
+                out.write(((ch >> 6) & 0x3f) | 0x80);
+                out.write((ch & 0x3f) | 0x80);
+            } else {
+                // Three bytes.
+                out.write((ch >> 12) | 0xe0);
+                out.write(((ch >> 6) & 0x3f) | 0x80);
+                out.write((ch & 0x3f) | 0x80);
+            }
+        }
+    }
+
+    private static String makeString(int length) {
+        StringBuilder result = new StringBuilder(length);
+        for (int i = 0; i < length; ++i) {
+            result.append('A' + (i % 26));
+        }
+        return result.toString();
+    }
+
+    private static byte[] makeBytes(String s) {
+        try {
+            return s.getBytes("US-ASCII");
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
diff --git a/benchmarks/regression/CharsetForNameBenchmark.java b/benchmarks/regression/CharsetForNameBenchmark.java
new file mode 100644
index 0000000..87dfb8b
--- /dev/null
+++ b/benchmarks/regression/CharsetForNameBenchmark.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.nio.charset.Charset;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class CharsetForNameBenchmark extends SimpleBenchmark {
+    // canonical    => canonical charset name
+    // built-in     => guaranteed-present charset
+    // special-case => libcore treats this charset specially for performance
+    @Param({
+        "UTF-16",     //     canonical,     built-in, non-special-case
+        "UTF-8",      //     canonical,     built-in,     special-case
+        "UTF8",       // non-canonical,     built-in,     special-case
+        "ISO-8859-1", //     canonical,     built-in,     special-case
+        "8859_1",     // non-canonical,     built-in,     special-case
+        "ISO-8859-2", //     canonical, non-built-in, non-special-case
+        "8859_2",     // non-canonical, non-built-in, non-special-case
+        "US-ASCII",   //     canonical,     built-in,     special-case
+        "ASCII"       // non-canonical,     built-in,     special-case
+    })
+    private String charsetName;
+
+    public void timeCharsetForName(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Charset.forName(charsetName);
+        }
+    }
+}
diff --git a/benchmarks/regression/ChecksumBenchmark.java b/benchmarks/regression/ChecksumBenchmark.java
new file mode 100644
index 0000000..0e7bf8d
--- /dev/null
+++ b/benchmarks/regression/ChecksumBenchmark.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.zip.Adler32;
+import java.util.zip.CRC32;
+
+public class ChecksumBenchmark extends SimpleBenchmark {
+    public void timeAdler_block(int reps) throws Exception {
+        byte[] bytes = new byte[10000];
+        Adler32 adler = new Adler32();
+        for (int i = 0; i < reps; ++i) {
+            adler.update(bytes);
+        }
+    }
+    public void timeAdler_byte(int reps) throws Exception {
+        Adler32 adler = new Adler32();
+        for (int i = 0; i < reps; ++i) {
+            adler.update(1);
+        }
+    }
+    public void timeCrc_block(int reps) throws Exception {
+        byte[] bytes = new byte[10000];
+        CRC32 crc = new CRC32();
+        for (int i = 0; i < reps; ++i) {
+            crc.update(bytes);
+        }
+    }
+    public void timeCrc_byte(int reps) throws Exception {
+        CRC32 crc = new CRC32();
+        for (int i = 0; i < reps; ++i) {
+            crc.update(1);
+        }
+    }
+}
diff --git a/benchmarks/regression/CipherBenchmark.java b/benchmarks/regression/CipherBenchmark.java
new file mode 100644
index 0000000..56d549b
--- /dev/null
+++ b/benchmarks/regression/CipherBenchmark.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Cipher benchmarks. Only runs on AES currently because of the combinatorial
+ * explosion of the test as it stands.
+ */
+public class CipherBenchmark extends SimpleBenchmark {
+
+    private static final int DATA_SIZE = 8192;
+    private static final byte[] DATA = new byte[DATA_SIZE];
+
+    private static final int IV_SIZE = 16;
+
+    private static final byte[] IV = new byte[IV_SIZE];
+
+    static {
+        for (int i = 0; i < DATA_SIZE; i++) {
+            DATA[i] = (byte) i;
+        }
+        for (int i = 0; i < IV_SIZE; i++) {
+            IV[i] = (byte) i;
+        }
+    }
+
+    @Param private Algorithm algorithm;
+
+    public enum Algorithm {
+        AES,
+    };
+
+    @Param private Mode mode;
+
+    public enum Mode {
+        CBC,
+        CFB,
+        CTR,
+        ECB,
+        OFB,
+    };
+
+    @Param private Padding padding;
+
+    public enum Padding {
+        NOPADDING,
+        PKCS1PADDING,
+    };
+
+    @Param({"128", "192", "256"}) private int keySize;
+
+    @Param({"16", "32", "64", "128", "1024", "8192"}) private int inputSize;
+
+    @Param private Implementation implementation;
+
+    public enum Implementation { OpenSSL, BouncyCastle };
+
+    private String providerName;
+
+    // Key generation isn't part of the benchmark so cache the results
+    private static Map<Integer, SecretKey> KEY_SIZES = new HashMap<Integer, SecretKey>();
+
+    private String cipherAlgorithm;
+    private SecretKey key;
+
+    private byte[] output = new byte[DATA.length];
+
+    private Cipher cipherEncrypt;
+
+    private Cipher cipherDecrypt;
+
+    private AlgorithmParameterSpec spec;
+
+    @Override protected void setUp() throws Exception {
+        cipherAlgorithm = algorithm.toString() + "/" + mode.toString() + "/"
+                + padding.toString();
+
+        String keyAlgorithm = algorithm.toString();
+        key = KEY_SIZES.get(keySize);
+        if (key == null) {
+            KeyGenerator generator = KeyGenerator.getInstance(keyAlgorithm);
+            generator.init(keySize);
+            key = generator.generateKey();
+            KEY_SIZES.put(keySize, key);
+        }
+
+        switch (implementation) {
+            case OpenSSL:
+                providerName = "AndroidOpenSSL";
+                break;
+            case BouncyCastle:
+                providerName = "BC";
+                break;
+            default:
+                throw new RuntimeException(implementation.toString());
+        }
+
+        if (mode != Mode.ECB) {
+            spec = new IvParameterSpec(IV);
+        }
+
+        cipherEncrypt = Cipher.getInstance(cipherAlgorithm, providerName);
+        cipherEncrypt.init(Cipher.ENCRYPT_MODE, key, spec);
+
+        cipherDecrypt = Cipher.getInstance(cipherAlgorithm, providerName);
+        cipherDecrypt.init(Cipher.DECRYPT_MODE, key, spec);
+    }
+
+    public void timeEncrypt(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            cipherEncrypt.doFinal(DATA, 0, inputSize, output);
+        }
+    }
+
+    public void timeDecrypt(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            cipherDecrypt.doFinal(DATA, 0, inputSize, output);
+        }
+    }
+}
diff --git a/benchmarks/regression/DateToStringBenchmark.java b/benchmarks/regression/DateToStringBenchmark.java
new file mode 100644
index 0000000..7fee61c
--- /dev/null
+++ b/benchmarks/regression/DateToStringBenchmark.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import android.text.format.DateFormat;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+public final class DateToStringBenchmark extends SimpleBenchmark {
+    Date date;
+    Calendar calendar;
+    SimpleDateFormat format;
+
+    @Override
+    protected void setUp() throws Exception {
+        date = new Date(0);
+        calendar = new GregorianCalendar();
+        calendar.setTime(date);
+        format = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
+    }
+
+    public void timeDateToString(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            date.toString();
+        }
+    }
+
+    public void timeDateToString_Formatter(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").format(date);
+        }
+    }
+
+    public void timeDateToString_ClonedFormatter(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            ((SimpleDateFormat) format.clone()).format(date);
+        }
+    }
+
+    public void timeDateToString_AndroidDateFormat(int reps) {
+        for (int i = 0; i < reps; i++) {
+            DateFormat.format("EEE MMM dd HH:mm:ss zzz yyyy", calendar);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        Runner.main(DateToStringBenchmark.class, args);
+    }
+}
diff --git a/benchmarks/regression/DefaultCharsetBenchmark.java b/benchmarks/regression/DefaultCharsetBenchmark.java
new file mode 100644
index 0000000..bc7b013
--- /dev/null
+++ b/benchmarks/regression/DefaultCharsetBenchmark.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.nio.charset.Charset;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class DefaultCharsetBenchmark extends SimpleBenchmark {
+    public void time_defaultCharset(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Charset.defaultCharset();
+        }
+    }
+}
diff --git a/benchmarks/regression/DigestBenchmark.java b/benchmarks/regression/DigestBenchmark.java
new file mode 100644
index 0000000..7d00fec
--- /dev/null
+++ b/benchmarks/regression/DigestBenchmark.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import com.android.org.bouncycastle.crypto.Digest;
+
+public class DigestBenchmark extends SimpleBenchmark {
+
+    private static final int DATA_SIZE = 8192;
+    private static final byte[] DATA = new byte[DATA_SIZE];
+    static {
+        for (int i = 0; i < DATA_SIZE; i++) {
+            DATA[i] = (byte)i;
+        }
+    }
+
+    @Param private Algorithm algorithm;
+
+    public enum Algorithm { MD5, SHA1, SHA256,  SHA384, SHA512 };
+
+    @Param private Implementation implementation;
+
+    public enum Implementation { OPENSSL, BOUNCYCASTLE };
+
+    private Class<? extends Digest> digestClass;
+
+    @Override protected void setUp() throws Exception {
+        String className = "com.android.org.bouncycastle.crypto.digests.";
+        switch (implementation) {
+            case OPENSSL:
+                className += ("OpenSSLDigest$" + algorithm);
+                break;
+            case BOUNCYCASTLE:
+                className += (algorithm + "Digest");
+                break;
+            default:
+                throw new RuntimeException(implementation.toString());
+        }
+        this.digestClass = (Class<? extends Digest>)Class.forName(className);
+    }
+
+    public void time(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Digest digest = digestClass.newInstance();
+            digest.update(DATA, 0, DATA_SIZE);
+            digest.doFinal(new byte[digest.getDigestSize()], 0);
+        }
+    }
+}
diff --git a/benchmarks/regression/DnsBenchmark.java b/benchmarks/regression/DnsBenchmark.java
new file mode 100644
index 0000000..2a71716
--- /dev/null
+++ b/benchmarks/regression/DnsBenchmark.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class DnsBenchmark extends SimpleBenchmark {
+    public void timeDns(int reps) throws Exception {
+        String[] hosts = new String[] {
+            "www.amazon.com",
+            "z-ecx.images-amazon.com",
+            "g-ecx.images-amazon.com",
+            "ecx.images-amazon.com",
+            "ad.doubleclick.com",
+            "bpx.a9.com",
+            "d3dtik4dz1nej0.cloudfront.net",
+            "uac.advertising.com",
+            "servedby.advertising.com",
+            "view.atdmt.com",
+            "rmd.atdmt.com",
+            "spe.atdmt.com",
+            "www.google.com",
+            "www.cnn.com",
+            "bad.host.mtv.corp.google.com",
+        };
+        for (int i = 0; i < reps; ++i) {
+            try {
+                InetAddress.getByName(hosts[i % hosts.length]);
+            } catch (UnknownHostException ex) {
+            }
+        }
+    }
+}
diff --git a/benchmarks/regression/DoPrivilegedBenchmark.java b/benchmarks/regression/DoPrivilegedBenchmark.java
new file mode 100644
index 0000000..effb284
--- /dev/null
+++ b/benchmarks/regression/DoPrivilegedBenchmark.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+public class DoPrivilegedBenchmark extends SimpleBenchmark {
+    public void timeDirect(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            String lineSeparator = System.getProperty("line.separator");
+        }
+    }
+    
+    public void timeFastAndSlow(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            String lineSeparator;
+            if (System.getSecurityManager() == null) {
+                lineSeparator = System.getProperty("line.separator");
+            } else {
+                lineSeparator = AccessController.doPrivileged(new PrivilegedAction<String>() {
+                    public String run() {
+                        return System.getProperty("line.separator");
+                    }
+                });
+            }
+        }
+    }
+    
+    public void timeNewAction(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            String lineSeparator = AccessController.doPrivileged(new PrivilegedAction<String>() {
+                public String run() {
+                    return System.getProperty("line.separator");
+                }
+            });
+        }
+    }
+    
+    public void timeReusedAction(int reps) throws Exception {
+        final PrivilegedAction<String> action = new ReusableAction("line.separator");
+        for (int i = 0; i < reps; ++i) {
+            String lineSeparator = AccessController.doPrivileged(action);
+        }
+    }
+    
+    private static final class ReusableAction implements PrivilegedAction<String> {
+        private final String propertyName;
+        
+        public ReusableAction(String propertyName) {
+            this.propertyName = propertyName;
+        }
+        
+        public String run() {
+            return System.getProperty(propertyName);
+        }
+    }
+}
diff --git a/benchmarks/regression/DoubleBenchmark.java b/benchmarks/regression/DoubleBenchmark.java
new file mode 100644
index 0000000..aa692ac
--- /dev/null
+++ b/benchmarks/regression/DoubleBenchmark.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class DoubleBenchmark extends SimpleBenchmark {
+    private double d = 1.2;
+    private long l = 4608083138725491507L;
+
+    public void timeDoubleToLongBits(int reps) {
+        long result = 123;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = Double.doubleToLongBits(d);
+        }
+        if (result != l) {
+            throw new RuntimeException(Long.toString(result));
+        }
+    }
+
+    public void timeDoubleToRawLongBits(int reps) {
+        long result = 123;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = Double.doubleToRawLongBits(d);
+        }
+        if (result != l) {
+            throw new RuntimeException(Long.toString(result));
+        }
+    }
+
+    public void timeLongBitsToDouble(int reps) {
+        double result = 123.0;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = Double.longBitsToDouble(l);
+        }
+        if (result != d) {
+            throw new RuntimeException(Double.toString(result) + " " + Double.doubleToRawLongBits(result));
+        }
+    }
+}
diff --git a/benchmarks/regression/EqualsHashCodeBenchmark.java b/benchmarks/regression/EqualsHashCodeBenchmark.java
new file mode 100644
index 0000000..a15a41a
--- /dev/null
+++ b/benchmarks/regression/EqualsHashCodeBenchmark.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.net.URI;
+import java.net.URL;
+
+public final class EqualsHashCodeBenchmark extends SimpleBenchmark {
+    private enum Type {
+        URI() {
+            @Override Object newInstance(String text) throws Exception {
+                return new URI(text);
+            }
+        },
+        URL() {
+            @Override Object newInstance(String text) throws Exception {
+                return new URL(text);
+            }
+        };
+        abstract Object newInstance(String text) throws Exception;
+    }
+
+    @Param Type type;
+
+    Object a1;
+    Object a2;
+    Object b1;
+    Object b2;
+
+    @Override protected void setUp() throws Exception {
+        a1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+        a2 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+        b1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
+        b2 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
+    }
+
+    public void timeEquals(int reps) {
+        for (int i = 0; i < reps; i+=3) {
+            a1.equals(b1);
+            a1.equals(a2);
+            b1.equals(b2);
+        }
+    }
+
+    public void timeHashCode(int reps) {
+        for (int i = 0; i < reps; i+=2) {
+            a1.hashCode();
+            b1.hashCode();
+        }
+    }
+}
diff --git a/benchmarks/regression/ExpensiveObjectsBenchmark.java b/benchmarks/regression/ExpensiveObjectsBenchmark.java
new file mode 100644
index 0000000..535e298
--- /dev/null
+++ b/benchmarks/regression/ExpensiveObjectsBenchmark.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Benchmark;
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.text.*;
+import java.util.*;
+
+/**
+ * Benchmarks creation and cloning various expensive objects.
+ */
+public class ExpensiveObjectsBenchmark extends SimpleBenchmark {
+    public void timeNewDateFormatTimeInstance(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
+            df.format(System.currentTimeMillis());
+        }
+    }
+
+    public void timeClonedDateFormatTimeInstance(int reps) {
+        DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
+        for (int i = 0; i < reps; ++i) {
+            ((DateFormat) df.clone()).format(System.currentTimeMillis());
+        }
+    }
+
+    public void timeReusedDateFormatTimeInstance(int reps) {
+        DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT);
+        for (int i = 0; i < reps; ++i) {
+            synchronized (df) {
+                df.format(System.currentTimeMillis());
+            }
+        }
+    }
+
+    public void timeNewCollator(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            Collator.getInstance(Locale.US);
+        }
+    }
+
+    public void timeClonedCollator(int reps) {
+        Collator c = Collator.getInstance(Locale.US);
+        for (int i = 0; i < reps; ++i) {
+            c.clone();
+        }
+    }
+
+    public void timeNewDateFormatSymbols(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            new DateFormatSymbols(Locale.US);
+        }
+    }
+
+    public void timeClonedDateFormatSymbols(int reps) {
+        DateFormatSymbols dfs = new DateFormatSymbols(Locale.US);
+        for (int i = 0; i < reps; ++i) {
+            dfs.clone();
+        }
+    }
+
+    public void timeNewDecimalFormatSymbols(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            new DecimalFormatSymbols(Locale.US);
+        }
+    }
+
+    public void timeClonedDecimalFormatSymbols(int reps) {
+        DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
+        for (int i = 0; i < reps; ++i) {
+            dfs.clone();
+        }
+    }
+
+    public void timeNewNumberFormat(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            NumberFormat.getInstance(Locale.US);
+        }
+    }
+
+    public void timeClonedNumberFormat(int reps) {
+        NumberFormat nf = NumberFormat.getInstance(Locale.US);
+        for (int i = 0; i < reps; ++i) {
+            nf.clone();
+        }
+    }
+
+    public void timeNumberFormatTrivialFormatLong(int reps) {
+        NumberFormat nf = NumberFormat.getInstance(Locale.US);
+        for (int i = 0; i < reps; ++i) {
+            nf.format(1024L);
+        }
+    }
+
+    public void timeLongToString(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            Long.toString(1024L);
+        }
+    }
+
+    public void timeNumberFormatTrivialFormatDouble(int reps) {
+        NumberFormat nf = NumberFormat.getInstance(Locale.US);
+        for (int i = 0; i < reps; ++i) {
+            nf.format(1024.0);
+        }
+    }
+
+    public void timeNewSimpleDateFormat(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            new SimpleDateFormat();
+        }
+    }
+
+    public void timeClonedSimpleDateFormat(int reps) {
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        for (int i = 0; i < reps; ++i) {
+            sdf.clone();
+        }
+    }
+
+    public void timeNewGregorianCalendar(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            new GregorianCalendar();
+        }
+    }
+
+    public void timeClonedGregorianCalendar(int reps) {
+        GregorianCalendar gc = new GregorianCalendar();
+        for (int i = 0; i < reps; ++i) {
+            gc.clone();
+        }
+    }
+}
diff --git a/benchmarks/regression/FloatBenchmark.java b/benchmarks/regression/FloatBenchmark.java
new file mode 100644
index 0000000..a92bb69
--- /dev/null
+++ b/benchmarks/regression/FloatBenchmark.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class FloatBenchmark extends SimpleBenchmark {
+    private float f = 1.2f;
+    private int i = 1067030938;
+
+    public void timeFloatToIntBits(int reps) {
+        int result = 123;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = Float.floatToIntBits(f);
+        }
+        if (result != i) {
+            throw new RuntimeException(Integer.toString(result));
+        }
+    }
+
+    public void timeFloatToRawIntBits(int reps) {
+        int result = 123;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = Float.floatToRawIntBits(f);
+        }
+        if (result != i) {
+            throw new RuntimeException(Integer.toString(result));
+        }
+    }
+
+    public void timeIntBitsToFloat(int reps) {
+        float result = 123.0f;
+        for (int rep = 0; rep < reps; ++rep) {
+            result = Float.intBitsToFloat(i);
+        }
+        if (result != f) {
+            throw new RuntimeException(Float.toString(result) + " " + Float.floatToRawIntBits(result));
+        }
+    }
+}
diff --git a/benchmarks/regression/FormatterBenchmark.java b/benchmarks/regression/FormatterBenchmark.java
new file mode 100644
index 0000000..0d4cf26
--- /dev/null
+++ b/benchmarks/regression/FormatterBenchmark.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.Formatter;
+import java.util.Locale;
+
+/**
+ * Compares Formatter against hand-written StringBuilder code.
+ */
+public class FormatterBenchmark extends SimpleBenchmark {
+    public void timeFormatter_NoFormatting(int reps) {
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that doesn't actually need any formatting");
+        }
+    }
+
+    public void timeStringBuilder_NoFormatting(int reps) {
+        for (int i = 0; i < reps; i++) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("this is a reasonably short string that doesn't actually need any formatting");
+        }
+    }
+
+    public void timeFormatter_OneInt(int reps) {
+        Integer value = Integer.valueOf(1024); // We're not trying to benchmark boxing here.
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that has an int %d in it", value);
+        }
+    }
+
+    public void timeFormatter_OneIntArabic(int reps) {
+        Locale arabic = new Locale("ar");
+        Integer value = Integer.valueOf(1024); // We're not trying to benchmark boxing here.
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format(arabic, "this is a reasonably short string that has an int %d in it", value);
+        }
+    }
+
+    public void timeStringBuilder_OneInt(int reps) {
+        for (int i = 0; i < reps; i++) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("this is a reasonably short string that has an int ");
+            sb.append(1024);
+            sb.append(" in it");
+        }
+    }
+
+    public void timeFormatter_OneHexInt(int reps) {
+        Integer value = Integer.valueOf(1024); // We're not trying to benchmark boxing here.
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that has an int %x in it", value);
+        }
+    }
+
+    public void timeStringBuilder_OneHexInt(int reps) {
+        for (int i = 0; i < reps; i++) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("this is a reasonably short string that has an int ");
+            sb.append(Integer.toHexString(1024));
+            sb.append(" in it");
+        }
+    }
+
+    public void timeFormatter_OneFloat(int reps) {
+        Float value = Float.valueOf(10.24f); // We're not trying to benchmark boxing here.
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that has a float %f in it", value);
+        }
+    }
+
+    public void timeFormatter_OneFloat_dot2f(int reps) {
+        Float value = Float.valueOf(10.24f); // We're not trying to benchmark boxing here.
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that has a float %.2f in it", value);
+        }
+    }
+
+    public void timeFormatter_TwoFloats(int reps) {
+        Float value = Float.valueOf(10.24f); // We're not trying to benchmark boxing here.
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that has two floats %f and %f in it", value, value);
+        }
+    }
+
+    public void timeStringBuilder_OneFloat(int reps) {
+        for (int i = 0; i < reps; i++) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("this is a reasonably short string that has a float ");
+            sb.append(10.24f);
+            sb.append(" in it");
+        }
+    }
+
+    public void timeFormatter_OneString(int reps) {
+        for (int i = 0; i < reps; i++) {
+            Formatter f = new Formatter();
+            f.format("this is a reasonably short string that has a string %s in it", "hello");
+        }
+    }
+
+    public void timeStringBuilder_OneString(int reps) {
+        for (int i = 0; i < reps; i++) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("this is a reasonably short string that has a string ");
+            sb.append("hello");
+            sb.append(" in it");
+        }
+    }
+}
diff --git a/benchmarks/regression/HostnameVerifierBenchmark.java b/benchmarks/regression/HostnameVerifierBenchmark.java
new file mode 100644
index 0000000..e9218c4
--- /dev/null
+++ b/benchmarks/regression/HostnameVerifierBenchmark.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.io.ByteArrayInputStream;
+import java.net.URL;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+
+/**
+ * This benchmark makes a real HTTP connection to a handful of hosts and
+ * captures the served certificates as a byte array. It then verifies each
+ * certificate in the benchmark loop, being careful to convert from the
+ * byte[] to the certificate each time. Otherwise the certificate class
+ * caches previous results which skews the results of the benchmark: In practice
+ * each certificate instance is verified once and then released.
+ */
+public final class HostnameVerifierBenchmark extends SimpleBenchmark {
+
+    @Param({"android.clients.google.com",
+            "m.google.com",
+            "www.google.com",
+            "www.amazon.com",
+            "www.ubs.com"}) String host;
+
+    private String hostname;
+    private HostnameVerifier hostnameVerifier;
+    private byte[][] encodedCertificates;
+
+    @Override protected void setUp() throws Exception {
+        URL url = new URL("https", host, "/");
+        hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+        connection.setHostnameVerifier(new HostnameVerifier() {
+            public boolean verify(String hostname, SSLSession sslSession) {
+                try {
+                    encodedCertificates = certificatesToBytes(sslSession.getPeerCertificates());
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                HostnameVerifierBenchmark.this.hostname = hostname;
+                return true;
+            }
+        });
+        connection.getInputStream();
+        connection.disconnect();
+    }
+
+    public void timeVerify(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            final Certificate[] certificates = bytesToCertificates(encodedCertificates);
+            FakeSSLSession sslSession = new FakeSSLSession() {
+                @Override public Certificate[] getPeerCertificates() {
+                    return certificates;
+                }
+            };
+            hostnameVerifier.verify(hostname, sslSession);
+        }
+    }
+
+    private byte[][] certificatesToBytes(Certificate[] certificates) throws Exception {
+        byte[][] result = new byte[certificates.length][];
+        for (int i = 0, certificatesLength = certificates.length; i < certificatesLength; i++) {
+            result[i] = certificates[i].getEncoded();
+        }
+        return result;
+    }
+
+    private Certificate[] bytesToCertificates(byte[][] encodedCertificates) throws Exception {
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+        Certificate[] result = new Certificate[encodedCertificates.length];
+        for (int i = 0; i < encodedCertificates.length; i++) {
+            result[i] = certificateFactory.generateCertificate(
+                new ByteArrayInputStream(encodedCertificates[i]));
+        }
+        return result;
+    }
+
+    private static class FakeSSLSession implements SSLSession {
+        public int getApplicationBufferSize() {
+            throw new UnsupportedOperationException();
+        }
+        public String getCipherSuite() {
+            throw new UnsupportedOperationException();
+        }
+        public long getCreationTime() {
+            throw new UnsupportedOperationException();
+        }
+        public byte[] getId() {
+            throw new UnsupportedOperationException();
+        }
+        public long getLastAccessedTime() {
+            throw new UnsupportedOperationException();
+        }
+        public Certificate[] getLocalCertificates() {
+            throw new UnsupportedOperationException();
+        }
+        public Principal getLocalPrincipal() {
+            throw new UnsupportedOperationException();
+        }
+        public int getPacketBufferSize() {
+            throw new UnsupportedOperationException();
+        }
+        public javax.security.cert.X509Certificate[] getPeerCertificateChain() {
+            throw new UnsupportedOperationException();
+        }
+        public Certificate[] getPeerCertificates() {
+            throw new UnsupportedOperationException();
+        }
+        public String getPeerHost() {
+            throw new UnsupportedOperationException();
+        }
+        public int getPeerPort() {
+            throw new UnsupportedOperationException();
+        }
+        public Principal getPeerPrincipal() {
+            throw new UnsupportedOperationException();
+        }
+        public String getProtocol() {
+            throw new UnsupportedOperationException();
+        }
+        public SSLSessionContext getSessionContext() {
+            throw new UnsupportedOperationException();
+        }
+        public Object getValue(String name) {
+            throw new UnsupportedOperationException();
+        }
+        public String[] getValueNames() {
+            throw new UnsupportedOperationException();
+        }
+        public void invalidate() {
+            throw new UnsupportedOperationException();
+        }
+        public boolean isValid() {
+            throw new UnsupportedOperationException();
+        }
+        public void putValue(String name, Object value) {
+            throw new UnsupportedOperationException();
+        }
+        public void removeValue(String name) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public static void main(String[] args) {
+        Runner.main(HostnameVerifierBenchmark.class, args);
+    }
+}
diff --git a/benchmarks/regression/IntConstantDivisionBenchmark.java b/benchmarks/regression/IntConstantDivisionBenchmark.java
new file mode 100644
index 0000000..498b783
--- /dev/null
+++ b/benchmarks/regression/IntConstantDivisionBenchmark.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class IntConstantDivisionBenchmark extends SimpleBenchmark {
+    public int timeDivideIntByConstant2(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result /= 2;
+        }
+        return result;
+    }
+    public int timeDivideIntByConstant8(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result /= 8;
+        }
+        return result;
+    }
+    public int timeDivideIntByConstant10(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result /= 10;
+        }
+        return result;
+    }
+    public int timeDivideIntByConstant100(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result /= 100;
+        }
+        return result;
+    }
+    public int timeDivideIntByConstant100_HandOptimized(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result = (int) ((0x51eb851fL * result) >>> 37);
+        }
+        return result;
+    }
+    public int timeDivideIntByConstant2048(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result /= 2048;
+        }
+        return result;
+    }
+    public int timeDivideIntByVariable2(int reps) {
+        int result = 1;
+        int factor = 2;
+        for (int i = 0; i < reps; ++i) {
+            result /= factor;
+        }
+        return result;
+    }
+    public int timeDivideIntByVariable10(int reps) {
+        int result = 1;
+        int factor = 10;
+        for (int i = 0; i < reps; ++i) {
+            result /= factor;
+        }
+        return result;
+    }
+}
diff --git a/benchmarks/regression/IntConstantMultiplicationBenchmark.java b/benchmarks/regression/IntConstantMultiplicationBenchmark.java
new file mode 100644
index 0000000..ca1349c
--- /dev/null
+++ b/benchmarks/regression/IntConstantMultiplicationBenchmark.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class IntConstantMultiplicationBenchmark extends SimpleBenchmark {
+    public int timeMultiplyIntByConstant6(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 6;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant7(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 7;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant8(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 8;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant8_Shift(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result <<= 3;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant10(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 10;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant10_Shift(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result = (result + (result << 2)) << 1;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant2047(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 2047;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant2048(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 2048;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByConstant2049(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result *= 2049;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByVariable10(int reps) {
+        int result = 1;
+        int factor = 10;
+        for (int i = 0; i < reps; ++i) {
+            result *= factor;
+        }
+        return result;
+    }
+    public int timeMultiplyIntByVariable8(int reps) {
+        int result = 1;
+        int factor = 8;
+        for (int i = 0; i < reps; ++i) {
+            result *= factor;
+        }
+        return result;
+    }
+}
diff --git a/benchmarks/regression/IntConstantRemainderBenchmark.java b/benchmarks/regression/IntConstantRemainderBenchmark.java
new file mode 100644
index 0000000..6174721
--- /dev/null
+++ b/benchmarks/regression/IntConstantRemainderBenchmark.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class IntConstantRemainderBenchmark extends SimpleBenchmark {
+    public int timeRemainderIntByConstant2(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result %= 2;
+        }
+        return result;
+    }
+    public int timeRemainderIntByConstant8(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result %= 8;
+        }
+        return result;
+    }
+/*
+    public int timeRemainderIntByConstant10(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result %= 10;
+        }
+        return result;
+    }
+    public int timeRemainderIntByConstant100(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result %= 100;
+        }
+        return result;
+    }
+*/
+    public int timeRemainderIntByConstant2048(int reps) {
+        int result = 1;
+        for (int i = 0; i < reps; ++i) {
+            result %= 2048;
+        }
+        return result;
+    }
+    public int timeRemainderIntByVariable2(int reps) {
+        int result = 1;
+        int factor = 2;
+        for (int i = 0; i < reps; ++i) {
+            result %= factor;
+        }
+        return result;
+    }
+/*
+    public int timeRemainderIntByVariable10(int reps) {
+        int result = 1;
+        int factor = 10;
+        for (int i = 0; i < reps; ++i) {
+            result %= factor;
+        }
+        return result;
+    }
+*/
+}
diff --git a/benchmarks/regression/IntegerBenchmark.java b/benchmarks/regression/IntegerBenchmark.java
new file mode 100644
index 0000000..601bb5d
--- /dev/null
+++ b/benchmarks/regression/IntegerBenchmark.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class IntegerBenchmark extends SimpleBenchmark {
+    public int timeLongSignumBranch(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += signum1(-i);
+            t += signum1(0);
+            t += signum1(i);
+        }
+        return t;
+    }
+
+    public int timeLongSignumBranchFree(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += signum2(-i);
+            t += signum2(0);
+            t += signum2(i);
+        }
+        return t;
+    }
+
+    private static int signum1(long v) {
+        return v < 0 ? -1 : (v == 0 ? 0 : 1);
+    }
+
+    private static int signum2(long v) {
+        return ((int)(v >> 63)) | (int) (-v >>> 63); // Hacker's delight 2-7
+    }
+
+    public int timeLongBitCount_BitSet(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += pop((long) i);
+        }
+        return t;
+    }
+
+    private static int pop(long l) {
+        int count = popX(l & 0xffffffffL);
+        count += popX(l >>> 32);
+        return count;
+    }
+
+    private static int popX(long x) {
+        // BEGIN android-note
+        // delegate to Integer.bitCount(i); consider using native code
+        // END android-note
+        x = x - ((x >>> 1) & 0x55555555);
+        x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
+        x = (x + (x >>> 4)) & 0x0f0f0f0f;
+        x = x + (x >>> 8);
+        x = x + (x >>> 16);
+        return (int) x & 0x0000003f;
+    }
+
+    public int timeLongBitCount_2Int(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += pop2((long) i);
+        }
+        return t;
+    }
+
+    private static int pop2(long l) {
+        int count = Integer.bitCount((int) (l & 0xffffffffL));
+        count += Integer.bitCount((int) (l >>> 32));
+        return count;
+    }
+
+    public int timeLongBitCount_Long(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += Long.bitCount((long) i);
+        }
+        return t;
+    }
+
+    /**
+     * Table for Seal's algorithm for Number of Trailing Zeros. Hacker's Delight
+     * online, Figure 5-18 (http://www.hackersdelight.org/revisions.pdf)
+     * The entries whose value is -1 are never referenced.
+     */
+    private static final byte[] NTZ_TABLE = {
+        32,  0,  1, 12,  2,  6, -1, 13,   3, -1,  7, -1, -1, -1, -1, 14,
+        10,  4, -1, -1,  8, -1, -1, 25,  -1, -1, -1, -1, -1, 21, 27, 15,
+        31, 11,  5, -1, -1, -1, -1, -1,   9, -1, -1, 24, -1, -1, 20, 26,
+        30, -1, -1, -1, -1, 23, -1, 19,  29, -1, 22, 18, 28, 17, 16, -1
+    };
+
+    private static int numberOfTrailingZerosHD(int i) {
+        // Seal's algorithm - Hacker's Delight 5-18
+        i &= -i;
+        i = (i <<  4) + i;    // x *= 17
+        i = (i <<  6) + i;    // x *= 65
+        i = (i << 16) - i;    // x *= 65535
+        return NTZ_TABLE[i >>> 26];
+    }
+
+    private static int numberOfTrailingZerosOL(int i) {
+        return NTZ_TABLE[((i & -i) * 0x0450FBAF) >>> 26];
+    }
+
+    public int timeNumberOfTrailingZerosHD(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += numberOfTrailingZerosHD(i);
+        }
+        return t;
+    }
+
+    public int timeNumberOfTrailingZerosOL(int reps) {
+        int t = 0;
+        for (int i = 0; i < reps; ++i) {
+            t += numberOfTrailingZerosOL(i);
+        }
+        return t;
+    }
+}
diff --git a/benchmarks/regression/IntegralToStringBenchmark.java b/benchmarks/regression/IntegralToStringBenchmark.java
new file mode 100644
index 0000000..cab9e98
--- /dev/null
+++ b/benchmarks/regression/IntegralToStringBenchmark.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class IntegralToStringBenchmark extends SimpleBenchmark {
+
+    private static final int SMALL  = 12;
+    private static final int MEDIUM = 12345;
+    private static final int LARGE  = 12345678;
+
+    public void time_IntegerToString_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(SMALL);
+        }
+    }
+
+    public void time_IntegerToString_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(MEDIUM);
+        }
+    }
+
+    public void time_IntegerToString_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(LARGE);
+        }
+    }
+
+    public void time_IntegerToString2_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(SMALL, 2);
+        }
+    }
+
+    public void time_IntegerToString2_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(MEDIUM, 2);
+        }
+    }
+
+    public void time_IntegerToString2_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(LARGE, 2);
+        }
+    }
+
+    public void time_IntegerToString10_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(SMALL, 10);
+        }
+    }
+
+    public void time_IntegerToString10_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(MEDIUM, 10);
+        }
+    }
+
+    public void time_IntegerToString10_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(LARGE, 10);
+        }
+    }
+
+    public void time_IntegerToString16_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(SMALL, 16);
+        }
+    }
+
+    public void time_IntegerToString16_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(MEDIUM, 16);
+        }
+    }
+
+    public void time_IntegerToString16_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toString(LARGE, 16);
+        }
+    }
+
+    public void time_IntegerToBinaryString_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toBinaryString(SMALL);
+        }
+    }
+
+    public void time_IntegerToBinaryString_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toBinaryString(MEDIUM);
+        }
+    }
+
+    public void time_IntegerToBinaryString_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toBinaryString(LARGE);
+        }
+    }
+
+    public void time_IntegerToHexString_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toHexString(SMALL);
+        }
+    }
+
+    public void time_IntegerToHexString_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toHexString(MEDIUM);
+        }
+    }
+
+    public void time_IntegerToHexString_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Integer.toHexString(LARGE);
+        }
+    }
+
+    public void time_StringBuilder_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            new StringBuilder().append(SMALL);
+        }
+    }
+
+    public void time_StringBuilder_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            new StringBuilder().append(MEDIUM);
+        }
+    }
+
+    public void time_StringBuilder_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            new StringBuilder().append(LARGE);
+        }
+    }
+
+    public void time_Formatter_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%d", SMALL);
+        }
+    }
+
+    public void time_Formatter_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%d", MEDIUM);
+        }
+    }
+
+    public void time_Formatter_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%d", LARGE);
+        }
+    }
+}
diff --git a/benchmarks/regression/JarFileBenchmark.java b/benchmarks/regression/JarFileBenchmark.java
new file mode 100644
index 0000000..626ca21
--- /dev/null
+++ b/benchmarks/regression/JarFileBenchmark.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.io.File;
+import java.util.jar.*;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class JarFileBenchmark extends SimpleBenchmark {
+    @Param({
+        "/system/framework/bouncycastle.jar",
+        "/system/framework/core.jar",
+        "/system/framework/framework.jar"
+    })
+    private String filename;
+
+    public void time(int reps) throws Exception {
+        File f = new File(filename);
+        for (int i = 0; i < reps; ++i) {
+            JarFile jf = new JarFile(f);
+            Manifest m = jf.getManifest();
+            jf.close();
+        }
+    }
+}
diff --git a/benchmarks/regression/KeyPairGeneratorBenchmark.java b/benchmarks/regression/KeyPairGeneratorBenchmark.java
new file mode 100644
index 0000000..762c935
--- /dev/null
+++ b/benchmarks/regression/KeyPairGeneratorBenchmark.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+public class KeyPairGeneratorBenchmark extends SimpleBenchmark {
+    @Param private Algorithm algorithm;
+
+    public enum Algorithm {
+        RSA,
+        DSA,
+    };
+
+    @Param private Implementation implementation;
+
+    public enum Implementation { OpenSSL, BouncyCastle };
+
+    private String generatorAlgorithm;
+    private KeyPairGenerator generator;
+    private SecureRandom random;
+
+    @Override protected void setUp() throws Exception {
+        this.generatorAlgorithm = algorithm.toString();
+        
+        final String provider;
+        if (implementation == Implementation.BouncyCastle) {
+            provider = "BC";
+        } else {
+            provider = "AndroidOpenSSL";
+        }
+
+        this.generator = KeyPairGenerator.getInstance(generatorAlgorithm, provider);
+        this.random = SecureRandom.getInstance("SHA1PRNG");
+        this.generator.initialize(1024);
+    }
+
+    public void time(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            KeyPair keyPair = generator.generateKeyPair();
+        }
+    }
+}
diff --git a/benchmarks/regression/LoopingBackwardsBenchmark.java b/benchmarks/regression/LoopingBackwardsBenchmark.java
new file mode 100644
index 0000000..054eff9
--- /dev/null
+++ b/benchmarks/regression/LoopingBackwardsBenchmark.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Testing the old canard that looping backwards is faster.
+ *
+ * @author Kevin Bourrillion
+ */
+public class LoopingBackwardsBenchmark extends SimpleBenchmark {
+  @Param({"2", "20", "2000", "20000000"}) int max;
+
+  public int timeForwards(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      for (int j = 0; j < max; j++) {
+        dummy += j;
+      }
+    }
+    return dummy;
+  }
+
+  public int timeBackwards(int reps) {
+    int dummy = 0;
+    for (int i = 0; i < reps; i++) {
+      for (int j = max - 1; j >= 0; j--) {
+        dummy += j;
+      }
+    }
+    return dummy;
+  }
+}
diff --git a/benchmarks/regression/MathBenchmark.java b/benchmarks/regression/MathBenchmark.java
new file mode 100644
index 0000000..25a871d
--- /dev/null
+++ b/benchmarks/regression/MathBenchmark.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Many of these tests are bogus in that the cost will vary wildly depending on inputs.
+ * For _my_ current purposes, that's okay. But beware!
+ */
+public class MathBenchmark extends SimpleBenchmark {
+    private final double d = 1.2;
+    private final float f = 1.2f;
+    private final int i = 1;
+    private final long l = 1L;
+
+    public void timeAbsD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.abs(d);
+        }
+    }
+
+    public void timeAbsF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.abs(f);
+        }
+    }
+
+    public void timeAbsI(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.abs(i);
+        }
+    }
+
+    public void timeAbsL(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.abs(l);
+        }
+    }
+
+    public void timeAcos(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.acos(d);
+        }
+    }
+
+    public void timeAsin(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.asin(d);
+        }
+    }
+
+    public void timeAtan(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.atan(d);
+        }
+    }
+
+    public void timeAtan2(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.atan2(3, 4);
+        }
+    }
+
+    public void timeCbrt(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.cbrt(d);
+        }
+    }
+
+    public void timeCeil(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.ceil(d);
+        }
+    }
+
+    public void timeCopySignD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.copySign(d, d);
+        }
+    }
+
+    public void timeCopySignF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.copySign(f, f);
+        }
+    }
+
+    public void timeCopySignD_strict(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.copySign(d, d);
+        }
+    }
+
+    public void timeCopySignF_strict(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.copySign(f, f);
+        }
+    }
+
+    public void timeCos(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.cos(d);
+        }
+    }
+
+    public void timeCosh(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.cosh(d);
+        }
+    }
+
+    public void timeExp(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.exp(d);
+        }
+    }
+
+    public void timeExpm1(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.expm1(d);
+        }
+    }
+
+    public void timeFloor(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.floor(d);
+        }
+    }
+
+    public void timeGetExponentD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.getExponent(d);
+        }
+    }
+
+    public void timeGetExponentF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.getExponent(f);
+        }
+    }
+
+    public void timeHypot(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.hypot(d, d);
+        }
+    }
+
+    public void timeIEEEremainder(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.IEEEremainder(d, d);
+        }
+    }
+
+    public void timeLog(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.log(d);
+        }
+    }
+
+    public void timeLog10(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.log10(d);
+        }
+    }
+
+    public void timeLog1p(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.log1p(d);
+        }
+    }
+
+    public void timeMaxD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.max(d, d);
+        }
+    }
+
+    public void timeMaxF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.max(f, f);
+        }
+    }
+
+    public void timeMaxI(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.max(i, i);
+        }
+    }
+
+    public void timeMaxL(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.max(l, l);
+        }
+    }
+
+    public void timeMinD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.min(d, d);
+        }
+    }
+
+    public void timeMinF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.min(f, f);
+        }
+    }
+
+    public void timeMinI(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.min(i, i);
+        }
+    }
+
+    public void timeMinL(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.min(l, l);
+        }
+    }
+
+    public void timeNextAfterD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.nextAfter(d, d);
+        }
+    }
+
+    public void timeNextAfterF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.nextAfter(f, f);
+        }
+    }
+
+    public void timeNextUpD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.nextUp(d);
+        }
+    }
+
+    public void timeNextUpF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.nextUp(f);
+        }
+    }
+
+    public void timePow(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.pow(d, d);
+        }
+    }
+
+    public void timeRandom(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.random();
+        }
+    }
+
+    public void timeRint(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.rint(d);
+        }
+    }
+
+    public void timeRoundD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.round(d);
+        }
+    }
+
+    public void timeRoundF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.round(f);
+        }
+    }
+
+    public void timeScalbD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.scalb(d, 5);
+        }
+    }
+
+    public void timeScalbF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.scalb(f, 5);
+        }
+    }
+
+    public void timeSignumD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.signum(d);
+        }
+    }
+
+    public void timeSignumF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.signum(f);
+        }
+    }
+
+    public void timeSin(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.sin(d);
+        }
+    }
+
+    public void timeSinh(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.sinh(d);
+        }
+    }
+
+    public void timeSqrt(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.sqrt(d);
+        }
+    }
+
+    public void timeTan(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.tan(d);
+        }
+    }
+
+    public void timeTanh(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.tanh(d);
+        }
+    }
+
+    public void timeToDegrees(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.toDegrees(d);
+        }
+    }
+
+    public void timeToRadians(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.toRadians(d);
+        }
+    }
+
+    public void timeUlpD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.ulp(d);
+        }
+    }
+
+    public void timeUlpF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Math.ulp(f);
+        }
+    }
+}
diff --git a/benchmarks/regression/MessageDigestBenchmark.java b/benchmarks/regression/MessageDigestBenchmark.java
new file mode 100644
index 0000000..c9d8074
--- /dev/null
+++ b/benchmarks/regression/MessageDigestBenchmark.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.security.MessageDigest;
+
+public class MessageDigestBenchmark extends SimpleBenchmark {
+
+    private static final int DATA_SIZE = 8192;
+    private static final byte[] DATA = new byte[DATA_SIZE];
+    static {
+        for (int i = 0; i < DATA_SIZE; i++) {
+            DATA[i] = (byte)i;
+        }
+    }
+
+    @Param private Algorithm algorithm;
+
+    public enum Algorithm { MD5, SHA1, SHA256,  SHA384, SHA512 };
+
+    @Param private Provider provider;
+
+    public enum Provider { AndroidOpenSSL, BC };
+
+    public void time(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(),
+                                                             provider.toString());
+            digest.update(DATA, 0, DATA_SIZE);
+            digest.digest();
+        }
+    }
+}
diff --git a/benchmarks/regression/MutableIntBenchmark.java b/benchmarks/regression/MutableIntBenchmark.java
new file mode 100644
index 0000000..ee4f2d9
--- /dev/null
+++ b/benchmarks/regression/MutableIntBenchmark.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class MutableIntBenchmark extends SimpleBenchmark {
+
+    enum Kind {
+        ARRAY() {
+            int[] value = new int[1];
+
+            @Override void timeCreate(int reps) {
+                for (int i = 0; i < reps; i++) {
+                    value = new int[] { 5 };
+                }
+            }
+            @Override void timeIncrement(int reps) {
+                for (int i = 0; i < reps; i++) {
+                    value[0]++;
+                }
+            }
+            @Override int timeGet(int reps) {
+                int sum = 0;
+                for (int i = 0; i < reps; i++) {
+                    sum += value[0];
+                }
+                return sum;
+            }
+        },
+        ATOMIC() {
+            AtomicInteger value = new AtomicInteger();
+
+            @Override void timeCreate(int reps) {
+                for (int i = 0; i < reps; i++) {
+                    value = new AtomicInteger(5);
+                }
+            }
+            @Override void timeIncrement(int reps) {
+                for (int i = 0; i < reps; i++) {
+                    value.incrementAndGet();
+                }
+            }
+            @Override int timeGet(int reps) {
+                int sum = 0;
+                for (int i = 0; i < reps; i++) {
+                    sum += value.intValue();
+                }
+                return sum;
+            }
+        };
+
+        abstract void timeCreate(int reps);
+        abstract void timeIncrement(int reps);
+        abstract int timeGet(int reps);
+    }
+
+    @Param Kind kind;
+
+    public void timeCreate(int reps) {
+        kind.timeCreate(reps);
+    }
+
+    public void timeIncrement(int reps) {
+        kind.timeIncrement(reps);
+    }
+
+    public void timeGet(int reps) {
+        kind.timeGet(reps);
+    }
+
+    public static void main(String[] args) {
+        Runner.main(MutableIntBenchmark.class, args);
+    }
+}
diff --git a/benchmarks/regression/NativeMethodBenchmark.java b/benchmarks/regression/NativeMethodBenchmark.java
new file mode 100644
index 0000000..2e482ef
--- /dev/null
+++ b/benchmarks/regression/NativeMethodBenchmark.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.SimpleBenchmark;
+import org.apache.harmony.dalvik.NativeTestTarget;
+
+public class NativeMethodBenchmark extends SimpleBenchmark {
+    public void time_emptyJniStaticSynchronizedMethod0(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            NativeTestTarget.emptyJniStaticSynchronizedMethod0();
+        }
+    }
+
+    public void time_emptyJniSynchronizedMethod0(int reps) throws Exception {
+        NativeTestTarget n = new NativeTestTarget();
+        for (int i = 0; i < reps; ++i) {
+            n.emptyJniSynchronizedMethod0();
+        }
+    }
+
+    public void time_emptyJniStaticMethod0(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            NativeTestTarget.emptyJniStaticMethod0();
+        }
+    }
+
+    public void time_emptyJniMethod0(int reps) throws Exception {
+        NativeTestTarget n = new NativeTestTarget();
+        for (int i = 0; i < reps; ++i) {
+            n.emptyJniMethod0();
+        }
+    }
+
+    public void time_emptyJniStaticMethod6(int reps) throws Exception {
+        int a = -1;
+        int b = 0;
+        for (int i = 0; i < reps; ++i) {
+            NativeTestTarget.emptyJniStaticMethod6(a, b, 1, 2, 3, i);
+        }
+    }
+
+    public void time_emptyJniMethod6(int reps) throws Exception {
+        int a = -1;
+        int b = 0;
+        NativeTestTarget n = new NativeTestTarget();
+        for (int i = 0; i < reps; ++i) {
+            n.emptyJniMethod6(a, b, 1, 2, 3, i);
+        }
+    }
+
+    public void time_emptyJniStaticMethod6L(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            NativeTestTarget.emptyJniStaticMethod6L(null, null, null, null, null, null);
+        }
+    }
+
+    public void time_emptyJniMethod6L(int reps) throws Exception {
+        NativeTestTarget n = new NativeTestTarget();
+        for (int i = 0; i < reps; ++i) {
+            n.emptyJniMethod6L(null, null, null, null, null, null);
+        }
+    }
+
+}
diff --git a/benchmarks/regression/ParseBenchmark.java b/benchmarks/regression/ParseBenchmark.java
new file mode 100644
index 0000000..b44b429
--- /dev/null
+++ b/benchmarks/regression/ParseBenchmark.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Measure throughput of various parsers.
+ *
+ * <p>This benchmark requires that ParseBenchmarkData.zip is on the classpath.
+ * That file contains Twitter feed data, which is representative of what
+ * applications will be parsing.
+ */
+public final class ParseBenchmark extends SimpleBenchmark {
+
+    @Param Document document;
+    @Param Api api;
+
+    private enum Document {
+        TWEETS,
+        READER_SHORT,
+        READER_LONG
+    }
+
+    private enum Api {
+        ANDROID_STREAM("json") {
+            @Override Parser newParser() {
+                return new AndroidStreamParser();
+            }
+        },
+        ORG_JSON("json") {
+            @Override Parser newParser() {
+                return new OrgJsonParser();
+            }
+        },
+        XML_PULL("xml") {
+            @Override Parser newParser() {
+                return new GeneralXmlPullParser();
+            }
+        },
+        XML_DOM("xml") {
+            @Override Parser newParser() {
+                return new XmlDomParser();
+            }
+        },
+        XML_SAX("xml") {
+            @Override Parser newParser() {
+                return new XmlSaxParser();
+            }
+        };
+
+        final String extension;
+
+        private Api(String extension) {
+            this.extension = extension;
+        }
+
+        abstract Parser newParser();
+    }
+
+    private String text;
+    private Parser parser;
+
+    @Override protected void setUp() throws Exception {
+        text = resourceToString("/" + document.name() + "." + api.extension);
+        parser = api.newParser();
+    }
+
+    public void timeParse(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            parser.parse(text);
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        Runner.main(ParseBenchmark.class, args);
+    }
+
+    private static String resourceToString(String path) throws Exception {
+        InputStream in = ParseBenchmark.class.getResourceAsStream(path);
+        if (in == null) {
+            throw new IllegalArgumentException("No such file: " + path);
+        }
+
+        Reader reader = new InputStreamReader(in, "UTF-8");
+        char[] buffer = new char[8192];
+        StringWriter writer = new StringWriter();
+        int count;
+        while ((count = reader.read(buffer)) != -1) {
+            writer.write(buffer, 0, count);
+        }
+        reader.close();
+        return writer.toString();
+    }
+
+    interface Parser {
+        void parse(String data) throws Exception;
+    }
+
+    private static class AndroidStreamParser implements Parser {
+        @Override public void parse(String data) throws Exception {
+            android.util.JsonReader jsonReader
+                    = new android.util.JsonReader(new StringReader(data));
+            readToken(jsonReader);
+            jsonReader.close();
+        }
+
+        public void readObject(android.util.JsonReader reader) throws IOException {
+            reader.beginObject();
+            while (reader.hasNext()) {
+                reader.nextName();
+                readToken(reader);
+            }
+            reader.endObject();
+        }
+
+        public void readArray(android.util.JsonReader reader) throws IOException {
+            reader.beginArray();
+            while (reader.hasNext()) {
+                readToken(reader);
+            }
+            reader.endArray();
+        }
+
+        private void readToken(android.util.JsonReader reader) throws IOException {
+            switch (reader.peek()) {
+            case BEGIN_ARRAY:
+                readArray(reader);
+                break;
+            case BEGIN_OBJECT:
+                readObject(reader);
+                break;
+            case BOOLEAN:
+                reader.nextBoolean();
+                break;
+            case NULL:
+                reader.nextNull();
+                break;
+            case NUMBER:
+                reader.nextLong();
+                break;
+            case STRING:
+                reader.nextString();
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected token" + reader.peek());
+            }
+        }
+    }
+
+    private static class OrgJsonParser implements Parser {
+        @Override public void parse(String data) throws Exception {
+            if (data.startsWith("[")) {
+                new JSONArray(data);
+            } else if (data.startsWith("{")) {
+                new JSONObject(data);
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+    }
+
+    private static class GeneralXmlPullParser implements Parser {
+        @Override public void parse(String data) throws Exception {
+            XmlPullParser xmlParser = android.util.Xml.newPullParser();
+            xmlParser.setInput(new StringReader(data));
+            xmlParser.nextTag();
+            while (xmlParser.next() != XmlPullParser.END_DOCUMENT) {
+                xmlParser.getName();
+                xmlParser.getText();
+            }
+        }
+    }
+
+    private static class XmlDomParser implements Parser {
+        @Override public void parse(String data) throws Exception {
+            DocumentBuilderFactory.newInstance().newDocumentBuilder()
+                    .parse(new InputSource(new StringReader(data)));
+        }
+    }
+
+    private static class XmlSaxParser implements Parser {
+        @Override public void parse(String data) throws Exception {
+            SAXParserFactory.newInstance().newSAXParser().parse(
+                    new InputSource(new StringReader(data)), new DefaultHandler());
+        }
+    }
+}
diff --git a/benchmarks/regression/ParseBenchmarkData.zip b/benchmarks/regression/ParseBenchmarkData.zip
new file mode 100644
index 0000000..7838e8a
--- /dev/null
+++ b/benchmarks/regression/ParseBenchmarkData.zip
Binary files differ
diff --git a/benchmarks/regression/PriorityQueueBenchmark.java b/benchmarks/regression/PriorityQueueBenchmark.java
new file mode 100644
index 0000000..2fe661b
--- /dev/null
+++ b/benchmarks/regression/PriorityQueueBenchmark.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Random;
+
+public class PriorityQueueBenchmark extends SimpleBenchmark {
+    @Param({"100", "1000", "10000"}) private int queueSize;
+    @Param({"0", "25", "50", "75", "100"}) private int hitRate;
+
+    private PriorityQueue<Integer> pq;
+    private PriorityQueue<Integer> usepq;
+    private List<Integer> seekElements;
+    private Random random = new Random(189279387L);
+
+    @Override protected void setUp() throws Exception {
+        pq = new PriorityQueue<Integer>();
+        usepq = new PriorityQueue<Integer>();
+        seekElements = new ArrayList<Integer>();
+        List<Integer> allElements = new ArrayList<Integer>();
+        int numShared = (int)(queueSize * ((double)hitRate / 100));
+        // the total number of elements we require to engineer a hit rate of hitRate%
+        int totalElements = 2 * queueSize - numShared;
+        for (int i = 0; i < totalElements; i++) {
+            allElements.add(i);
+        }
+        // shuffle these elements so that we get a reasonable distribution of missed elements
+        Collections.shuffle(allElements, random);
+        // add shared elements
+        for (int i = 0; i < numShared; i++) {
+            pq.add(allElements.get(i));
+            seekElements.add(allElements.get(i));
+        }
+        // add priority queue only elements (these won't be touched)
+        for (int i = numShared; i < queueSize; i++) {
+            pq.add(allElements.get(i));
+        }
+        // add non-priority queue elements (these will be misses)
+        for (int i = queueSize; i < totalElements; i++) {
+            seekElements.add(allElements.get(i));
+        }
+        usepq = new PriorityQueue<Integer>(pq);
+        // shuffle again so that elements are accessed in a different pattern than they were
+        // inserted
+        Collections.shuffle(seekElements, random);
+    }
+
+    public boolean timeRemove(int reps) {
+        boolean dummy = false;
+        int elementsSize = seekElements.size();
+        // At most allow the queue to empty 10%.
+        int resizingThreshold = queueSize / 10;
+        for (int i = 0; i < reps; i++) {
+            // Reset queue every so often. This will be called more often for smaller
+            // queueSizes, but since a copy is linear, it will also cost proportionally
+            // less, and hopefully it will approximately balance out.
+            if (i % resizingThreshold == 0) {
+                usepq = new PriorityQueue<Integer>(pq);
+            }
+            dummy = usepq.remove(seekElements.get(i % elementsSize));
+        }
+        return dummy;
+    }
+}
diff --git a/benchmarks/regression/PropertyAccessBenchmark.java b/benchmarks/regression/PropertyAccessBenchmark.java
new file mode 100644
index 0000000..cabd6ed
--- /dev/null
+++ b/benchmarks/regression/PropertyAccessBenchmark.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.SimpleBenchmark;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public final class PropertyAccessBenchmark extends SimpleBenchmark {
+    private View view = new View();
+    private Method setX;
+    private GeneratedProperty generatedSetter = new GeneratedSetter();
+    private GeneratedProperty generatedField = new GeneratedField();
+    private Field x;
+    private Object[] argsBox = new Object[1];
+
+    @Override protected void setUp() throws Exception {
+        setX = View.class.getDeclaredMethod("setX", float.class);
+        x = View.class.getDeclaredField("x");
+    }
+
+    public void timeDirectSetter(int reps) {
+        for (int i = 0; i < reps; i++) {
+            view.setX(0.1f);
+        }
+    }
+
+    public void timeDirectFieldSet(int reps) {
+        for (int i = 0; i < reps; i++) {
+            view.x = 0.1f;
+        }
+    }
+
+    public void timeDirectSetterAndBoxing(int reps) {
+        for (int i = 0; i < reps; i++) {
+            Float value = 0.1f;
+            view.setX(value);
+        }
+    }
+
+    public void timeDirectFieldSetAndBoxing(int reps) {
+        for (int i = 0; i < reps; i++) {
+            Float value = 0.1f;
+            view.x = value;
+        }
+    }
+
+    public void timeReflectionSetterAndTwoBoxes(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            setX.invoke(view, 0.1f);
+        }
+    }
+
+    public void timeReflectionSetterAndOneBox(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            argsBox[0] = 0.1f;
+            setX.invoke(view, argsBox);
+        }
+    }
+
+    public void timeReflectionFieldSet(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            x.setFloat(view, 0.1f);
+        }
+    }
+
+    public void timeGeneratedSetter(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            generatedSetter.setFloat(view, 0.1f);
+        }
+    }
+
+    public void timeGeneratedFieldSet(int reps) throws Exception {
+        for (int i = 0; i < reps; i++) {
+            generatedField.setFloat(view, 0.1f);
+        }
+    }
+
+    static class View {
+        float x;
+
+        public void setX(float x) {
+            this.x = x;
+        }
+    }
+
+    static interface GeneratedProperty {
+        void setFloat(View v, float f);
+    }
+
+    static class GeneratedSetter implements GeneratedProperty {
+        public void setFloat(View v, float f) {
+            v.setX(f);
+        }
+    }
+
+    static class GeneratedField implements GeneratedProperty {
+        public void setFloat(View v, float f) {
+            v.x = f;
+        }
+    }
+}
diff --git a/benchmarks/regression/RandomBenchmark.java b/benchmarks/regression/RandomBenchmark.java
new file mode 100644
index 0000000..0792805
--- /dev/null
+++ b/benchmarks/regression/RandomBenchmark.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.security.SecureRandom;
+import java.util.Random;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class RandomBenchmark extends SimpleBenchmark {
+    public void timeNewRandom(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Random rng = new Random();
+            rng.nextInt();
+        }
+    }
+
+    public void timeReusedRandom(int reps) throws Exception {
+        Random rng = new Random();
+        for (int i = 0; i < reps; ++i) {
+            rng.nextInt();
+        }
+    }
+
+    public void timeReusedSecureRandom(int reps) throws Exception {
+        SecureRandom rng = new SecureRandom();
+        for (int i = 0; i < reps; ++i) {
+            rng.nextInt();
+        }
+    }
+
+    public void timeNewSecureRandom(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            SecureRandom rng = new SecureRandom();
+            rng.nextInt();
+        }
+    }
+}
diff --git a/benchmarks/regression/RealToStringBenchmark.java b/benchmarks/regression/RealToStringBenchmark.java
new file mode 100644
index 0000000..8914cf64
--- /dev/null
+++ b/benchmarks/regression/RealToStringBenchmark.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class RealToStringBenchmark extends SimpleBenchmark {
+
+    private static final float SMALL  = -123.45f;
+    private static final float MEDIUM = -123.45e8f;
+    private static final float LARGE  = -123.45e36f;
+
+    public void timeFloat_toString_NaN(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(Float.NaN);
+        }
+    }
+
+    public void timeFloat_toString_NEGATIVE_INFINITY(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(Float.NEGATIVE_INFINITY);
+        }
+    }
+
+    public void timeFloat_toString_POSITIVE_INFINITY(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(Float.POSITIVE_INFINITY);
+        }
+    }
+
+    public void timeFloat_toString_zero(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(0.0f);
+        }
+    }
+
+    public void timeFloat_toString_minusZero(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(-0.0f);
+        }
+    }
+
+    public void timeFloat_toString_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(SMALL);
+        }
+    }
+
+    public void timeFloat_toString_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(MEDIUM);
+        }
+    }
+
+    public void timeFloat_toString_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.toString(LARGE);
+        }
+    }
+
+    public void timeStringBuilder_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            new StringBuilder().append(SMALL);
+        }
+    }
+
+    public void timeStringBuilder_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            new StringBuilder().append(MEDIUM);
+        }
+    }
+
+    public void timeStringBuilder_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            new StringBuilder().append(LARGE);
+        }
+    }
+
+    public void timeFormatter_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%f", SMALL);
+        }
+    }
+
+    public void timeFormatter_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%f", MEDIUM);
+        }
+    }
+
+    public void timeFormatter_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%f", LARGE);
+        }
+    }
+
+    public void timeFormatter_dot2f_small(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%.2f", SMALL);
+        }
+    }
+
+    public void timeFormatter_dot2f_medium(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%.2f", MEDIUM);
+        }
+    }
+
+    public void timeFormatter_dot2f_large(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            String.format("%.2f", LARGE);
+        }
+    }
+}
diff --git a/benchmarks/regression/ReflectionBenchmark.java b/benchmarks/regression/ReflectionBenchmark.java
new file mode 100644
index 0000000..fe00fa2
--- /dev/null
+++ b/benchmarks/regression/ReflectionBenchmark.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.lang.reflect.*;
+
+public class ReflectionBenchmark extends SimpleBenchmark {
+    public void timeObject_getClass(int reps) throws Exception {
+        C c = new C();
+        for (int rep = 0; rep < reps; ++rep) {
+            c.getClass();
+        }
+    }
+
+    public void timeClass_getField(int reps) throws Exception {
+        Class<?> klass = C.class;
+        for (int rep = 0; rep < reps; ++rep) {
+            klass.getField("f");
+        }
+    }
+
+    public void timeClass_getDeclaredField(int reps) throws Exception {
+        Class<?> klass = C.class;
+        for (int rep = 0; rep < reps; ++rep) {
+            klass.getDeclaredField("f");
+        }
+    }
+
+    public void timeClass_getConstructor(int reps) throws Exception {
+        Class<?> klass = C.class;
+        for (int rep = 0; rep < reps; ++rep) {
+            klass.getConstructor();
+        }
+    }
+
+    public void timeClass_newInstance(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Constructor constructor = klass.getConstructor();
+        for (int rep = 0; rep < reps; ++rep) {
+            constructor.newInstance();
+        }
+    }
+
+    public void timeClass_getMethod(int reps) throws Exception {
+        Class<?> klass = C.class;
+        for (int rep = 0; rep < reps; ++rep) {
+            klass.getMethod("m");
+        }
+    }
+
+    public void timeClass_getDeclaredMethod(int reps) throws Exception {
+        Class<?> klass = C.class;
+        for (int rep = 0; rep < reps; ++rep) {
+            klass.getDeclaredMethod("m");
+        }
+    }
+
+    public void timeField_setInt(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Field f = klass.getDeclaredField("f");
+        C instance = new C();
+        for (int rep = 0; rep < reps; ++rep) {
+            f.setInt(instance, 1);
+        }
+    }
+
+    public void timeField_getInt(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Field f = klass.getDeclaredField("f");
+        C instance = new C();
+        for (int rep = 0; rep < reps; ++rep) {
+            f.getInt(instance);
+        }
+    }
+
+    public void timeMethod_invokeV(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Method m = klass.getDeclaredMethod("m");
+        C instance = new C();
+        for (int rep = 0; rep < reps; ++rep) {
+            m.invoke(instance);
+        }
+    }
+
+    public void timeMethod_invokeStaticV(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Method m = klass.getDeclaredMethod("sm");
+        for (int rep = 0; rep < reps; ++rep) {
+            m.invoke(null);
+        }
+    }
+
+    public void timeMethod_invokeI(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Method m = klass.getDeclaredMethod("setField", int.class);
+        C instance = new C();
+        for (int rep = 0; rep < reps; ++rep) {
+            m.invoke(instance, 1);
+        }
+    }
+
+    public void timeMethod_invokePreBoxedI(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Method m = klass.getDeclaredMethod("setField", int.class);
+        C instance = new C();
+        Integer one = Integer.valueOf(1);
+        for (int rep = 0; rep < reps; ++rep) {
+            m.invoke(instance, one);
+        }
+    }
+
+    public void timeMethod_invokeStaticI(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Method m = klass.getDeclaredMethod("setStaticField", int.class);
+        for (int rep = 0; rep < reps; ++rep) {
+            m.invoke(null, 1);
+        }
+    }
+
+    public void timeMethod_invokeStaticPreBoxedI(int reps) throws Exception {
+        Class<?> klass = C.class;
+        Method m = klass.getDeclaredMethod("setStaticField", int.class);
+        Integer one = Integer.valueOf(1);
+        for (int rep = 0; rep < reps; ++rep) {
+            m.invoke(null, one);
+        }
+    }
+
+    public void timeRegularMethodInvocation(int reps) throws Exception {
+        C instance = new C();
+        for (int rep = 0; rep < reps; ++rep) {
+            instance.setField(1);
+        }
+    }
+
+    public void timeRegularConstructor(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            new C();
+        }
+    }
+
+    public static class C {
+        public static int sf = 0;
+        public int f = 0;
+
+        public C() {
+            // A non-empty constructor so we don't get optimized away.
+            f = 1;
+        }
+
+        public void m() {
+        }
+
+        public static void sm() {
+        }
+
+        public void setField(int value) {
+            f = value;
+        }
+
+        public static void setStaticField(int value) {
+            sf = value;
+        }
+    }
+}
diff --git a/benchmarks/regression/SSLSocketBenchmark.java b/benchmarks/regression/SSLSocketBenchmark.java
new file mode 100644
index 0000000..fd72a79
--- /dev/null
+++ b/benchmarks/regression/SSLSocketBenchmark.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URL;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+
+public class SSLSocketBenchmark extends SimpleBenchmark {
+
+    private static final int BUFFER_SIZE = 8192;
+
+    final byte[] buffer = new byte[BUFFER_SIZE];
+
+    @Param private WebSite webSite;
+
+    public enum WebSite {
+        DOCS("https://docs.google.com"),
+        MAIL("https://mail.google.com"),
+        SITES("https://sites.google.com"),
+        WWW("https://www.google.com");
+        final InetAddress host;
+        final int port;
+        final byte[] request;
+        WebSite(String uri) {
+            try {
+                URL url = new URL(uri);
+
+                this.host = InetAddress.getByName(url.getHost());
+
+                int p = url.getPort();
+                String portString;
+                if (p == -1) {
+                    this.port = 443;
+                    portString = "";
+                } else {
+                    this.port = p;
+                    portString = ":" + port;
+                }
+
+                this.request = ("GET " + uri + " HTTP/1.0\r\n"
+                                + "Host: " + host + portString + "\r\n"
+                                +"\r\n").getBytes();
+
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Param private Implementation implementation;
+
+    public enum Implementation { OPENSSL, HARMONY };
+
+    private SocketFactory sf;
+
+    @Override protected void setUp() throws Exception {
+        SSLContext sslContext;
+        switch (implementation) {
+            case OPENSSL:
+                sslContext = SSLContext.getInstance("SSL", "AndroidOpenSSL");
+                break;
+            case HARMONY:
+                sslContext = SSLContext.getInstance("SSL", "HarmonyJSSE");
+                break;
+            default:
+                throw new RuntimeException(implementation.toString());
+        }
+        sslContext.init(null, null, null);
+        this.sf = sslContext.getSocketFactory();
+    }
+
+    public void time(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Socket s = sf.createSocket(webSite.host, webSite.port);
+            OutputStream out = s.getOutputStream();
+            out.write(webSite.request);
+            InputStream in = s.getInputStream();
+            while (true) {
+                int n = in.read(buffer);
+                if (n == -1) {
+                    break;
+                }
+            }
+            in.close();
+        }
+    }
+}
diff --git a/benchmarks/regression/SchemePrefixBenchmark.java b/benchmarks/regression/SchemePrefixBenchmark.java
new file mode 100644
index 0000000..d90fe29
--- /dev/null
+++ b/benchmarks/regression/SchemePrefixBenchmark.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class SchemePrefixBenchmark extends SimpleBenchmark {
+
+    enum Strategy {
+        JAVA() {
+            @Override String execute(String spec) {
+                int colon = spec.indexOf(':');
+
+                if (colon < 1) {
+                    return null;
+                }
+
+                for (int i = 0; i < colon; i++) {
+                    char c = spec.charAt(i);
+                    if (!isValidSchemeChar(i, c)) {
+                        return null;
+                    }
+                }
+
+                return spec.substring(0, colon).toLowerCase(Locale.US);
+            }
+
+            private boolean isValidSchemeChar(int index, char c) {
+                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+                    return true;
+                }
+                if (index > 0 && ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')) {
+                    return true;
+                }
+                return false;
+            }
+        },
+
+        REGEX() {
+            private final Pattern pattern = Pattern.compile("^([a-zA-Z][a-zA-Z0-9+\\-.]*):");
+
+            @Override String execute(String spec) {
+                Matcher matcher = pattern.matcher(spec);
+                if (matcher.find()) {
+                    return matcher.group(1).toLowerCase(Locale.US);
+                } else {
+                    return null;
+                }
+            }
+        };
+
+
+        abstract String execute(String spec);
+    }
+
+    @Param Strategy strategy;
+
+    public void timeSchemePrefix(int reps) {
+        for (int i = 0; i < reps; i++) {
+            strategy.execute("http://android.com");
+        }
+    }
+}
diff --git a/benchmarks/regression/SerializationBenchmark.java b/benchmarks/regression/SerializationBenchmark.java
new file mode 100644
index 0000000..bc958eb
--- /dev/null
+++ b/benchmarks/regression/SerializationBenchmark.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class SerializationBenchmark extends SimpleBenchmark {
+    private static byte[] bytes(Object o) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+        ObjectOutputStream out = new ObjectOutputStream(baos);
+        out.writeObject(o);
+        out.close();
+        return baos.toByteArray();
+    }
+
+    public void timeReadIntArray(int reps) throws Exception {
+        int[] intArray = new int[256];
+        readSingleObject(reps, intArray);
+    }
+
+    public void timeWriteIntArray(int reps) throws Exception {
+        int[] intArray = new int[256];
+        writeSingleObject(reps, intArray);
+    }
+    public void timeReadArrayListInteger(int reps) throws Exception {
+        ArrayList<Integer> object = new ArrayList<Integer>();
+        for (int i = 0; i < 256; ++i) {
+            object.add(i);
+        }
+        readSingleObject(reps, object);
+    }
+
+    public void timeWriteArrayListInteger(int reps) throws Exception {
+        ArrayList<Integer> object = new ArrayList<Integer>();
+        for (int i = 0; i < 256; ++i) {
+            object.add(i);
+        }
+        writeSingleObject(reps, object);
+    }
+
+    public void timeReadString(int reps) throws Exception {
+        readSingleObject(reps, "hello");
+    }
+
+    public void timeReadObjectStreamClass(int reps) throws Exception {
+        // A special case because serialization itself requires this class.
+        // (This should really be a unit test.)
+        ObjectStreamClass osc = ObjectStreamClass.lookup(String.class);
+        readSingleObject(reps, osc);
+    }
+
+    public void timeWriteString(int reps) throws Exception {
+        // String is a special case that avoids JNI.
+        writeSingleObject(reps, "hello");
+    }
+
+    public void timeWriteObjectStreamClass(int reps) throws Exception {
+        // A special case because serialization itself requires this class.
+        // (This should really be a unit test.)
+        ObjectStreamClass osc = ObjectStreamClass.lookup(String.class);
+        writeSingleObject(reps, osc);
+    }
+
+    // This is a baseline for the others.
+    public void timeWriteNoObjects(int reps) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+        ObjectOutputStream out = new ObjectOutputStream(baos);
+        for (int rep = 0; rep < reps; ++rep) {
+            out.reset();
+            baos.reset();
+        }
+        out.close();
+    }
+
+    private void readSingleObject(int reps, Object object) throws Exception {
+        byte[] bytes = bytes(object);
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        for (int rep = 0; rep < reps; ++rep) {
+            ObjectInputStream in = new ObjectInputStream(bais);
+            in.readObject();
+            in.close();
+            bais.reset();
+        }
+    }
+
+    private void writeSingleObject(int reps, Object o) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+        ObjectOutputStream out = new ObjectOutputStream(baos);
+        for (int rep = 0; rep < reps; ++rep) {
+            out.writeObject(o);
+            out.reset();
+            baos.reset();
+        }
+        out.close();
+    }
+
+    public void timeWriteEveryKindOfField(int reps) throws Exception {
+        writeSingleObject(reps, new LittleBitOfEverything());
+    }
+    public void timeWriteSerializableBoolean(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableBoolean());
+    }
+    public void timeWriteSerializableByte(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableByte());
+    }
+    public void timeWriteSerializableChar(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableChar());
+    }
+    public void timeWriteSerializableDouble(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableDouble());
+    }
+    public void timeWriteSerializableFloat(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableFloat());
+    }
+    public void timeWriteSerializableInt(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableInt());
+    }
+    public void timeWriteSerializableLong(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableLong());
+    }
+    public void timeWriteSerializableShort(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableShort());
+    }
+    public void timeWriteSerializableReference(int reps) throws Exception {
+        writeSingleObject(reps, new SerializableReference());
+    }
+
+    public void timeReadEveryKindOfField(int reps) throws Exception {
+        readSingleObject(reps, new LittleBitOfEverything());
+    }
+    public void timeReadSerializableBoolean(int reps) throws Exception {
+        readSingleObject(reps, new SerializableBoolean());
+    }
+    public void timeReadSerializableByte(int reps) throws Exception {
+        readSingleObject(reps, new SerializableByte());
+    }
+    public void timeReadSerializableChar(int reps) throws Exception {
+        readSingleObject(reps, new SerializableChar());
+    }
+    public void timeReadSerializableDouble(int reps) throws Exception {
+        readSingleObject(reps, new SerializableDouble());
+    }
+    public void timeReadSerializableFloat(int reps) throws Exception {
+        readSingleObject(reps, new SerializableFloat());
+    }
+    public void timeReadSerializableInt(int reps) throws Exception {
+        readSingleObject(reps, new SerializableInt());
+    }
+    public void timeReadSerializableLong(int reps) throws Exception {
+        readSingleObject(reps, new SerializableLong());
+    }
+    public void timeReadSerializableShort(int reps) throws Exception {
+        readSingleObject(reps, new SerializableShort());
+    }
+    public void timeReadSerializableReference(int reps) throws Exception {
+        readSingleObject(reps, new SerializableReference());
+    }
+
+    public static class SerializableBoolean implements Serializable {
+        boolean z;
+    }
+    public static class SerializableByte implements Serializable {
+        byte b;
+    }
+    public static class SerializableChar implements Serializable {
+        char c;
+    }
+    public static class SerializableDouble implements Serializable {
+        double d;
+    }
+    public static class SerializableFloat implements Serializable {
+        float f;
+    }
+    public static class SerializableInt implements Serializable {
+        int i;
+    }
+    public static class SerializableLong implements Serializable {
+        long j;
+    }
+    public static class SerializableShort implements Serializable {
+        short s;
+    }
+    public static class SerializableReference implements Serializable {
+        Object l;
+    }
+
+    public static class LittleBitOfEverything implements Serializable {
+        boolean z;
+        byte b;
+        char c;
+        double d;
+        float f;
+        int i;
+        long j;
+        short s;
+        Object l;
+    }
+}
diff --git a/benchmarks/regression/SignatureBenchmark.java b/benchmarks/regression/SignatureBenchmark.java
new file mode 100644
index 0000000..383dcfd
--- /dev/null
+++ b/benchmarks/regression/SignatureBenchmark.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLSignature;
+
+/**
+ * Tests RSA and DSA signature creation and verification.
+ */
+public class SignatureBenchmark extends SimpleBenchmark {
+
+    private static final int DATA_SIZE = 8192;
+    private static final byte[] DATA = new byte[DATA_SIZE];
+    static {
+        for (int i = 0; i < DATA_SIZE; i++) {
+            DATA[i] = (byte)i;
+        }
+    }
+    @Param private Algorithm algorithm;
+
+    public enum Algorithm {
+        MD5WithRSA,
+        SHA1WithRSA,
+        SHA256WithRSA,
+        SHA384WithRSA,
+        SHA512WithRSA,
+        SHA1withDSA
+    };
+
+    @Param private Implementation implementation;
+
+    public enum Implementation { OpenSSL, BouncyCastle };
+
+    // Key generation and signing aren't part of the benchmark for verification
+    // so cache the results
+    private static Map<String,KeyPair> KEY_PAIRS = new HashMap<String,KeyPair>();
+    private static Map<String,byte[]> SIGNATURES = new HashMap<String,byte[]>();
+
+    private String signatureAlgorithm;
+    private byte[] signature;
+    private PrivateKey privateKey;
+    private PublicKey publicKey;
+
+    @Override protected void setUp() throws Exception {
+        this.signatureAlgorithm = algorithm.toString();
+
+        String keyAlgorithm = signatureAlgorithm.substring(signatureAlgorithm.length() - 3 ,
+                                                           signatureAlgorithm.length());
+        KeyPair keyPair = KEY_PAIRS.get(keyAlgorithm);
+        if (keyPair == null) {
+            KeyPairGenerator generator = KeyPairGenerator.getInstance(keyAlgorithm);
+            keyPair = generator.generateKeyPair();
+            KEY_PAIRS.put(keyAlgorithm, keyPair);
+        }
+        this.privateKey = keyPair.getPrivate();
+        this.publicKey = keyPair.getPublic();
+
+        this.signature = SIGNATURES.get(signatureAlgorithm);
+        if (this.signature == null) {
+            Signature signer = Signature.getInstance(signatureAlgorithm);
+            signer.initSign(keyPair.getPrivate());
+            signer.update(DATA);
+            this.signature = signer.sign();
+            SIGNATURES.put(signatureAlgorithm, signature);
+        }
+    }
+
+    public void timeSign(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Signature signer;
+            switch (implementation) {
+                case OpenSSL:
+                    signer = Signature.getInstance(signatureAlgorithm, "AndroidOpenSSL");
+                    break;
+                case BouncyCastle:
+                    signer = Signature.getInstance(signatureAlgorithm, "BC");
+                    break;
+                default:
+                    throw new RuntimeException(implementation.toString());
+            }
+            signer.initSign(privateKey);
+            signer.update(DATA);
+            signer.sign();
+        }
+    }
+
+    public void timeVerify(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            Signature verifier;
+            switch (implementation) {
+                case OpenSSL:
+                    verifier = Signature.getInstance(signatureAlgorithm, "AndroidOpenSSL");
+                    break;
+                case BouncyCastle:
+                    verifier = Signature.getInstance(signatureAlgorithm, "BC");
+                    break;
+                default:
+                    throw new RuntimeException(implementation.toString());
+            }
+            verifier.initVerify(publicKey);
+            verifier.update(DATA);
+            verifier.verify(signature);
+        }
+    }
+}
diff --git a/benchmarks/regression/StrictMathBenchmark.java b/benchmarks/regression/StrictMathBenchmark.java
new file mode 100644
index 0000000..44c030a
--- /dev/null
+++ b/benchmarks/regression/StrictMathBenchmark.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Many of these tests are bogus in that the cost will vary wildly depending on inputs.
+ * For _my_ current purposes, that's okay. But beware!
+ */
+public class StrictMathBenchmark extends SimpleBenchmark {
+    private final double d = 1.2;
+    private final float f = 1.2f;
+    private final int i = 1;
+    private final long l = 1L;
+
+    public void timeAbsD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.abs(d);
+        }
+    }
+
+    public void timeAbsF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.abs(f);
+        }
+    }
+
+    public void timeAbsI(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.abs(i);
+        }
+    }
+
+    public void timeAbsL(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.abs(l);
+        }
+    }
+
+    public void timeAcos(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.acos(d);
+        }
+    }
+
+    public void timeAsin(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.asin(d);
+        }
+    }
+
+    public void timeAtan(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.atan(d);
+        }
+    }
+
+    public void timeAtan2(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.atan2(3, 4);
+        }
+    }
+
+    public void timeCbrt(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.cbrt(d);
+        }
+    }
+
+    public void timeCeil(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.ceil(d);
+        }
+    }
+
+    public void timeCopySignD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.copySign(d, d);
+        }
+    }
+
+    public void timeCopySignF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.copySign(f, f);
+        }
+    }
+
+    public void timeCos(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.cos(d);
+        }
+    }
+
+    public void timeCosh(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.cosh(d);
+        }
+    }
+
+    public void timeExp(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.exp(d);
+        }
+    }
+
+    public void timeExpm1(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.expm1(d);
+        }
+    }
+
+    public void timeFloor(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.floor(d);
+        }
+    }
+
+    public void timeGetExponentD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.getExponent(d);
+        }
+    }
+
+    public void timeGetExponentF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.getExponent(f);
+        }
+    }
+
+    public void timeHypot(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.hypot(d, d);
+        }
+    }
+
+    public void timeIEEEremainder(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.IEEEremainder(d, d);
+        }
+    }
+
+    public void timeLog(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.log(d);
+        }
+    }
+
+    public void timeLog10(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.log10(d);
+        }
+    }
+
+    public void timeLog1p(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.log1p(d);
+        }
+    }
+
+    public void timeMaxD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.max(d, d);
+        }
+    }
+
+    public void timeMaxF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.max(f, f);
+        }
+    }
+
+    public void timeMaxI(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.max(i, i);
+        }
+    }
+
+    public void timeMaxL(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.max(l, l);
+        }
+    }
+
+    public void timeMinD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.min(d, d);
+        }
+    }
+
+    public void timeMinF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.min(f, f);
+        }
+    }
+
+    public void timeMinI(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.min(i, i);
+        }
+    }
+
+    public void timeMinL(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.min(l, l);
+        }
+    }
+
+    public void timeNextAfterD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.nextAfter(d, d);
+        }
+    }
+
+    public void timeNextAfterF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.nextAfter(f, f);
+        }
+    }
+
+    public void timeNextUpD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.nextUp(d);
+        }
+    }
+
+    public void timeNextUpF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.nextUp(f);
+        }
+    }
+
+    public void timePow(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.pow(d, d);
+        }
+    }
+
+    public void timeRandom(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.random();
+        }
+    }
+
+    public void timeRint(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.rint(d);
+        }
+    }
+
+    public void timeRoundD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.round(d);
+        }
+    }
+
+    public void timeRoundF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.round(f);
+        }
+    }
+
+    public void timeScalbD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.scalb(d, 5);
+        }
+    }
+
+    public void timeScalbF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.scalb(f, 5);
+        }
+    }
+
+    public void timeSignumD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.signum(d);
+        }
+    }
+
+    public void timeSignumF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.signum(f);
+        }
+    }
+
+    public void timeSin(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.sin(d);
+        }
+    }
+
+    public void timeSinh(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.sinh(d);
+        }
+    }
+
+    public void timeSqrt(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.sqrt(d);
+        }
+    }
+
+    public void timeTan(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.tan(d);
+        }
+    }
+
+    public void timeTanh(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.tanh(d);
+        }
+    }
+
+    public void timeToDegrees(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.toDegrees(d);
+        }
+    }
+
+    public void timeToRadians(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.toRadians(d);
+        }
+    }
+
+    public void timeUlpD(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.ulp(d);
+        }
+    }
+
+    public void timeUlpF(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            StrictMath.ulp(f);
+        }
+    }
+}
diff --git a/benchmarks/regression/StringBenchmark.java b/benchmarks/regression/StringBenchmark.java
new file mode 100644
index 0000000..e09ee8b
--- /dev/null
+++ b/benchmarks/regression/StringBenchmark.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class StringBenchmark extends SimpleBenchmark {
+    enum StringLengths {
+        EMPTY(""),
+        SHORT("short"),
+        EIGHTY(makeString(80)),
+        EIGHT_KI(makeString(8192));
+        final String value;
+        private StringLengths(String value) { this.value = value; }
+    }
+    @Param private StringLengths s;
+
+    private static String makeString(int length) {
+        StringBuilder result = new StringBuilder(length);
+        for (int i = 0; i < length; ++i) {
+            result.append((char) i);
+        }
+        return result.toString();
+    }
+
+    public void timeHashCode(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            s.value.hashCode();
+        }
+    }
+}
diff --git a/benchmarks/regression/StringBuilderBenchmark.java b/benchmarks/regression/StringBuilderBenchmark.java
new file mode 100644
index 0000000..79eff2a
--- /dev/null
+++ b/benchmarks/regression/StringBuilderBenchmark.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+/**
+ * Tests the performance of various StringBuilder methods.
+ */
+public class StringBuilderBenchmark extends SimpleBenchmark {
+
+    @Param({"1", "10", "100"}) private int length;
+
+    public void timeAppendBoolean(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(true);
+            }
+        }
+    }
+
+    public void timeAppendChar(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append('c');
+            }
+        }
+    }
+
+    public void timeAppendCharArray(int reps) {
+        char[] chars = "chars".toCharArray();
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(chars);
+            }
+        }
+    }
+
+    public void timeAppendCharSequence(int reps) {
+        CharSequence cs = "chars";
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(cs);
+            }
+        }
+    }
+
+    public void timeAppendDouble(int reps) {
+        double d = 1.2;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(d);
+            }
+        }
+    }
+
+    public void timeAppendFloat(int reps) {
+        float f = 1.2f;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(f);
+            }
+        }
+    }
+
+    public void timeAppendInt(int reps) {
+        int n = 123;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(n);
+            }
+        }
+    }
+
+    public void timeAppendLong(int reps) {
+        long l = 123;
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(l);
+            }
+        }
+    }
+
+    public void timeAppendObject(int reps) {
+        // We don't want to time the toString, so ensure we're calling a trivial one...
+        Object o = new Object() {
+            @Override public String toString() {
+                return "constant";
+            }
+        };
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(o);
+            }
+        }
+    }
+
+    public void timeAppendString(int reps) {
+        String s = "chars";
+        for (int i = 0; i < reps; ++i) {
+            StringBuilder sb = new StringBuilder();
+            for (int j = 0; j < length; ++j) {
+                sb.append(s);
+            }
+        }
+    }
+}
diff --git a/benchmarks/regression/StringCaseMappingBenchmark.java b/benchmarks/regression/StringCaseMappingBenchmark.java
new file mode 100644
index 0000000..ba5b59e
--- /dev/null
+++ b/benchmarks/regression/StringCaseMappingBenchmark.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import java.util.Locale;
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+
+public class StringCaseMappingBenchmark extends SimpleBenchmark {
+    enum Inputs {
+        EMPTY(""),
+
+        // TODO: include hairy inputs like turkish and greek.
+        // TODO: locale makes a difference too.
+
+        LOWER2(lower(2)),
+        UPPER2(upper(2)),
+        MIXED2(mixed(2)),
+
+        LOWER8(lower(8)),
+        UPPER8(upper(8)),
+        MIXED8(mixed(8)),
+
+        LOWER32(lower(32)),
+        UPPER32(upper(32)),
+        MIXED32(mixed(32)),
+
+        LOWER512(lower(512)),
+        UPPER512(upper(512)),
+        MIXED512(mixed(512)),
+
+        LOWER2048(lower(2048)),
+        UPPER2048(upper(2048)),
+        MIXED2048(mixed(2048)),
+
+        LOWER_1M(lower(1024*1024)),
+        UPPER_1M(upper(1024*1024)),
+        MIXED_1M(mixed(1024*1024));
+
+        final String value;
+        private Inputs(String value) { this.value = value; }
+        private static String lower(int length) {
+            return makeString(length, "a0b1c2d3e4f5g6h7i8j9klmnopqrstuvwxyz");
+        }
+        private static String upper(int length) {
+            return makeString(length, "A0B1C2D3E4F5G6H7I8J9KLMNOPQRSTUVWXYZ");
+        }
+        private static String mixed(int length) {
+            return makeString(length, "Aa0Bb1Cc2Dd3Ee4Ff5Gg6Hh7Ii8Jj9KkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz");
+        }
+        private static String makeString(int length, String alphabet) {
+            StringBuilder sb = new StringBuilder(length);
+            for (int i = 0; i < length; ++i) {
+                sb.append(alphabet.charAt(i % alphabet.length()));
+            }
+            return sb.toString();
+        }
+    }
+    @Param private Inputs s;
+
+    public void timeToUpperCase_US(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            s.value.toUpperCase(Locale.US);
+        }
+    }
+
+    public void timeToLowerCase_US(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            s.value.toUpperCase(Locale.US);
+        }
+    }
+
+    public void timeToUpperCase_Ascii(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            toUpperCaseAscii(s.value);
+        }
+    }
+
+    public void timeToLowerCase_Ascii(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            toUpperCaseAscii(s.value);
+        }
+    }
+
+    public void timeToUpperCase_ICU(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            libcore.icu.ICU.toUpperCase(s.value, Locale.US.toString());
+        }
+    }
+
+    public void timeToLowerCase_ICU(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            libcore.icu.ICU.toLowerCase(s.value, Locale.US.toString());
+        }
+    }
+
+    public static String toUpperCaseAscii(String s) {
+        for (int i = 0, length = s.length(); i < length; i++) {
+            char c = s.charAt(i);
+            if (c < 'a' || c > 'z') {
+                continue; // fast path avoids allocation
+            }
+
+            // slow path: s contains lower case chars
+            char[] result = s.toCharArray();
+            for (; i < length; i++) {
+                c = result[i];
+                if (c >= 'a' && c <= 'z') {
+                    result[i] -= ('a' - 'A');
+                }
+            }
+            return new String(result);
+        }
+        return s;
+    }
+
+    public static String toLowerCaseAscii(String s) {
+        for (int i = 0, length = s.length(); i < length; i++) {
+            char c = s.charAt(i);
+            if (c < 'A' || c > 'Z') {
+                continue; // fast path avoids allocation
+            }
+
+            // slow path: s contains upper case chars
+            char[] result = s.toCharArray();
+            for (; i < length; i++) {
+                c = result[i];
+                if (c >= 'A' && c <= 'Z') {
+                    result[i] += ('a' - 'A');
+                }
+            }
+            return new String(result);
+        }
+        return s;
+    }
+}
diff --git a/benchmarks/regression/StringIsEmptyBenchmark.java b/benchmarks/regression/StringIsEmptyBenchmark.java
new file mode 100644
index 0000000..a3be80a
--- /dev/null
+++ b/benchmarks/regression/StringIsEmptyBenchmark.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class StringIsEmptyBenchmark extends SimpleBenchmark {
+    public void timeIsEmpty_NonEmpty(int reps) {
+        boolean result = true;
+        for (int i = 0; i < reps; ++i) {
+            result &= !("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".isEmpty());
+        }
+        if (!result) throw new RuntimeException();
+    }
+    
+    public void timeIsEmpty_Empty(int reps) {
+        boolean result = true;
+        for (int i = 0; i < reps; ++i) {
+            result &= ("".isEmpty());
+        }
+        if (!result) throw new RuntimeException();
+    }
+    
+    public void timeLengthEqualsZero(int reps) {
+        boolean result = true;
+        for (int i = 0; i < reps; ++i) {
+            result &= !("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".length() == 0);
+        }
+        if (!result) throw new RuntimeException();
+    }
+ 
+    public void timeEqualsEmpty(int reps) {
+        boolean result = true;
+        for (int i = 0; i < reps; ++i) {
+            result &= !"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".equals("");
+        }
+        if (!result) throw new RuntimeException();
+    }
+}
diff --git a/benchmarks/regression/StringLengthBenchmark.java b/benchmarks/regression/StringLengthBenchmark.java
new file mode 100644
index 0000000..962e395
--- /dev/null
+++ b/benchmarks/regression/StringLengthBenchmark.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class StringLengthBenchmark extends SimpleBenchmark {
+    public void timeLength(int reps) {
+        int length = 0;
+        for (int i = 0; i < reps; ++i) {
+            length = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".length();
+        }
+        if (length != 51) throw new RuntimeException();
+    }
+}
diff --git a/benchmarks/regression/StringSplitBenchmark.java b/benchmarks/regression/StringSplitBenchmark.java
new file mode 100644
index 0000000..37fdf8f
--- /dev/null
+++ b/benchmarks/regression/StringSplitBenchmark.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.SimpleBenchmark;
+import java.util.regex.Pattern;
+
+public class StringSplitBenchmark extends SimpleBenchmark {
+    public void timeStringSplitComma(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            "this,is,a,simple,example".split(",");
+        }
+    }
+
+    public void timeStringSplitLiteralDot(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            "this.is.a.simple.example".split("\\.");
+        }
+    }
+
+    public void timeStringSplitNewline(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            "this\nis\na\nsimple\nexample\n".split("\n");
+        }
+    }
+
+    public void timePatternSplitComma(int reps) {
+        Pattern p = Pattern.compile(",");
+        for (int i = 0; i < reps; ++i) {
+            p.split("this,is,a,simple,example");
+        }
+    }
+
+    public void timePatternSplitLiteralDot(int reps) {
+        Pattern p = Pattern.compile("\\.");
+        for (int i = 0; i < reps; ++i) {
+            p.split("this.is.a.simple.example");
+        }
+    }
+
+    public void timeStringSplitHard(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            "this,is,a,harder,example".split("[,]");
+        }
+    }
+}
diff --git a/benchmarks/regression/StringToRealBenchmark.java b/benchmarks/regression/StringToRealBenchmark.java
new file mode 100644
index 0000000..ef7a633
--- /dev/null
+++ b/benchmarks/regression/StringToRealBenchmark.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class StringToRealBenchmark extends SimpleBenchmark {
+
+    @Param({
+        "NaN",
+        "-1",
+        "0",
+        "1",
+        "1.2",
+        "-123.45",
+        "-123.45e8",
+        "-123.45e36"
+    }) String string;
+
+    public void timeFloat_parseFloat(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Float.parseFloat(string);
+        }
+    }
+
+    public void timeDouble_parseDouble(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            Double.parseDouble(string);
+        }
+    }
+}
diff --git a/benchmarks/regression/SystemPropertiesBenchmark.java b/benchmarks/regression/SystemPropertiesBenchmark.java
new file mode 100644
index 0000000..9c675e1
--- /dev/null
+++ b/benchmarks/regression/SystemPropertiesBenchmark.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.SimpleBenchmark;
+
+public class SystemPropertiesBenchmark extends SimpleBenchmark {
+    public void timeGetProperties(int reps) throws Exception {
+        for (int i = 0; i < reps; ++i) {
+            // Force the properties to be recreated.
+            System.setProperties(null);
+            System.getProperties();
+        }
+    }
+}
diff --git a/benchmarks/regression/ThreadLocalBenchmark.java b/benchmarks/regression/ThreadLocalBenchmark.java
new file mode 100644
index 0000000..df0715b
--- /dev/null
+++ b/benchmarks/regression/ThreadLocalBenchmark.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.Runner;
+import com.google.caliper.SimpleBenchmark;
+
+public class ThreadLocalBenchmark extends SimpleBenchmark {
+    private static final ThreadLocal<char[]> BUFFER = new ThreadLocal<char[]>() {
+        @Override protected char[] initialValue() {
+            return new char[20];
+        }
+    };
+
+    public void timeThreadLocal_get(int reps) {
+        for (int rep = 0; rep < reps; ++rep) {
+            BUFFER.get();
+        }
+    }
+}
diff --git a/benchmarks/regression/TimeZoneBenchmark.java b/benchmarks/regression/TimeZoneBenchmark.java
new file mode 100644
index 0000000..ccef392
--- /dev/null
+++ b/benchmarks/regression/TimeZoneBenchmark.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.SimpleBenchmark;
+import java.util.TimeZone;
+
+public class TimeZoneBenchmark extends SimpleBenchmark {
+    public void timeTimeZone_getDefault(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            TimeZone.getDefault();
+        }
+    }
+
+    public void timeTimeZone_getTimeZoneUTC(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            TimeZone.getTimeZone("UTC");
+        }
+    }
+
+    public void timeTimeZone_getTimeZone_default(int reps) throws Exception {
+        String defaultId = TimeZone.getDefault().getID();
+        for (int rep = 0; rep < reps; ++rep) {
+            TimeZone.getTimeZone(defaultId);
+        }
+    }
+
+    // A time zone with relatively few transitions.
+    public void timeTimeZone_getTimeZone_America_Caracas(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            TimeZone.getTimeZone("America/Caracas");
+        }
+    }
+
+    // A time zone with a lot of transitions.
+    public void timeTimeZone_getTimeZone_America_Santiago(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            TimeZone.getTimeZone("America/Santiago");
+        }
+    }
+
+    public void timeTimeZone_getTimeZone_GMT_plus_10(int reps) throws Exception {
+        for (int rep = 0; rep < reps; ++rep) {
+            TimeZone.getTimeZone("GMT+10");
+        }
+    }
+}
diff --git a/benchmarks/regression/URLConnectionBenchmark.java b/benchmarks/regression/URLConnectionBenchmark.java
new file mode 100644
index 0000000..e5ceec4
--- /dev/null
+++ b/benchmarks/regression/URLConnectionBenchmark.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import tests.http.MockResponse;
+import tests.http.MockWebServer;
+
+public final class URLConnectionBenchmark extends SimpleBenchmark {
+
+    @Param({"0", "1024", "1048576"}) private int bodySize;
+    @Param({"2048"}) private int chunkSize;
+    @Param({"1024"}) private int readBufferSize;
+    @Param private ResponseHeaders responseHeaders;
+    @Param private TransferEncoding transferEncoding;
+    private byte[] readBuffer;
+
+    private MockWebServer server;
+    private URL url;
+
+    protected void setUp() throws Exception {
+        readBuffer = new byte[readBufferSize];
+        server = new MockWebServer();
+
+        MockResponse response = new MockResponse();
+        responseHeaders.apply(response);
+        transferEncoding.setBody(response, bodySize, chunkSize);
+        server.enqueue(response);
+
+        server.setContinuousServing(true);
+        server.play();
+
+        url = server.getUrl("/");
+        get(); // ensure the server has started its threads, etc.
+    }
+
+    protected void tearDown() throws Exception {
+        /*
+         * Entice the server to shut itself down gracefully. The shutdown method
+         * doesn't work on Dalvik because socket.close() doesn't release blocked
+         * threads. Instead, read the last continuously-served request, and then
+         * cause the server to close the otherwise-reusable HTTP connection.
+         */
+        server.setContinuousServing(false);
+        server.enqueue(new MockResponse().setDisconnectAtEnd(true));
+        get();
+        get();
+        server.shutdown();
+    }
+
+    public int timeGet(int reps) throws IOException {
+        int totalBytesRead = 0;
+        for (int i = 0; i < reps; i++) {
+            totalBytesRead += get();
+        }
+        return totalBytesRead;
+    }
+
+    private int get() throws IOException {
+        int totalBytesRead = 0;
+        URLConnection connection = url.openConnection();
+        InputStream in = connection.getInputStream();
+        int count;
+        while ((count = in.read(readBuffer)) != -1) {
+            totalBytesRead += count;
+        }
+        return totalBytesRead;
+    }
+
+    enum TransferEncoding {
+        FIXED_LENGTH,
+        CHUNKED;
+
+        void setBody(MockResponse response, int bodySize, int chunkSize) throws IOException {
+            if (this == TransferEncoding.FIXED_LENGTH) {
+                response.setBody(new byte[bodySize]);
+            } else if (this == TransferEncoding.CHUNKED) {
+                response.setChunkedBody(new byte[bodySize], chunkSize);
+            }
+        }
+    }
+
+    enum ResponseHeaders {
+        MINIMAL,
+        TYPICAL;
+
+        void apply(MockResponse response) {
+            if (this == TYPICAL) {
+                /* from http://api.twitter.com/1/statuses/public_timeline.json */
+                response.addHeader("Date: Wed, 30 Jun 2010 17:57:39 GMT");
+                response.addHeader("Server: hi");
+                response.addHeader("X-RateLimit-Remaining: 0");
+                response.addHeader("X-Runtime: 0.01637");
+                response.addHeader("Content-Type: application/json; charset=utf-8");
+                response.addHeader("X-RateLimit-Class: api_whitelisted");
+                response.addHeader("Cache-Control: no-cache, max-age=300");
+                response.addHeader("X-RateLimit-Reset: 1277920980");
+                response.addHeader("Set-Cookie: _twitter_sess=BAh7EDoOcmV0dXJuX3RvIjZodHRwOi8vZGV2L"
+                        + "nR3aXR0ZXIuY29tL3BhZ2Vz%250AL3NpZ25faW5fd2l0aF90d2l0dGVyOgxjc3JmX2lkIiUw"
+                        + "ODFhNGY2NTM5NjRm%250ANjY1N2M2NzcwNWI0MDlmZGZjZjoVaW5fbmV3X3VzZXJfZmxvdzA"
+                        + "6EXRyYW5z%250AX3Byb21wdDAiKXNob3dfZGlzY292ZXJhYmlsaXR5X2Zvcl9qZXNzZXdpbH"
+                        + "Nv%250AbjA6E3Nob3dfaGVscF9saW5rMDoTcGFzc3dvcmRfdG9rZW4iLWUyYjlhNmM3%250A"
+                        + "MWJiNzI3NWNlZDI1NDY3MGMzZWNmMTE0MjI4N2EyNGE6D2NyZWF0ZWRfYXRs%250AKwhiM%2"
+                        + "52F6JKQE6CXVzZXJpA8tE3iIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxl%250Acjo6Rmxhc2"
+                        + "g6OkZsYXNoSGFzaHsABjoKQHVzZWR7ADoHaWQiJWZmMTNhM2Qx%250AZTU1YTkzMmYyMWM0M"
+                        + "GNhZjU4NDVjMTQz--11250628c85830219438eb7eba96a541a9af4098; domain=.twitt"
+                        + "er.com; path=/");
+                response.addHeader("Expires: Wed, 30 Jun 2010 18:02:39 GMT");
+                response.addHeader("Vary: Accept-Encoding");
+            }
+        }
+    }
+}
diff --git a/benchmarks/regression/XmlEntitiesBenchmark.java b/benchmarks/regression/XmlEntitiesBenchmark.java
new file mode 100644
index 0000000..4c5cc18
--- /dev/null
+++ b/benchmarks/regression/XmlEntitiesBenchmark.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import java.io.StringReader;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.xml.sax.InputSource;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+// http://code.google.com/p/android/issues/detail?id=18102
+public final class XmlEntitiesBenchmark extends SimpleBenchmark {
+  
+  @Param({"10", "100", "1000"}) int length;
+  @Param({"0", "0.5", "1.0"}) float entityFraction;
+
+  private XmlPullParserFactory xmlPullParserFactory;
+  private DocumentBuilderFactory documentBuilderFactory;
+
+  /** a string like {@code <doc>&amp;&amp;++</doc>}. */
+  private String xml;
+
+  @Override protected void setUp() throws Exception {
+    xmlPullParserFactory = XmlPullParserFactory.newInstance();
+    documentBuilderFactory = DocumentBuilderFactory.newInstance();
+
+    StringBuilder xmlBuilder = new StringBuilder();
+    xmlBuilder.append("<doc>");
+    for (int i = 0; i < (length * entityFraction); i++) {
+      xmlBuilder.append("&amp;");
+    }
+    while (xmlBuilder.length() < length) {
+      xmlBuilder.append("+");
+    }
+    xmlBuilder.append("</doc>");
+    xml = xmlBuilder.toString();
+  }
+  
+  public void timeXmlParser(int reps) throws Exception {
+    for (int i = 0; i < reps; i++) {
+      XmlPullParser parser = xmlPullParserFactory.newPullParser();
+      parser.setInput(new StringReader(xml));
+      while (parser.next() != XmlPullParser.END_DOCUMENT) {
+      }
+    }
+  }
+  
+  public void timeDocumentBuilder(int reps) throws Exception {
+    for (int i = 0; i < reps; i++) {
+      DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+      documentBuilder.parse(new InputSource(new StringReader(xml)));
+    }
+  }
+}
diff --git a/dalvik/src/main/java/dalvik/annotation/package.html b/dalvik/src/main/java/dalvik/annotation/package.html
deleted file mode 100644
index 2a67dc0..0000000
--- a/dalvik/src/main/java/dalvik/annotation/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<html>
-  <body>
-    <p>
-      Defines some annotations that are used within the Android system, either
-      for the core libraries or for testing purposes.
-    </p>
-  </body>
-</html>
diff --git a/dalvik/src/main/java/dalvik/bytecode/package.html b/dalvik/src/main/java/dalvik/bytecode/package.html
deleted file mode 100644
index 92b0e92..0000000
--- a/dalvik/src/main/java/dalvik/bytecode/package.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<html>
-  <body>
-    <p>
-      Provides classes related to Dalvik bytecode. 
-    </p>
-  </body>
-</html>
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
index 62ec5e3..d3ec95a 100644
--- a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
+++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@@ -25,13 +25,6 @@
  * {@link ClassLoader} implementations.
  */
 public class BaseDexClassLoader extends ClassLoader {
-    /** originally specified path (just used for {@code toString()}) */
-    private final String originalPath;
-
-    /** originally specified library path (just used for {@code toString()}) */
-    private final String originalLibraryPath;
-
-    /** structured lists of path elements */
     private final DexPathList pathList;
 
     /**
@@ -50,22 +43,16 @@
     public BaseDexClassLoader(String dexPath, File optimizedDirectory,
             String libraryPath, ClassLoader parent) {
         super(parent);
-
-        this.originalPath = dexPath;
-        this.originalLibraryPath = libraryPath;
-        this.pathList =
-            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
+        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
     }
 
     @Override
     protected Class<?> findClass(String name) throws ClassNotFoundException {
-        Class clazz = pathList.findClass(name);
-
-        if (clazz == null) {
-            throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + originalPath);
+        Class c = pathList.findClass(name);
+        if (c == null) {
+            throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
         }
-
-        return clazz;
+        return c;
     }
 
     @Override
@@ -125,9 +112,21 @@
         return null;
     }
 
-    @Override
-    public String toString() {
-        return getClass().getName()
-                + "[dexPath=" + originalPath + ",libraryPath=" + originalLibraryPath + "]";
+    /**
+     * @hide
+     */
+    public String getLdLibraryPath() {
+        StringBuilder result = new StringBuilder();
+        for (File directory : pathList.getNativeLibraryDirectories()) {
+            if (result.length() > 0) {
+                result.append(':');
+            }
+            result.append(directory);
+        }
+        return result.toString();
+    }
+
+    @Override public String toString() {
+        return getClass().getName() + "[" + pathList + "]";
     }
 }
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index ba4bb15..3d9ee3e 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -21,10 +21,16 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.regex.Pattern;
 import java.util.zip.ZipFile;
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.StructStat;
+import static libcore.io.OsConstants.*;
 
 /**
  * A pair of lists of entries, associated with a {@code ClassLoader}.
@@ -48,10 +54,14 @@
     /** class definition context */
     private final ClassLoader definingContext;
 
-    /** list of dex/resource (class path) elements */
+    /**
+     * List of dex/resource (class path) elements.
+     * Should be called pathElements, but the Facebook app uses reflection
+     * to modify 'dexElements' (http://b/7726934).
+     */
     private final Element[] dexElements;
 
-    /** list of native library directory elements */
+    /** List of native library directories. */
     private final File[] nativeLibraryDirectories;
 
     /**
@@ -93,11 +103,22 @@
         }
 
         this.definingContext = definingContext;
-        this.dexElements =
-            makeDexElements(splitDexPath(dexPath), optimizedDirectory);
+        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
         this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
     }
 
+    @Override public String toString() {
+        return "DexPathList[" + Arrays.toString(dexElements) +
+            ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]";
+    }
+
+    /**
+     * For BaseDexClassLoader.getLdLibraryPath.
+     */
+    public File[] getNativeLibraryDirectories() {
+        return nativeLibraryDirectories;
+    }
+
     /**
      * Splits the given dex path string into elements using the path
      * separator, pruning out any elements that do not refer to existing
@@ -154,36 +175,19 @@
      * Helper for {@link #splitPaths}, which does the actual splitting
      * and filtering and adding to a result.
      */
-    private static void splitAndAdd(String path, boolean wantDirectories,
+    private static void splitAndAdd(String searchPath, boolean directoriesOnly,
             ArrayList<File> resultList) {
-        if (path == null) {
+        if (searchPath == null) {
             return;
         }
-
-        String[] strings = path.split(Pattern.quote(File.pathSeparator));
-
-        for (String s : strings) {
-            File file = new File(s);
-
-            if (! (file.exists() && file.canRead())) {
-                continue;
-            }
-
-            /*
-             * Note: There are other entities in filesystems than
-             * regular files and directories.
-             */
-            if (wantDirectories) {
-                if (!file.isDirectory()) {
-                    continue;
+        for (String path : searchPath.split(":")) {
+            try {
+                StructStat sb = Libcore.os.stat(path);
+                if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
+                    resultList.add(new File(path));
                 }
-            } else {
-                if (!file.isFile()) {
-                    continue;
-                }
+            } catch (ErrnoException ignored) {
             }
-
-            resultList.add(file);
         }
     }
 
@@ -226,12 +230,16 @@
                      * the exception here, and let dex == null.
                      */
                 }
+            } else if (file.isDirectory()) {
+                // We support directories for looking up resources.
+                // This is only useful for running libcore tests.
+                elements.add(new Element(file, true, null, null));
             } else {
                 System.logW("Unknown file type for: " + file);
             }
 
             if ((zip != null) || (dex != null)) {
-                elements.add(new Element(file, zip, dex));
+                elements.add(new Element(file, false, zip, dex));
             }
         }
 
@@ -360,14 +368,12 @@
      */
     public String findLibrary(String libraryName) {
         String fileName = System.mapLibraryName(libraryName);
-
         for (File directory : nativeLibraryDirectories) {
-            File file = new File(directory, fileName);
-            if (file.exists() && file.isFile() && file.canRead()) {
-                return file.getPath();
+            String path = new File(directory, fileName).getPath();
+            if (IoUtils.canOpenReadOnly(path)) {
+                return path;
             }
         }
-
         return null;
     }
 
@@ -376,31 +382,38 @@
      */
     /*package*/ static class Element {
         private final File file;
+        private final boolean isDirectory;
         private final File zip;
         private final DexFile dexFile;
 
         private ZipFile zipFile;
-        private boolean init;
+        private boolean initialized;
 
-        public Element(File file, File zip, DexFile dexFile) {
+        public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
             this.file = file;
+            this.isDirectory = isDirectory;
             this.zip = zip;
             this.dexFile = dexFile;
         }
 
+        @Override public String toString() {
+            if (isDirectory) {
+                return "directory \"" + file + "\"";
+            } else if (zip != null) {
+                return "zip file \"" + zip + "\"";
+            } else {
+                return "dex file \"" + dexFile + "\"";
+            }
+        }
+
         public synchronized void maybeInit() {
-            if (init) {
+            if (initialized) {
                 return;
             }
 
-            init = true;
+            initialized = true;
 
-            if (zip == null) {
-                /*
-                 * Either this element has no zip/jar file (first
-                 * clause), or the zip/jar file doesn't have an entry
-                 * for the given name (second clause).
-                 */
+            if (isDirectory || zip == null) {
                 return;
             }
 
@@ -421,7 +434,25 @@
         public URL findResource(String name) {
             maybeInit();
 
+            // We support directories so we can run tests and/or legacy code
+            // that uses Class.getResource.
+            if (isDirectory) {
+                File resourceFile = new File(file, name);
+                if (resourceFile.exists()) {
+                    try {
+                        return resourceFile.toURI().toURL();
+                    } catch (MalformedURLException ex) {
+                        throw new RuntimeException(ex);
+                    }
+                }
+            }
+
             if (zipFile == null || zipFile.getEntry(name) == null) {
+                /*
+                 * Either this element has no zip/jar file (first
+                 * clause), or the zip/jar file doesn't have an entry
+                 * for the given name (second clause).
+                 */
                 return null;
             }
 
diff --git a/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java b/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java
index ff0f759..f8fb0b1 100644
--- a/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java
+++ b/dalvik/src/main/java/dalvik/system/TemporaryDirectory.java
@@ -19,79 +19,20 @@
 import java.io.File;
 
 /**
- * Utility class to handle the setup of the core library's concept of
- * what the "default temporary directory" is. Application code may
- * call into this class with an appropriate base directory during its
- * startup, as a reasonably easy way to get the standard property
- * <code>java.io.tmpdir</code> to point at something useful.
+ * Obsolete, for binary compatibility only.
  *
  * @hide
  */
 public class TemporaryDirectory {
-    /** system property name for the temporary directory */
-    private static final String PROPERTY = "java.io.tmpdir";
-
-    /** final path component name for the temporary directory */
-    private static final String PATH_NAME = "tmp";
-
-    /** whether a temporary directory has been configured yet */
-    private static boolean configured = false;
-
     /**
-     * Convenience method which is equivalent to
-     * <code>setupDirectory(new File(baseDir))</code>.
-     *
-     * @param baseDir the base directory of the temporary directory
+     * This method exists for binary compatibility only.
      */
     public static void setUpDirectory(String baseDir) {
-        setUpDirectory(new File(baseDir));
     }
 
     /**
-     * Sets up the temporary directory, but only if one isn't already
-     * defined for this process, and only if it is possible (e.g., the
-     * directory already exists and is read-write, or the directory
-     * can be created). This call will do one of three things:
-     *
-     * <ul>
-     * <li>return without error and without doing anything, if a
-     * previous call to this method succeeded</li>
-     * <li>return without error, having either created a temporary
-     * directory under the given base or verified that such a directory
-     * already exists</li>
-     * <li>throw <code>UnsupportedOperationException</code> if the
-     * directory could not be created or accessed</li>
-     * </ul>
-     *
-     * @param baseDir the base directory of the temporary directory
+     * This method exists for binary compatibility only.
      */
     public static synchronized void setUpDirectory(File baseDir) {
-        if (configured) {
-            System.logE("Already set to: " + System.getProperty(PROPERTY));
-            return;
-        }
-
-        File dir = new File(baseDir, PATH_NAME);
-        String absolute = dir.getAbsolutePath();
-
-        if (dir.exists()) {
-            if (!dir.isDirectory()) {
-                throw new UnsupportedOperationException(
-                        "Name is used by a non-directory file: " +
-                        absolute);
-            } else if (!(dir.canRead() && dir.canWrite())) {
-                throw new UnsupportedOperationException(
-                        "Existing directory is not readable and writable: " +
-                        absolute);
-            }
-        } else {
-            if (!dir.mkdirs()) {
-                throw new UnsupportedOperationException(
-                        "Failed to create directory: " + absolute);
-            }
-        }
-
-        System.setProperty(PROPERTY, absolute);
-        configured = true;
     }
 }
diff --git a/dalvik/src/main/java/dalvik/system/VMDebug.java b/dalvik/src/main/java/dalvik/system/VMDebug.java
index ace149c..8f40165 100644
--- a/dalvik/src/main/java/dalvik/system/VMDebug.java
+++ b/dalvik/src/main/java/dalvik/system/VMDebug.java
@@ -165,11 +165,10 @@
      * @param flags flags to control method tracing. The only one that
      * is currently defined is {@link #TRACE_COUNT_ALLOCS}.
      */
-    public static void startMethodTracing(String traceFileName,
-        int bufferSize, int flags) {
+    public static void startMethodTracing(String traceFileName, int bufferSize, int flags) {
 
         if (traceFileName == null) {
-            throw new NullPointerException();
+            throw new NullPointerException("traceFileName == null");
         }
 
         startMethodTracingNative(traceFileName, null, bufferSize, flags);
@@ -183,8 +182,11 @@
     public static void startMethodTracing(String traceFileName,
         FileDescriptor fd, int bufferSize, int flags)
     {
-        if (traceFileName == null || fd == null) {
-            throw new NullPointerException();
+        if (traceFileName == null) {
+            throw new NullPointerException("traceFileName == null");
+        }
+        if (fd == null) {
+            throw new NullPointerException("fd == null");
         }
 
         startMethodTracingNative(traceFileName, fd, bufferSize, flags);
@@ -291,15 +293,16 @@
      *
      * The VM may create a temporary file in the same directory.
      *
-     * @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
+     * @param filename Full pathname of output file (e.g. "/sdcard/dump.hprof").
      * @throws UnsupportedOperationException if the VM was built without
      *         HPROF support.
      * @throws IOException if an error occurs while opening or writing files.
      */
-    public static void dumpHprofData(String fileName) throws IOException {
-        if (fileName == null)
-            throw new NullPointerException();
-        dumpHprofData(fileName, null);
+    public static void dumpHprofData(String filename) throws IOException {
+        if (filename == null) {
+            throw new NullPointerException("filename == null");
+        }
+        dumpHprofData(filename, null);
     }
 
     /**
diff --git a/dalvik/src/main/java/dalvik/system/Zygote.java b/dalvik/src/main/java/dalvik/system/Zygote.java
index c06314e..9e96204 100644
--- a/dalvik/src/main/java/dalvik/system/Zygote.java
+++ b/dalvik/src/main/java/dalvik/system/Zygote.java
@@ -16,6 +16,9 @@
 
 package dalvik.system;
 
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
 import java.io.File;
 
 /**
@@ -172,12 +175,18 @@
 
     /**
      * Executes "/system/bin/sh -c &lt;command&gt;" using the exec() system call.
-     * This method never returns.
+     * This method throws a runtime exception if exec() failed, otherwise, this
+     * method never returns.
      *
      * @param command The shell command to execute.
      */
     public static void execShell(String command) {
-        nativeExecShell(command);
+        String[] args = { "/system/bin/sh", "-c", command };
+        try {
+            Libcore.os.execv(args[0], args);
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -194,6 +203,4 @@
             command.append(" '").append(arg.replace("'", "'\\''")).append("'");
         }
     }
-
-    native private static void nativeExecShell(String command);
 }
diff --git a/dalvik/src/main/java/dalvik/system/package.html b/dalvik/src/main/java/dalvik/system/package.html
deleted file mode 100644
index 01d0d30..0000000
--- a/dalvik/src/main/java/dalvik/system/package.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<html>
-  <body>
-    <p>
-      Provides utility and system information classes specific to the Dalvik VM.
-    </p>
-  </body>
-</html>
diff --git a/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java b/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java
index 0266103..744977c 100644
--- a/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java
+++ b/dalvik/src/main/java/dalvik/system/profiler/SamplingProfiler.java
@@ -74,7 +74,7 @@
 
     /**
      * A sampler is created every time profiling starts and cleared
-     * everytime profiling stops because once a {@code TimerTask} is
+     * every time profiling stops because once a {@code TimerTask} is
      * canceled it cannot be reused.
      */
     private Sampler sampler;
@@ -94,7 +94,7 @@
      *  Real hprof output examples don't start the thread and trace
      *  identifiers at one but seem to start at these arbitrary
      *  constants. It certainly seems useful to have relatively unique
-     *  identifers when manual searching hprof output.
+     *  identifiers when manual searching hprof output.
      */
     private int nextThreadId = 200001;
     private int nextStackTraceId = 300001;
@@ -146,9 +146,9 @@
      *
      * @param threadSet The thread set specifies which threads to
      * sample. In a general purpose program, all threads typically
-     * should be sample with a ThreadSet such as provied by {@link
-     * #newThreadGroupTheadSet newThreadGroupTheadSet}. For a
-     * benchmark a fixed set such as provied by {@link
+     * should be sample with a ThreadSet such as provided by {@link
+     * #newThreadGroupThreadSet newThreadGroupThreadSet}. For a
+     * benchmark a fixed set such as provided by {@link
      * #newArrayThreadSet newArrayThreadSet} can reduce the overhead
      * of profiling.
      */
@@ -188,7 +188,7 @@
     /**
      * Returns a ThreadSet for a fixed set of threads that will not
      * vary at runtime. This has less overhead than a dynamically
-     * calculated set, such as {@link #newThreadGroupTheadSet}, which has
+     * calculated set, such as {@link #newThreadGroupThreadSet}, which has
      * to enumerate the threads each time profiler wants to collect
      * samples.
      */
@@ -218,7 +218,7 @@
      * threads found in the specified ThreadGroup and that
      * ThreadGroup's children.
      */
-    public static ThreadSet newThreadGroupTheadSet(ThreadGroup threadGroup) {
+    public static ThreadSet newThreadGroupThreadSet(ThreadGroup threadGroup) {
         return new ThreadGroupThreadSet(threadGroup);
     }
 
diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java b/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java
index 7b46d2e..5daf6a0 100644
--- a/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java
+++ b/dalvik/src/main/java/org/apache/harmony/dalvik/NativeTestTarget.java
@@ -22,25 +22,20 @@
  * and performance of calling native methods.
  */
 public final class NativeTestTarget {
-    /**
-     * This class is uninstantiable.
-     */
-    private NativeTestTarget() {
-        // This space intentionally left blank.
+    public NativeTestTarget() {
     }
 
-    /**
-     * This is an empty native static method with no args, hooked up using
-     * JNI.
-     */
+    public static native synchronized void emptyJniStaticSynchronizedMethod0();
+
+    public native synchronized void emptyJniSynchronizedMethod0();
+
     public static native void emptyJniStaticMethod0();
 
-    /**
-     * This is an empty native static method with six args, hooked up using
-     * JNI.
-     */
-    public static native void emptyJniStaticMethod6(int a, int b, int c,
-        int d, int e, int f);
+    public native void emptyJniMethod0();
+
+    public static native void emptyJniStaticMethod6(int a, int b, int c, int d, int e, int f);
+
+    public native void emptyJniMethod6(int a, int b, int c, int d, int e, int f);
 
     /**
      * This is an empty native static method with six args, hooked up
@@ -51,18 +46,17 @@
     public static native void emptyJniStaticMethod6L(String a, String[] b,
         int[][] c, Object d, Object[] e, Object[][][][] f);
 
+    public native void emptyJniMethod6L(String a, String[] b,
+        int[][] c, Object d, Object[] e, Object[][][][] f);
+
     /**
-     * This method is intended to be "inlined" by the virtual machine
-     * (e.g., given special treatment as an intrinsic).
+     * This is used to benchmark dalvik's inline natives.
      */
     public static void emptyInlineMethod() {
-        // This space intentionally left blank.
     }
 
     /**
-     * This method is intended to be defined in native code and hooked
-     * up using the virtual machine's special fast-path native linkage
-     * (as opposed to being hooked up using JNI).
+     * This is used to benchmark dalvik's inline natives.
      */
     public static native void emptyInternalStaticMethod();
 }
diff --git a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java
index 61bee34..7717fd9 100644
--- a/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java
+++ b/dalvik/src/main/java/org/apache/harmony/dalvik/ddmc/DdmServer.java
@@ -50,9 +50,9 @@
      * Throws an exception if the type already has a handler registered.
      */
     public static void registerHandler(int type, ChunkHandler handler) {
-        if (handler == null)
-            throw new NullPointerException();
-
+        if (handler == null) {
+            throw new NullPointerException("handler == null");
+        }
         synchronized (mHandlerMap) {
             if (mHandlerMap.get(type) != null)
                 throw new RuntimeException("type " + Integer.toHexString(type)
@@ -171,4 +171,3 @@
         return handler.handleChunk(chunk);
     }
 }
-
diff --git a/dalvik/src/main/native/org_apache_harmony_dalvik_NativeTestTarget.cpp b/dalvik/src/main/native/org_apache_harmony_dalvik_NativeTestTarget.cpp
index 50e2fc2..11735eb 100644
--- a/dalvik/src/main/native/org_apache_harmony_dalvik_NativeTestTarget.cpp
+++ b/dalvik/src/main/native/org_apache_harmony_dalvik_NativeTestTarget.cpp
@@ -14,55 +14,30 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "NativeTestTarget"
+
 #include "JNIHelp.h"
+#include "../../../../luni/src/main/native/JniConstants.h"
 
-/*
- * public static void emptyJniStaticMethod0()
- *
- * For benchmarks, a do-nothing JNI method with no arguments.
- */
-static void emptyJniStaticMethod0(JNIEnv*, jclass)
-{
-    // This space intentionally left blank.
-}
-
-/*
- * public static void emptyJniStaticMethod6(int a, int b, int c,
- *   int d, int e, int f)
- *
- * For benchmarks, a do-nothing JNI method with six arguments.
- */
-static void emptyJniStaticMethod6(JNIEnv*, jclass,
-    int, int, int, int, int, int)
-{
-    // This space intentionally left blank.
-}
-
-/*
- * public static void emptyJniStaticMethod6L(String a, String[] b,
- *   int[][] c, Object d, Object[] e, Object[][][][] f)
- *
- * For benchmarks, a do-nothing JNI method with six arguments.
- */
-static void emptyJniStaticMethod6L(JNIEnv*, jclass,
-    jobject, jarray, jarray, jobject, jarray, jarray)
-{
-    // This space intentionally left blank.
-}
+static void NativeTestTarget_emptyJniMethod0(JNIEnv*, jobject) { }
+static void NativeTestTarget_emptyJniMethod6(JNIEnv*, jclass, int, int, int, int, int, int) { }
+static void NativeTestTarget_emptyJniMethod6L(JNIEnv*, jclass, jobject, jarray, jarray, jobject, jarray, jarray) { }
+static void NativeTestTarget_emptyJniStaticMethod0(JNIEnv*, jclass) { }
+static void NativeTestTarget_emptyJniStaticMethod6(JNIEnv*, jclass, int, int, int, int, int, int) { }
+static void NativeTestTarget_emptyJniStaticMethod6L(JNIEnv*, jclass, jobject, jarray, jarray, jobject, jarray, jarray) { }
+static void NativeTestTarget_emptyJniStaticSynchronizedMethod0(JNIEnv*, jclass) { }
+static void NativeTestTarget_emptyJniSynchronizedMethod0(JNIEnv*, jclass) { }
 
 static JNINativeMethod gMethods[] = {
-  { "emptyJniStaticMethod0",  "()V",       (void*)emptyJniStaticMethod0 },
-  { "emptyJniStaticMethod6",  "(IIIIII)V", (void*)emptyJniStaticMethod6 },
-  { "emptyJniStaticMethod6L", "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V", (void*)emptyJniStaticMethod6L },
+    NATIVE_METHOD(NativeTestTarget, emptyJniMethod0, "()V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniMethod6, "(IIIIII)V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniMethod6L, "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniStaticMethod0, "()V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniStaticMethod6, "(IIIIII)V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniStaticMethod6L, "(Ljava/lang/String;[Ljava/lang/String;[[ILjava/lang/Object;[Ljava/lang/Object;[[[[Ljava/lang/Object;)V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniStaticSynchronizedMethod0, "()V"),
+    NATIVE_METHOD(NativeTestTarget, emptyJniSynchronizedMethod0, "()V"),
 };
 int register_org_apache_harmony_dalvik_NativeTestTarget(JNIEnv* env) {
-    int result = jniRegisterNativeMethods(env,
-            "org/apache/harmony/dalvik/NativeTestTarget",
-            gMethods, NELEM(gMethods));
-    if (result != 0) {
-        /* print warning, but allow to continue */
-        ALOGW("WARNING: NativeTestTarget not registered\n");
-        env->ExceptionClear();
-    }
-    return 0;
+    return jniRegisterNativeMethods(env, "org/apache/harmony/dalvik/NativeTestTarget", gMethods, NELEM(gMethods));
 }
diff --git a/expectations/brokentests.txt b/expectations/brokentests.txt
index e1d81e0..dfc65e7 100644
--- a/expectations/brokentests.txt
+++ b/expectations/brokentests.txt
@@ -18,11 +18,6 @@
   bug: 5534202
 },
 {
-  description: "libcore.java.security.KeyPairGeneratorTest long test is too long",
-  name: "libcore.java.security.KeyPairGeneratorTest#test_getInstance",
-  bug: 5513723
-},
-{
   description: "Support digest authentication in HttpURLConnection",
   name: "libcore.net.http.ParsedHeadersTest#testParseChallengesWithManyParameters",
   bug: 6156454
@@ -890,15 +885,6 @@
   substring: "java.util.IllegalFormatFlagsException: %% doesn't take an argument"
 },
 {
-  description: "These tests expect to be able to load resources from the file system",
-  result: EXEC_FAILED,
-  names: [
-    "libcore.java.lang.OldClassTest#test_getResourceAsStreamLjava_lang_String",
-    "libcore.java.lang.OldClassTest#test_getResourceAsStream_withSharpChar",
-    "libcore.java.lang.OldClassTest#test_getResourceLjava_lang_String"
-  ]
-},
-{
   description: "Some tests (ExcludedProxyTest) connect to a public webserver to check that the HTTP client works",
   result: EXEC_FAILED,
   failure: "connect to the Internet",
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index 5e24be3..5d7f716 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -26,14 +26,6 @@
   bug: 4723412
 },
 {
-  description: "Encoded bytes don't match for EC elliptic curve keys created through KeyFactory.generatePrivate()",
-  names: [
-    "org.apache.harmony.security.tests.java.security.KeyFactory2Test#test_generatePrivateLjava_security_spec_KeySpec",
-    "org.apache.harmony.security.tests.java.security.KeyFactory2Test#test_getKeySpecLjava_security_KeyLjava_lang_Class"
-  ],
-  bug: 3483365
-},
-{
   description: "Expat uses an unbounded number of global references",
   name: "libcore.xml.ExpatSaxParserTest#testGlobalReferenceTableOverflow",
   bug: 2772628
@@ -58,23 +50,10 @@
   bug: 3387655
 },
 {
-  name: "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#testGetMaxAllowedKeyLength",
-  bug: 3387688
-},
-{
   name: "libcore.java.io.FileTest#test_emptyFilename",
   bug: 3387758
 },
 {
-  description: "Turkish dotless i behaves differently on dalvik vs. RI",
-  names: [
-    "libcore.java.lang.StringTest#testChangeCase_en_US",
-    "libcore.java.lang.StringTest#testEqualsIgnoreCase_en_US",
-    "libcore.java.lang.StringTest#testEqualsIgnoreCase_tr_TR"
-  ],
-  bug: 3325799
-},
-{
   description: "KxmlPullParser doesn't enforce top-level document element",
   names: [
     "libcore.xml.KxmlPullParserDtdTest#testDoctypeInDocumentElement",
@@ -334,11 +313,6 @@
   name: "tests.java.security.SecureClassLoaderTest#testSecureClassLoaderClassLoader"
 },
 {
-  description: "Assertion does not evaluate to true... Works in javax.Certificate",
-  result: EXEC_FAILED,
-  name: "tests.security.cert.CertificateTest#testGetEncoded"
-},
-{
   description: "Not all Drivers are loaded in testsetup. ClassLoader issue in DriverManager.",
   result: EXEC_FAILED,
   name: "org.apache.harmony.sql.tests.java.sql.DriverManagerTest#testDeregisterDriver"
@@ -1680,5 +1654,42 @@
     "libcore.xml.JaxenXPathTestSuite$4#xml/web.xml /web-app/servlet[1] upper-case( servlet-class, 'fr' )",
     "libcore.xml.JaxenXPathTestSuite$4#xml/web.xml /web-app/servlet[1] upper-case( servlet-class, 'fr-CA' )"
   ]
+},
+{
+  description: "Android's PKIX validation fails on many NIST PKIX tests",
+  bug: 8030138,
+  names: [
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidLongSerialNumberTest18",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidNegativeSerialNumberTest15",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidRevokedCATest2",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidRevokedEETest3",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidSeparateCertificateandCRLKeysTest20",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidSeparateCertificateandCRLKeysTest21",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_InvalidUnknownCRLEntryExtensionTest8",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testBasicCertificateRevocationTests_ValidSeparateCertificateandCRLKeysTest19",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDeltaCRLs_InvaliddeltaCRLTest3",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDeltaCRLs_InvaliddeltaCRLTest4",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDeltaCRLs_InvaliddeltaCRLTest6",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDeltaCRLs_InvaliddeltaCRLTest9",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvalidIDPwithindirectCRLTest23",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvalidcRLIssuerTest34",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvaliddistributionPointTest2",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvaliddistributionPointTest6",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvalidonlySomeReasonsTest15",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvalidonlySomeReasonsTest16",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvalidonlySomeReasonsTest20",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_InvalidonlySomeReasonsTest21",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_ValidIDPwithindirectCRLTest24",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_ValidIDPwithindirectCRLTest25",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_ValidcRLIssuerTest28",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_ValidcRLIssuerTest29",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_ValidcRLIssuerTest30",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testDistributionPoints_ValidcRLIssuerTest33",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testKeyUsage_ValidSelfIssuedDNnameConstraintsTest19",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testVerifyingNameChaining_ValidRFC3280OptionalAttributeTypesTest8",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testVerifyingPathswithSelfIssuedCertificates_InvalidBasicSelfIssuedOldWithNewTest2",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testVerifyingPathswithSelfIssuedCertificates_ValidBasicSelfIssuedCRLSigningKeyTest6",
+    "libcore.java.security.cert.X509CertificateNistPkitsTest#testVerifyingPathswithSelfIssuedCertificates_ValidBasicSelfIssuedNewWithOldTest4"
+  ]
 }
 ]
diff --git a/expectations/taggedtests.txt b/expectations/taggedtests.txt
index 883864c..aa220d0 100644
--- a/expectations/taggedtests.txt
+++ b/expectations/taggedtests.txt
@@ -7,12 +7,12 @@
   description: "large tests",
   result: SUCCESS,
   names: [
-    "java.util.EnumSet.EnumSetBash",
-    "org.apache.harmony.tests.java.util.regex.MatcherTest#testAllCodePoints",
-    "libcore.javax.net.ssl.SSLSocketTest",
+    /* libcore tests that take over 15 minutes on device because of DHParametersHelper.generateSafePrimes */
     "org.apache.harmony.crypto.tests.javax.crypto.func.KeyAgreementFunctionalTest",
     "org.apache.harmony.crypto.tests.javax.crypto.interfaces.DHPrivateKeyTest",
     "org.apache.harmony.crypto.tests.javax.crypto.interfaces.DHPublicKeyTest",
+    /* non-AOSP tests http://b/8027066 */
+    "java.util.EnumSet.EnumSetBash",
     "java.io.PipedInputStream.CloseAndAvailableRC",
     "java.io.PrintStream.OversynchronizedTest",
     "java.io.PrintWriter.OversynchronizedTest",
@@ -42,4 +42,4 @@
   ],
   "tags": [ "large" ]
 }
-]
\ No newline at end of file
+]
diff --git a/include/ScopedJavaUnicodeString.h b/include/ScopedJavaUnicodeString.h
index b108a6b..f6ed7ad 100644
--- a/include/ScopedJavaUnicodeString.h
+++ b/include/ScopedJavaUnicodeString.h
@@ -1,12 +1,12 @@
 /*
  * Copyright (C) 2010 The Android Open Source Project
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,34 +24,44 @@
 // jstring. We give ICU a direct pointer to the characters on the Java heap.
 // It's clever enough to copy-on-write if necessary.
 class ScopedJavaUnicodeString {
-public:
-    ScopedJavaUnicodeString(JNIEnv* env, jstring s) : mEnv(env), mString(s) {
-        mChars = env->GetStringChars(mString, NULL);
-        const int32_t charCount = env->GetStringLength(mString);
-        mUnicodeString.setTo(false, mChars, charCount);
+ public:
+  ScopedJavaUnicodeString(JNIEnv* env, jstring s) : mEnv(env), mString(s) {
+    if (s == NULL) {
+      jniThrowNullPointerException(mEnv, NULL);
+    } else {
+      mChars = env->GetStringChars(mString, NULL);
+      const int32_t charCount = env->GetStringLength(mString);
+      mUnicodeString.setTo(false, mChars, charCount);
     }
+  }
 
-    ~ScopedJavaUnicodeString() {
-        mEnv->ReleaseStringChars(mString, mChars);
+  ~ScopedJavaUnicodeString() {
+    if (mString != NULL) {
+      mEnv->ReleaseStringChars(mString, mChars);
     }
+  }
 
-    const UnicodeString& unicodeString() const {
-        return mUnicodeString;
-    }
+  bool valid() const {
+    return (mString != NULL);
+  }
 
-    UnicodeString& unicodeString() {
-        return mUnicodeString;
-    }
+  const UnicodeString& unicodeString() const {
+    return mUnicodeString;
+  }
 
-private:
-    JNIEnv* mEnv;
-    jstring mString;
-    const UChar* mChars;
-    UnicodeString mUnicodeString;
+  UnicodeString& unicodeString() {
+    return mUnicodeString;
+  }
 
-    // Disallow copy and assignment.
-    ScopedJavaUnicodeString(const ScopedJavaUnicodeString&);
-    void operator=(const ScopedJavaUnicodeString&);
+ private:
+  JNIEnv* mEnv;
+  jstring mString;
+  const UChar* mChars;
+  UnicodeString mUnicodeString;
+
+  // Disallow copy and assignment.
+  ScopedJavaUnicodeString(const ScopedJavaUnicodeString&);
+  void operator=(const ScopedJavaUnicodeString&);
 };
 
 #endif  // SCOPED_JAVA_UNICODE_STRING_H_included
diff --git a/include/ScopedPrimitiveArray.h b/include/ScopedPrimitiveArray.h
index 079e98c..f6626b2 100644
--- a/include/ScopedPrimitiveArray.h
+++ b/include/ScopedPrimitiveArray.h
@@ -40,6 +40,7 @@
             } \
         } \
         const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
         const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
         size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \
     private: \
@@ -82,6 +83,7 @@
             } \
         } \
         const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
         const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
         PRIMITIVE_TYPE* get() { return mRawArray; } \
         PRIMITIVE_TYPE& operator[](size_t n) { return mRawArray[n]; } \
diff --git a/json/src/main/java/org/json/JSONObject.java b/json/src/main/java/org/json/JSONObject.java
index e7ca735..c2a656c 100644
--- a/json/src/main/java/org/json/JSONObject.java
+++ b/json/src/main/java/org/json/JSONObject.java
@@ -41,12 +41,12 @@
  *       be coerced using {@link Number#intValue() intValue}. Strings
  *       that can be coerced using {@link Double#valueOf(String)} will be,
  *       and then cast to int.
- *   <li>When the requested type is a long, other {@link Number} types will
+ *   <li><a name="lossy">When the requested type is a long, other {@link Number} types will
  *       be coerced using {@link Number#longValue() longValue}. Strings
  *       that can be coerced using {@link Double#valueOf(String)} will be,
  *       and then cast to long. This two-step conversion is lossy for very
  *       large values. For example, the string "9223372036854775806" yields the
- *       long 9223372036854775807.
+ *       long 9223372036854775807.</a>
  *   <li>When the requested type is a String, other non-null values will be
  *       coerced using {@link String#valueOf(Object)}. Although null cannot be
  *       coerced, the sentinel value {@link JSONObject#NULL} is coerced to the
@@ -131,7 +131,7 @@
              */
             String key = (String) entry.getKey();
             if (key == null) {
-                throw new NullPointerException();
+                throw new NullPointerException("key == null");
             }
             nameValuePairs.put(key, entry.getValue());
         }
@@ -468,7 +468,8 @@
 
     /**
      * Returns the value mapped by {@code name} if it exists and is a long or
-     * can be coerced to a long.
+     * can be coerced to a long. Note that JSON represents numbers as doubles,
+     * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
      *
      * @throws JSONException if the mapping doesn't exist or cannot be coerced
      *     to a long.
@@ -484,7 +485,8 @@
 
     /**
      * Returns the value mapped by {@code name} if it exists and is a long or
-     * can be coerced to a long. Returns 0 otherwise.
+     * can be coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as doubles,
+     * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
      */
     public long optLong(String name) {
         return optLong(name, 0L);
@@ -492,7 +494,9 @@
 
     /**
      * Returns the value mapped by {@code name} if it exists and is a long or
-     * can be coerced to a long. Returns {@code fallback} otherwise.
+     * can be coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents
+     * numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
+     * numbers via JSON.
      */
     public long optLong(String name, long fallback) {
         Object object = opt(name);
diff --git a/json/src/main/java/org/json/JSONTokener.java b/json/src/main/java/org/json/JSONTokener.java
index ebfabe4..202e2e6 100644
--- a/json/src/main/java/org/json/JSONTokener.java
+++ b/json/src/main/java/org/json/JSONTokener.java
@@ -543,7 +543,7 @@
      */
     public String nextTo(String excluded) {
         if (excluded == null) {
-            throw new NullPointerException();
+            throw new NullPointerException("excluded == null");
         }
         return nextToInternal(excluded).trim();
     }
diff --git a/luni/src/main/java/java/io/BufferedReader.java b/luni/src/main/java/java/io/BufferedReader.java
index 03ecf9f..9fba039 100644
--- a/luni/src/main/java/java/io/BufferedReader.java
+++ b/luni/src/main/java/java/io/BufferedReader.java
@@ -119,7 +119,7 @@
      * Populates the buffer with data. It is an error to call this method when
      * the buffer still contains data; ie. if {@code pos < end}.
      *
-     * @return the number of bytes read into the buffer, or -1 if the end of the
+     * @return the number of chars read into the buffer, or -1 if the end of the
      *      source stream has been reached.
      */
     private int fillBuf() throws IOException {
@@ -190,7 +190,7 @@
     @Override
     public void mark(int markLimit) throws IOException {
         if (markLimit < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("markLimit < 0:" + markLimit);
         }
         synchronized (lock) {
             checkNotClosed();
@@ -254,7 +254,7 @@
      * @param buffer
      *            the character array to store the characters read.
      * @param offset
-     *            the initial position in {@code buffer} to store the bytes read
+     *            the initial position in {@code buffer} to store the chars read
      *            from this reader.
      * @param length
      *            the maximum number of characters to read, must be
@@ -277,7 +277,7 @@
             while (outstanding > 0) {
 
                 /*
-                 * If there are bytes in the buffer, grab those first.
+                 * If there are chars in the buffer, grab those first.
                  */
                 int available = end - pos;
                 if (available > 0) {
@@ -291,7 +291,7 @@
                 /*
                  * Before attempting to read from the underlying stream, make
                  * sure we really, really want to. We won't bother if we're
-                 * done, or if we've already got some bytes and reading from the
+                 * done, or if we've already got some chars and reading from the
                  * underlying stream would block.
                  */
                 if (outstanding == 0 || (outstanding < length && !in.ready())) {
@@ -302,7 +302,7 @@
 
                 /*
                  * If we're unmarked and the requested size is greater than our
-                 * buffer, read the bytes directly into the caller's buffer. We
+                 * buffer, read the chars directly into the caller's buffer. We
                  * don't read into smaller buffers because that could result in
                  * a many reads.
                  */
@@ -464,17 +464,16 @@
     }
 
     /**
-     * Skips {@code byteCount} bytes in this stream. Subsequent calls to
-     * {@code read} will not return these bytes unless {@code reset} is
+     * Skips {@code charCount} chars in this stream. Subsequent calls to
+     * {@code read} will not return these chars unless {@code reset} is
      * used.
-     * Skipping characters may invalidate a mark if {@code markLimit}
+     *
+     * <p>Skipping characters may invalidate a mark if {@code markLimit}
      * is surpassed.
      *
-     * @param byteCount
-     *            the maximum number of characters to skip.
+     * @param charCount the maximum number of characters to skip.
      * @return the number of characters actually skipped.
-     * @throws IllegalArgumentException
-     *             if {@code byteCount < 0}.
+     * @throws IllegalArgumentException if {@code charCount < 0}.
      * @throws IOException
      *             if this reader is closed or some other I/O error occurs.
      * @see #mark(int)
@@ -482,35 +481,35 @@
      * @see #reset()
      */
     @Override
-    public long skip(long byteCount) throws IOException {
-        if (byteCount < 0) {
-            throw new IllegalArgumentException("byteCount < 0: " + byteCount);
+    public long skip(long charCount) throws IOException {
+        if (charCount < 0) {
+            throw new IllegalArgumentException("charCount < 0: " + charCount);
         }
         synchronized (lock) {
             checkNotClosed();
-            if (byteCount < 1) {
+            if (charCount < 1) {
                 return 0;
             }
-            if (end - pos >= byteCount) {
-                pos += byteCount;
-                return byteCount;
+            if (end - pos >= charCount) {
+                pos += charCount;
+                return charCount;
             }
 
             long read = end - pos;
             pos = end;
-            while (read < byteCount) {
+            while (read < charCount) {
                 if (fillBuf() == -1) {
                     return read;
                 }
-                if (end - pos >= byteCount - read) {
-                    pos += byteCount - read;
-                    return byteCount;
+                if (end - pos >= charCount - read) {
+                    pos += charCount - read;
+                    return charCount;
                 }
                 // Couldn't get all the characters, skip what we read
                 read += (end - pos);
                 pos = end;
             }
-            return byteCount;
+            return charCount;
         }
     }
 }
diff --git a/luni/src/main/java/java/io/BufferedWriter.java b/luni/src/main/java/java/io/BufferedWriter.java
index 55ae121..bbb18f7 100644
--- a/luni/src/main/java/java/io/BufferedWriter.java
+++ b/luni/src/main/java/java/io/BufferedWriter.java
@@ -46,7 +46,7 @@
 
     /**
      * Constructs a new {@code BufferedWriter}, providing {@code out} with a buffer
-     * of 8192 bytes.
+     * of 8192 chars.
      *
      * @param out the {@code Writer} the buffer writes to.
      */
@@ -55,11 +55,11 @@
     }
 
     /**
-     * Constructs a new {@code BufferedWriter}, providing {@code out} with {@code size} bytes
+     * Constructs a new {@code BufferedWriter}, providing {@code out} with {@code size} chars
      * of buffer.
      *
      * @param out the {@code OutputStream} the buffer writes to.
-     * @param size the size of buffer in bytes.
+     * @param size the size of buffer in chars.
      * @throws IllegalArgumentException if {@code size <= 0}.
      */
     public BufferedWriter(Writer out, int size) {
diff --git a/luni/src/main/java/java/io/ObjectStreamClass.java b/luni/src/main/java/java/io/ObjectStreamClass.java
index a28489a..1bde314 100644
--- a/luni/src/main/java/java/io/ObjectStreamClass.java
+++ b/luni/src/main/java/java/io/ObjectStreamClass.java
@@ -147,7 +147,7 @@
     private transient Class<?> resolvedClass;
 
     private transient Class<?> resolvedConstructorClass;
-    private transient int resolvedConstructorMethodId;
+    private transient long resolvedConstructorMethodId;
 
     // Serial version UID of the class the descriptor represents
     private transient long svUID;
@@ -653,7 +653,7 @@
         resolveConstructorClass(instantiationClass);
         return newInstance(instantiationClass, resolvedConstructorMethodId);
     }
-    private static native Object newInstance(Class<?> instantiationClass, int methodId);
+    private static native Object newInstance(Class<?> instantiationClass, long methodId);
 
     private Class<?> resolveConstructorClass(Class<?> objectClass) throws InvalidClassException {
         if (resolvedConstructorClass != null) {
@@ -720,7 +720,7 @@
         resolvedConstructorMethodId = getConstructorId(resolvedConstructorClass);
         return constructorClass;
     }
-    private static native int getConstructorId(Class<?> c);
+    private static native long getConstructorId(Class<?> c);
 
     /**
      * Checks if two classes belong to the same package.
diff --git a/luni/src/main/java/java/io/PipedInputStream.java b/luni/src/main/java/java/io/PipedInputStream.java
index aa77e6e..739fb98 100644
--- a/luni/src/main/java/java/io/PipedInputStream.java
+++ b/luni/src/main/java/java/io/PipedInputStream.java
@@ -18,6 +18,7 @@
 package java.io;
 
 import java.util.Arrays;
+import libcore.io.IoUtils;
 
 /**
  * Receives information from a communications pipe. When two threads want to
@@ -235,7 +236,7 @@
                 wait(1000);
             }
         } catch (InterruptedException e) {
-            throw new InterruptedIOException();
+            IoUtils.throwInterruptedIoException();
         }
 
         int result = buffer[out++] & 0xff;
@@ -314,7 +315,7 @@
                 wait(1000);
             }
         } catch (InterruptedException e) {
-            throw new InterruptedIOException();
+            IoUtils.throwInterruptedIoException();
         }
 
         int totalCopied = 0;
@@ -394,7 +395,7 @@
                 wait(1000);
             }
         } catch (InterruptedException e) {
-            throw new InterruptedIOException();
+            IoUtils.throwInterruptedIoException();
         }
         if (buffer == null) {
             throw new IOException("Pipe is closed");
diff --git a/luni/src/main/java/java/io/PipedReader.java b/luni/src/main/java/java/io/PipedReader.java
index 6190c86..8450a34 100644
--- a/luni/src/main/java/java/io/PipedReader.java
+++ b/luni/src/main/java/java/io/PipedReader.java
@@ -18,6 +18,7 @@
 package java.io;
 
 import java.util.Arrays;
+import libcore.io.IoUtils;
 
 /**
  * Receives information on a communications pipe. When two threads want to pass
@@ -261,7 +262,7 @@
                 wait(1000);
             }
         } catch (InterruptedException e) {
-            throw new InterruptedIOException();
+            IoUtils.throwInterruptedIoException();
         }
 
         int copyLength = 0;
@@ -362,7 +363,7 @@
                 }
             }
         } catch (InterruptedException e) {
-            throw new InterruptedIOException();
+            IoUtils.throwInterruptedIoException();
         }
         if (buffer == null) {
             throw new IOException("Pipe is closed");
@@ -411,7 +412,7 @@
                     }
                 }
             } catch (InterruptedException e) {
-                throw new InterruptedIOException();
+                IoUtils.throwInterruptedIoException();
             }
             if (buffer == null) {
                 throw new IOException("Pipe is closed");
diff --git a/luni/src/main/java/java/io/StringReader.java b/luni/src/main/java/java/io/StringReader.java
index 9f8ff13..5579d62 100644
--- a/luni/src/main/java/java/io/StringReader.java
+++ b/luni/src/main/java/java/io/StringReader.java
@@ -83,7 +83,7 @@
     @Override
     public void mark(int readLimit) throws IOException {
         if (readLimit < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("readLimit < 0: " + readLimit);
         }
 
         synchronized (lock) {
diff --git a/luni/src/main/java/java/io/StringWriter.java b/luni/src/main/java/java/io/StringWriter.java
index f0b1d18..2946483 100644
--- a/luni/src/main/java/java/io/StringWriter.java
+++ b/luni/src/main/java/java/io/StringWriter.java
@@ -49,11 +49,11 @@
      * writer.
      *
      * @param initialSize
-     *            the intial size of the target string buffer.
+     *            the initial size of the target string buffer.
      */
     public StringWriter(int initialSize) {
         if (initialSize < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("initialSize < 0: " + initialSize);
         }
         buf = new StringBuffer(initialSize);
         lock = buf;
diff --git a/luni/src/main/java/java/lang/Class.java b/luni/src/main/java/java/lang/Class.java
index b6183a5..f618b66 100644
--- a/luni/src/main/java/java/lang/Class.java
+++ b/luni/src/main/java/java/lang/Class.java
@@ -148,18 +148,15 @@
 
     /**
      * Returns a {@code Class} object which represents the class with the
-     * specified name. The name should be the name of a class as described in
-     * the {@link Class class definition}; however, {@code Class}es representing
-     * primitive types can not be found using this method.
-     * <p>
-     * If the class has not been loaded so far, it is being loaded and linked
-     * first. This is done through either the class loader of the calling class
-     * or one of its parent class loaders. The class is also being initialized,
-     * which means that a possible static initializer block is executed.
+     * given name. The name should be the name of a non-primitive class, as described in
+     * the {@link Class class definition}.
+     * Primitive types can not be found using this method; use {@code int.class} or {@code Integer.TYPE} instead.
      *
-     * @param className
-     *            the name of the non-primitive-type class to find.
-     * @return the named {@code Class} instance.
+     * <p>If the class has not yet been loaded, it is loaded and initialized
+     * first. This is done through either the class loader of the calling class
+     * or one of its parent class loaders. It is possible that a static initializer is run as
+     * a result of this call.
+     *
      * @throws ClassNotFoundException
      *             if the requested class can not be found.
      * @throws LinkageError
@@ -174,24 +171,14 @@
 
     /**
      * Returns a {@code Class} object which represents the class with the
-     * specified name. The name should be the name of a class as described in
-     * the {@link Class class definition}, however {@code Class}es representing
-     * primitive types can not be found using this method. Security rules will
-     * be obeyed.
-     * <p>
-     * If the class has not been loaded so far, it is being loaded and linked
-     * first. This is done through either the specified class loader or one of
-     * its parent class loaders. The caller can also request the class to be
-     * initialized, which means that a possible static initializer block is
-     * executed.
+     * given name. The name should be the name of a non-primitive class, as described in
+     * the {@link Class class definition}.
+     * Primitive types can not be found using this method; use {@code int.class} or {@code Integer.TYPE} instead.
      *
-     * @param className
-     *            the name of the non-primitive-type class to find.
-     * @param initializeBoolean
-     *            indicates whether the class should be initialized.
-     * @param classLoader
-     *            the class loader to use to load the class.
-     * @return the named {@code Class} instance.
+     * <p>If the class has not yet been loaded, it is loaded first, using the given class loader.
+     * If the class has not yet been initialized and {@code shouldInitialize} is true,
+     * the class will be initialized.
+     *
      * @throws ClassNotFoundException
      *             if the requested class can not be found.
      * @throws LinkageError
@@ -200,7 +187,7 @@
      *             if an exception occurs during static initialization of a
      *             class.
      */
-    public static Class<?> forName(String className, boolean initializeBoolean,
+    public static Class<?> forName(String className, boolean shouldInitialize,
             ClassLoader classLoader) throws ClassNotFoundException {
 
         if (classLoader == null) {
@@ -209,12 +196,12 @@
         // Catch an Exception thrown by the underlying native code. It wraps
         // up everything inside a ClassNotFoundException, even if e.g. an
         // Error occurred during initialization. This as a workaround for
-        // an ExceptionInInitilaizerError that's also wrapped. It is actually
+        // an ExceptionInInitializerError that's also wrapped. It is actually
         // expected to be thrown. Maybe the same goes for other errors.
         // Not wrapping up all the errors will break android though.
         Class<?> result;
         try {
-            result = classForName(className, initializeBoolean,
+            result = classForName(className, shouldInitialize,
                     classLoader);
         } catch (ClassNotFoundException e) {
             Throwable cause = e.getCause();
@@ -226,17 +213,7 @@
         return result;
     }
 
-    /*
-     * Returns a class by name without any security checks.
-     *
-     * @param className The name of the non-primitive type class to find
-     * @param initializeBoolean A boolean indicating whether the class should be
-     *        initialized
-     * @param classLoader The class loader to use to load the class
-     * @return the named class.
-     * @throws ClassNotFoundException If the class could not be found
-     */
-    static native Class<?> classForName(String className, boolean initializeBoolean,
+    private static native Class<?> classForName(String className, boolean shouldInitialize,
             ClassLoader classLoader) throws ClassNotFoundException;
 
     /**
@@ -245,11 +222,17 @@
      * members inherited from super classes and interfaces. If there are no such
      * class members or if this object represents a primitive type then an array
      * of length 0 is returned.
-     *
-     * @return the public class members of the class represented by this object.
      */
     public Class<?>[] getClasses() {
-        return getFullListOfClasses(true);
+        Class<?>[] result = getDeclaredClasses(this, true);
+        // Traverse all superclasses.
+        for (Class<?> c = this.getSuperclass(); c != null; c = c.getSuperclass()) {
+            Class<?>[] temp = getDeclaredClasses(c, true);
+            if (temp.length != 0) {
+                result = arraycopy(new Class[result.length + temp.length], result, temp);
+            }
+        }
+        return result;
     }
 
     @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
@@ -275,10 +258,9 @@
     }
 
     /**
-     * Returns all the annotations of this class. If there are no annotations
+     * Returns an array containing all the annotations of this class. If there are no annotations
      * then an empty array is returned.
      *
-     * @return a copy of the array containing this class' annotations.
      * @see #getDeclaredAnnotations()
      */
     public Annotation[] getAnnotations() {
@@ -318,9 +300,6 @@
      * Returns the canonical name of this class. If this class does not have a
      * canonical name as defined in the Java Language Specification, then the
      * method returns {@code null}.
-     *
-     * @return this class' canonical name, or {@code null} if it does not have a
-     *         canonical name.
      */
     public String getCanonicalName() {
         if (isLocalClass() || isAnonymousClass())
@@ -362,11 +341,8 @@
      * Returns the class loader which was used to load the class represented by
      * this {@code Class}. Implementations are free to return {@code null} for
      * classes that were loaded by the bootstrap class loader. The Android
-     * reference implementation, though, returns a reference to an actual
-     * representation of the bootstrap class loader.
-     *
-     * @return the class loader for the represented class.
-     * @see ClassLoader
+     * reference implementation, though, always returns a reference to an actual
+     * class loader.
      */
     public ClassLoader getClassLoader() {
         if (this.isPrimitive()) {
@@ -388,8 +364,6 @@
      * this Class without doing any security checks. The bootstrap ClassLoader
      * is returned, unlike getClassLoader() which returns null in place of the
      * bootstrap ClassLoader.
-     *
-     * @return the ClassLoader
      */
     ClassLoader getClassLoaderImpl() {
         ClassLoader loader = getClassLoader(this);
@@ -398,30 +372,22 @@
 
     /*
      * Returns the defining class loader for the given class.
-     *
-     * @param clazz the class the class loader of which we want
-     * @return the class loader
      */
-    private static native ClassLoader getClassLoader(Class<?> clazz);
+    private static native ClassLoader getClassLoader(Class<?> c);
 
     /**
      * Returns a {@code Class} object which represents the component type if
      * this class represents an array type. Returns {@code null} if this class
      * does not represent an array type. The component type of an array type is
      * the type of the elements of the array.
-     *
-     * @return the component type of this class.
      */
     public native Class<?> getComponentType();
 
     /**
      * Returns a {@code Constructor} object which represents the public
-     * constructor matching the specified parameter types.
+     * constructor matching the given parameter types.
+     * {@code (Class[]) null} is equivalent to the empty array.
      *
-     * @param parameterTypes
-     *            the parameter types of the requested constructor.
-     *            {@code (Class[]) null} is equivalent to the empty array.
-     * @return the constructor described by {@code parameterTypes}.
      * @throws NoSuchMethodException
      *             if the constructor can not be found.
      * @see #getDeclaredConstructor(Class[])
@@ -432,14 +398,11 @@
     }
 
     /**
-     * Returns a constructor or method with the specified name.
-     *
-     * @param name the method name, or "<init>" to return a constructor.
-     * @param recursive true to search supertypes.
+     * Returns a constructor or method with the given name. Use "<init>" to return a constructor.
      */
-    private Member getConstructorOrMethod(String name, boolean recursive,
+    private Member getConstructorOrMethod(String name, boolean searchSuperTypes,
             boolean publicOnly, Class<?>[] parameterTypes) throws NoSuchMethodException {
-        if (recursive && !publicOnly) {
+        if (searchSuperTypes && !publicOnly) {
             throw new AssertionError(); // can't lookup non-public members recursively
         }
         if (name == null) {
@@ -453,7 +416,7 @@
                 throw new NoSuchMethodException("parameter type is null");
             }
         }
-        Member result = recursive
+        Member result = searchSuperTypes
                 ? getPublicConstructorOrMethodRecursive(name, parameterTypes)
                 : Class.getDeclaredConstructorOrMethod(this, name, parameterTypes);
         if (result == null || publicOnly && (result.getModifiers() & Modifier.PUBLIC) == 0) {
@@ -486,12 +449,10 @@
 
     /**
      * Returns an array containing {@code Constructor} objects for all public
-     * constructors for the class represented by this {@code Class}. If there
+     * constructors for this {@code Class}. If there
      * are no public constructors or if this {@code Class} represents an array
      * class, a primitive type or void then an empty array is returned.
      *
-     * @return an array with the public constructors of the class represented by
-     *         this {@code Class}.
      * @see #getDeclaredConstructors()
      */
     public Constructor<?>[] getConstructors() {
@@ -504,11 +465,9 @@
      * included in the result. If there are no annotations at all, an empty
      * array is returned.
      *
-     * @return a copy of the array containing the annotations defined for the
-     *         class that this {@code Class} represents.
      * @see #getAnnotations()
      */
-    native public Annotation[] getDeclaredAnnotations();
+    public native Annotation[] getDeclaredAnnotations();
 
     /**
      * Returns the annotation if it exists.
@@ -526,58 +485,23 @@
      * Class} represents. If there are no classes or interfaces declared or if
      * this class represents an array class, a primitive type or void, then an
      * empty array is returned.
-     *
-     * @return an array with {@code Class} objects for all the classes and
-     *         interfaces that are used in member declarations.
      */
     public Class<?>[] getDeclaredClasses() {
         return getDeclaredClasses(this, false);
     }
 
     /*
-     * Returns the list of member classes without performing any security checks
-     * first. This includes the member classes inherited from superclasses. If no
-     * member classes exist at all, an empty array is returned.
-     *
-     * @param publicOnly reflects whether we want only public members or all of them
-     * @return the list of classes
+     * Returns the list of member classes of the given class.
+     * If no members exist, an empty array is returned.
      */
-    private Class<?>[] getFullListOfClasses(boolean publicOnly) {
-        Class<?>[] result = getDeclaredClasses(this, publicOnly);
-
-        // Traverse all superclasses
-        Class<?> clazz = this.getSuperclass();
-        while (clazz != null) {
-            Class<?>[] temp = getDeclaredClasses(clazz, publicOnly);
-            if (temp.length != 0) {
-                result = arraycopy(new Class[result.length + temp.length], result, temp);
-            }
-
-            clazz = clazz.getSuperclass();
-        }
-
-        return result;
-    }
-
-    /*
-     * Returns the list of member classes of the given class. No security checks
-     * are performed. If no members exist, an empty array is returned.
-     *
-     * @param clazz the class the members of which we want
-     * @param publicOnly reflects whether we want only public member or all of them
-     * @return the class' class members
-     */
-    private static native Class<?>[] getDeclaredClasses(Class<?> clazz, boolean publicOnly);
+    private static native Class<?>[] getDeclaredClasses(Class<?> c, boolean publicOnly);
 
     /**
      * Returns a {@code Constructor} object which represents the constructor
-     * matching the specified parameter types that is declared by the class
+     * matching the given parameter types that is declared by the class
      * represented by this {@code Class}.
+     * {@code (Class[]) null} is equivalent to the empty array.
      *
-     * @param parameterTypes
-     *            the parameter types of the requested constructor.
-     *            {@code (Class[]) null} is equivalent to the empty array.
-     * @return the constructor described by {@code parameterTypes}.
      * @throws NoSuchMethodException
      *             if the requested constructor can not be found.
      * @see #getConstructor(Class[])
@@ -592,10 +516,8 @@
      * Returns an array containing {@code Constructor} objects for all
      * constructors declared in the class represented by this {@code Class}. If
      * there are no constructors or if this {@code Class} represents an array
-     * class, a primitive type or void then an empty array is returned.
+     * class, a primitive type, or void then an empty array is returned.
      *
-     * @return an array with the constructors declared in the class represented
-     *         by this {@code Class}.
      * @see #getConstructors()
      */
     public Constructor<?>[] getDeclaredConstructors() {
@@ -603,22 +525,15 @@
     }
 
     /*
-     * Returns the list of constructors without performing any security checks
-     * first. If no constructors exist, an empty array is returned.
-     *
-     * @param clazz the class of interest
-     * @param publicOnly reflects whether we want only public constructors or all of them
-     * @return the list of constructors
+     * Returns the list of constructors. If no constructors exist, an empty array is returned.
      */
-    private static native <T> Constructor<T>[] getDeclaredConstructors(
-            Class<T> clazz, boolean publicOnly);
+    private static native <T> Constructor<T>[] getDeclaredConstructors(Class<T> c,
+                                                                       boolean publicOnly);
 
     /**
-     * Returns a {@code Field} object for the field with the specified name
+     * Returns a {@code Field} object for the field with the given name
      * which is declared in the class represented by this {@code Class}.
      *
-     * @param name the name of the requested field.
-     * @return the requested field in the class represented by this class.
      * @throws NoSuchFieldException if the requested field can not be found.
      * @see #getField(String)
      */
@@ -639,8 +554,6 @@
      * if this {@code Class} represents an array class, a primitive type or void
      * then an empty array is returned.
      *
-     * @return an array with the fields declared in the class represented by
-     *         this class.
      * @see #getFields()
      */
     public Field[] getDeclaredFields() {
@@ -650,32 +563,23 @@
     /*
      * Returns the list of fields without performing any security checks
      * first. If no fields exist at all, an empty array is returned.
-     *
-     * @param clazz the class of interest
-     * @param publicOnly reflects whether we want only public fields or all of them
-     * @return the list of fields
      */
-    static native Field[] getDeclaredFields(Class<?> clazz, boolean publicOnly);
+    static native Field[] getDeclaredFields(Class<?> c, boolean publicOnly);
 
     /**
-     * Returns the field if it is defined by {@code clazz}; null otherwise. This
+     * Returns the field if it is defined by {@code c}; null otherwise. This
      * may return a non-public member.
      */
-    static native Field getDeclaredField(Class<?> clazz, String name);
+    static native Field getDeclaredField(Class<?> c, String name);
 
     /**
      * Returns a {@code Method} object which represents the method matching the
-     * specified name and parameter types that is declared by the class
+     * given name and parameter types that is declared by the class
      * represented by this {@code Class}.
+     * {@code (Class[]) null} is equivalent to the empty array.
      *
-     * @param name
-     *            the requested method's name.
-     * @param parameterTypes
-     *            the parameter types of the requested method.
-     *            {@code (Class[]) null} is equivalent to the empty array.
-     * @return the method described by {@code name} and {@code parameterTypes}.
      * @throws NoSuchMethodException
-     *             if the requested constructor can not be found.
+     *             if the requested method can not be found.
      * @throws NullPointerException
      *             if {@code name} is {@code null}.
      * @see #getMethod(String, Class[])
@@ -695,8 +599,6 @@
      * methods or if this {@code Class} represents an array class, a primitive
      * type or void then an empty array is returned.
      *
-     * @return an array with the methods declared in the class represented by
-     *         this {@code Class}.
      * @see #getMethods()
      */
     public Method[] getDeclaredMethods() {
@@ -704,58 +606,45 @@
     }
 
     /**
-     * Returns the list of methods without performing any security checks
-     * first. If no methods exist, an empty array is returned.
+     * Returns the list of methods. If no methods exist, an empty array is returned.
      */
-    static native Method[] getDeclaredMethods(Class<?> clazz, boolean publicOnly);
+    static native Method[] getDeclaredMethods(Class<?> c, boolean publicOnly);
 
     /**
-     * Returns the constructor or method if it is defined by {@code clazz}; null
-     * otherwise. This may return a non-public member.
-     *
-     * @param name the method name, or "<init>" to get a constructor.
+     * Returns the constructor or method if it is defined by {@code c}; null
+     * otherwise. This may return a non-public member. Use "<init>" to get a constructor.
      */
-    static native Member getDeclaredConstructorOrMethod(Class clazz, String name, Class[] args);
+    static native Member getDeclaredConstructorOrMethod(Class c, String name, Class[] args);
 
     /**
      * Returns the declaring {@code Class} of this {@code Class}. Returns
      * {@code null} if the class is not a member of another class or if this
-     * {@code Class} represents an array class, a primitive type or void.
-     *
-     * @return the declaring {@code Class} or {@code null}.
+     * {@code Class} represents an array class, a primitive type, or void.
      */
-    native public Class<?> getDeclaringClass();
+    public native Class<?> getDeclaringClass();
 
     /**
      * Returns the enclosing {@code Class} of this {@code Class}. If there is no
      * enclosing class the method returns {@code null}.
-     *
-     * @return the enclosing {@code Class} or {@code null}.
      */
-    native public Class<?> getEnclosingClass();
+    public native Class<?> getEnclosingClass();
 
     /**
-     * Gets the enclosing {@code Constructor} of this {@code Class}, if it is an
+     * Returns the enclosing {@code Constructor} of this {@code Class}, if it is an
      * anonymous or local/automatic class; otherwise {@code null}.
-     *
-     * @return the enclosing {@code Constructor} instance or {@code null}.
      */
-    native public Constructor<?> getEnclosingConstructor();
+    public native Constructor<?> getEnclosingConstructor();
 
     /**
-     * Gets the enclosing {@code Method} of this {@code Class}, if it is an
+     * Returns the enclosing {@code Method} of this {@code Class}, if it is an
      * anonymous or local/automatic class; otherwise {@code null}.
-     *
-     * @return the enclosing {@code Method} instance or {@code null}.
      */
-    native public Method getEnclosingMethod();
+    public native Method getEnclosingMethod();
 
     /**
-     * Gets the {@code enum} constants associated with this {@code Class}.
+     * Returns the {@code enum} constants associated with this {@code Class}.
      * Returns {@code null} if this {@code Class} does not represent an {@code
      * enum} type.
-     *
-     * @return an array with the {@code enum} constants or {@code null}.
      */
     @SuppressWarnings("unchecked") // we only cast after confirming that this class is an enum
     public T[] getEnumConstants() {
@@ -767,13 +656,10 @@
 
     /**
      * Returns a {@code Field} object which represents the public field with the
-     * specified name. This method first searches the class C represented by
+     * given name. This method first searches the class C represented by
      * this {@code Class}, then the interfaces implemented by C and finally the
      * superclasses of C.
      *
-     * @param name
-     *            the name of the requested field.
-     * @return the public field specified by {@code name}.
      * @throws NoSuchFieldException
      *             if the field can not be found.
      * @see #getDeclaredField(String)
@@ -820,8 +706,6 @@
      * <p>If there are no public fields or if this class represents an array class,
      * a primitive type or {@code void} then an empty array is returned.
      *
-     * @return an array with the public fields of the class represented by this
-     *         {@code Class}.
      * @see #getDeclaredFields()
      */
     public Field[] getFields() {
@@ -829,7 +713,7 @@
         getPublicFieldsRecursive(fields);
 
         /*
-         * The result may include duplicates when clazz implements an interface
+         * The result may include duplicates when this class implements an interface
          * through multiple paths. Remove those duplicates.
          */
         CollectionUtils.removeDuplicates(fields, Field.ORDER_BY_NAME_AND_DECLARING_CLASS);
@@ -857,12 +741,9 @@
     }
 
     /**
-     * Gets the {@link Type}s of the interfaces that this {@code Class} directly
+     * Returns the {@link Type}s of the interfaces that this {@code Class} directly
      * implements. If the {@code Class} represents a primitive type or {@code
      * void} then an empty array is returned.
-     *
-     * @return an array of {@link Type} instances directly implemented by the
-     *         class represented by this {@code class}.
      */
     public Type[] getGenericInterfaces() {
         GenericSignatureParser parser = new GenericSignatureParser(getClassLoader());
@@ -871,10 +752,8 @@
     }
 
     /**
-     * Gets the {@code Type} that represents the superclass of this {@code
+     * Returns the {@code Type} that represents the superclass of this {@code
      * class}.
-     *
-     * @return an instance of {@code Type} representing the superclass.
      */
     public Type getGenericSuperclass() {
         GenericSignatureParser parser = new GenericSignatureParser(getClassLoader());
@@ -884,29 +763,22 @@
 
     /**
      * Returns an array of {@code Class} objects that match the interfaces
-     * specified in the {@code implements} declaration of the class represented
+     * in the {@code implements} declaration of the class represented
      * by this {@code Class}. The order of the elements in the array is
      * identical to the order in the original class declaration. If the class
      * does not implement any interfaces, an empty array is returned.
-     *
-     * @return an array with the interfaces of the class represented by this
-     *         class.
      */
     public native Class<?>[] getInterfaces();
 
     /**
      * Returns a {@code Method} object which represents the public method with
-     * the specified name and parameter types. This method first searches the
+     * the given name and parameter types.
+     * {@code (Class[]) null} is equivalent to the empty array.
+     * This method first searches the
      * class C represented by this {@code Class}, then the superclasses of C and
      * finally the interfaces implemented by C and finally the superclasses of C
      * for a method with matching name.
      *
-     * @param name
-     *            the requested method's name.
-     * @param parameterTypes
-     *            the parameter types of the requested method.
-     *            {@code (Class[]) null} is equivalent to the empty array.
-     * @return the public field specified by {@code name}.
      * @throws NoSuchMethodException
      *             if the method can not be found.
      * @see #getDeclaredMethod(String, Class[])
@@ -924,13 +796,10 @@
      * for the class C represented by this {@code Class}. Methods may be
      * declared in C, the interfaces it implements or in the superclasses of C.
      * The elements in the returned array are in no particular order.
-     * <p>
-     * If there are no public methods or if this {@code Class} represents a
-     * primitive type or {@code void} then an empty array is returned.
-     * </p>
      *
-     * @return an array with the methods of the class represented by this
-     *         {@code Class}.
+     * <p>If there are no public methods or if this {@code Class} represents a
+     * primitive type or {@code void} then an empty array is returned.
+     *
      * @see #getDeclaredMethods()
      */
     public Method[] getMethods() {
@@ -946,7 +815,7 @@
     }
 
     /**
-     * Populates {@code result} with public methods defined by {@code clazz}, its
+     * Populates {@code result} with public methods defined by this class, its
      * superclasses, and all implemented interfaces, including overridden methods.
      */
     private void getPublicMethodsRecursive(List<Method> result) {
@@ -969,18 +838,15 @@
      * Returns an integer that represents the modifiers of the class represented
      * by this {@code Class}. The returned value is a combination of bits
      * defined by constants in the {@link Modifier} class.
-     *
-     * @return the modifiers of the class represented by this {@code Class}.
      */
     public int getModifiers() {
         return getModifiers(this, false);
     }
 
     /*
-     * Return the modifiers for the given class.
+     * Returns the modifiers for the given class.
      *
-     * @param clazz the class of interest
-     * @ignoreInnerClassesAttrib determines whether we look for and use the
+     * {@code ignoreInnerClassesAttrib} determines whether we look for and use the
      *     flags from an "inner class" attribute
      */
     private static native int getModifiers(Class<?> clazz, boolean ignoreInnerClassesAttrib);
@@ -989,8 +855,6 @@
      * Returns the name of the class represented by this {@code Class}. For a
      * description of the format which is used, see the class definition of
      * {@link Class}.
-     *
-     * @return the name of the class represented by this {@code Class}.
      */
     public String getName() {
         String result = name;
@@ -1033,8 +897,6 @@
 
     /*
      * Returns the simple name of a member or local class, or null otherwise.
-     *
-     * @return The name.
      */
     private native String getInnerClassName();
 
@@ -1046,20 +908,15 @@
     }
 
     /**
-     * Returns the URL of the resource specified by {@code resName}. The mapping
-     * between the resource name and the URL is managed by the class' class
-     * loader.
+     * Returns the URL of the given resource, or null if the resource is not found.
+     * The mapping between the resource name and the URL is managed by the class' class loader.
      *
-     * @param resName
-     *            the name of the resource.
-     * @return the requested resource's {@code URL} object or {@code null} if
-     *         the resource can not be found.
      * @see ClassLoader
      */
-    public URL getResource(String resName) {
+    public URL getResource(String resourceName) {
         // Get absolute resource name, but without the leading slash
-        if (resName.startsWith("/")) {
-            resName = resName.substring(1);
+        if (resourceName.startsWith("/")) {
+            resourceName = resourceName.substring(1);
         } else {
             String pkg = getName();
             int dot = pkg.lastIndexOf('.');
@@ -1069,33 +926,29 @@
                 pkg = "";
             }
 
-            resName = pkg + "/" + resName;
+            resourceName = pkg + "/" + resourceName;
         }
 
         // Delegate to proper class loader
         ClassLoader loader = getClassLoader();
         if (loader != null) {
-            return loader.getResource(resName);
+            return loader.getResource(resourceName);
         } else {
-            return ClassLoader.getSystemResource(resName);
+            return ClassLoader.getSystemResource(resourceName);
         }
     }
 
     /**
-     * Returns a read-only stream for the contents of the resource specified by
-     * {@code resName}. The mapping between the resource name and the stream is
-     * managed by the class' class loader.
+     * Returns a read-only stream for the contents of the given resource, or null if the resource
+     * is not found.
+     * The mapping between the resource name and the stream is managed by the class' class loader.
      *
-     * @param resName
-     *            the name of the resource.
-     * @return a stream for the requested resource or {@code null} if no
-     *         resource with the specified name can be found.
      * @see ClassLoader
      */
-    public InputStream getResourceAsStream(String resName) {
+    public InputStream getResourceAsStream(String resourceName) {
         // Get absolute resource name, but without the leading slash
-        if (resName.startsWith("/")) {
-            resName = resName.substring(1);
+        if (resourceName.startsWith("/")) {
+            resourceName = resourceName.substring(1);
         } else {
             String pkg = getName();
             int dot = pkg.lastIndexOf('.');
@@ -1105,15 +958,15 @@
                 pkg = "";
             }
 
-            resName = pkg + "/" + resName;
+            resourceName = pkg + "/" + resourceName;
         }
 
         // Delegate to proper class loader
         ClassLoader loader = getClassLoader();
         if (loader != null) {
-            return loader.getResourceAsStream(resName);
+            return loader.getResourceAsStream(resourceName);
         } else {
-            return ClassLoader.getSystemResourceAsStream(resName);
+            return ClassLoader.getSystemResourceAsStream(resourceName);
         }
     }
 
@@ -1122,8 +975,6 @@
      * All classes from any given dex file will have the same signers, but different dex
      * files may have different signers. This does not fit well with the original
      * {@code ClassLoader}-based model of {@code getSigners}.)
-     *
-     * @return null.
      */
     public Object[] getSigners() {
         // See http://code.google.com/p/android/issues/detail?id=1766.
@@ -1136,8 +987,6 @@
      * the {@code Object} class, a primitive type, an interface or void then the
      * method returns {@code null}. If this {@code Class} represents an array
      * class then the {@code Object} class is returned.
-     *
-     * @return the superclass of the class represented by this {@code Class}.
      */
     public native Class<? super T> getSuperclass();
 
@@ -1145,9 +994,6 @@
      * Returns an array containing {@code TypeVariable} objects for type
      * variables declared by the generic class represented by this {@code
      * Class}. Returns an empty array if the class is not generic.
-     *
-     * @return an array with the type variables of the class represented by this
-     *         class.
      */
     @SuppressWarnings("unchecked")
     public synchronized TypeVariable<Class<T>>[] getTypeParameters() {
@@ -1157,10 +1003,7 @@
     }
 
     /**
-     * Indicates whether this {@code Class} represents an annotation class.
-     *
-     * @return {@code true} if this {@code Class} represents an annotation
-     *         class; {@code false} otherwise.
+     * Tests whether this {@code Class} represents an annotation class.
      */
     public boolean isAnnotation() {
         final int ACC_ANNOTATION = 0x2000;  // not public in reflect.Modifiers
@@ -1189,59 +1032,43 @@
     }
 
     /**
-     * Indicates whether the class represented by this {@code Class} is
-     * anonymously declared.
-     *
-     * @return {@code true} if the class represented by this {@code Class} is
-     *         anonymous; {@code false} otherwise.
+     * Tests whether the class represented by this {@code Class} is
+     * anonymous.
      */
     native public boolean isAnonymousClass();
 
     /**
-     * Indicates whether the class represented by this {@code Class} is an array
-     * class.
-     *
-     * @return {@code true} if the class represented by this {@code Class} is an
-     *         array class; {@code false} otherwise.
+     * Tests whether the class represented by this {@code Class} is an array class.
      */
     public boolean isArray() {
         return getComponentType() != null;
     }
 
     /**
-     * Indicates whether the specified class type can be converted to the class
+     * Tests whether the given class type can be converted to the class
      * represented by this {@code Class}. Conversion may be done via an identity
      * conversion or a widening reference conversion (if either the receiver or
      * the argument represent primitive types, only the identity conversion
      * applies).
      *
-     * @param cls
-     *            the class to check.
-     * @return {@code true} if {@code cls} can be converted to the class
-     *         represented by this {@code Class}; {@code false} otherwise.
      * @throws NullPointerException
-     *             if {@code cls} is {@code null}.
+     *             if {@code c} is {@code null}.
      */
-    public native boolean isAssignableFrom(Class<?> cls);
+    public native boolean isAssignableFrom(Class<?> c);
 
     /**
-     * Indicates whether the class represented by this {@code Class} is an
+     * Tests whether the class represented by this {@code Class} is an
      * {@code enum}.
-     *
-     * @return {@code true} if the class represented by this {@code Class} is an
-     *         {@code enum}; {@code false} otherwise.
      */
     public boolean isEnum() {
         return ((getModifiers() & 0x4000) != 0) && (getSuperclass() == Enum.class);
     }
 
     /**
-     * Indicates whether the specified object can be cast to the class
+     * Tests whether the given object can be cast to the class
      * represented by this {@code Class}. This is the runtime version of the
      * {@code instanceof} operator.
      *
-     * @param object
-     *            the object to check.
      * @return {@code true} if {@code object} can be cast to the type
      *         represented by this {@code Class}; {@code false} if {@code
      *         object} is {@code null} or cannot be cast.
@@ -1249,19 +1076,13 @@
     public native boolean isInstance(Object object);
 
     /**
-     * Indicates whether this {@code Class} represents an interface.
-     *
-     * @return {@code true} if this {@code Class} represents an interface;
-     *         {@code false} otherwise.
+     * Tests whether this {@code Class} represents an interface.
      */
     public native boolean isInterface();
 
     /**
-     * Indicates whether the class represented by this {@code Class} is defined
+     * Tests whether the class represented by this {@code Class} is defined
      * locally.
-     *
-     * @return {@code true} if the class represented by this {@code Class} is
-     *         defined locally; {@code false} otherwise.
      */
     public boolean isLocalClass() {
         boolean enclosed = (getEnclosingMethod() != null ||
@@ -1270,29 +1091,20 @@
     }
 
     /**
-     * Indicates whether the class represented by this {@code Class} is a member
+     * Tests whether the class represented by this {@code Class} is a member
      * class.
-     *
-     * @return {@code true} if the class represented by this {@code Class} is a
-     *         member class; {@code false} otherwise.
      */
     public boolean isMemberClass() {
         return getDeclaringClass() != null;
     }
 
     /**
-     * Indicates whether this {@code Class} represents a primitive type.
-     *
-     * @return {@code true} if this {@code Class} represents a primitive type;
-     *         {@code false} otherwise.
+     * Tests whether this {@code Class} represents a primitive type.
      */
     public native boolean isPrimitive();
 
     /**
-     * Indicates whether this {@code Class} represents a synthetic type.
-     *
-     * @return {@code true} if this {@code Class} represents a synthetic type;
-     *         {@code false} otherwise.
+     * Tests whether this {@code Class} represents a synthetic type.
      */
     public boolean isSynthetic() {
         final int ACC_SYNTHETIC = 0x1000;   // not public in reflect.Modifiers
@@ -1309,7 +1121,6 @@
      * constructor exists but is not accessible from the context where this
      * method is invoked, an {@code IllegalAccessException} is thrown.
      *
-     * @return a new instance of the class represented by this {@code Class}.
      * @throws IllegalAccessException
      *             if the default constructor is not visible.
      * @throws InstantiationException
@@ -1334,9 +1145,6 @@
      * Returns the {@code Package} of which the class represented by this
      * {@code Class} is a member. Returns {@code null} if no {@code Package}
      * object was created by the class loader of the class.
-     *
-     * @return Package the {@code Package} of which this {@code Class} is a
-     *         member or {@code null}.
      */
     public Package getPackage() {
         // TODO This might be a hack, but the VM doesn't have the necessary info.
@@ -1353,42 +1161,33 @@
      * Returns the assertion status for the class represented by this {@code
      * Class}. Assertion is enabled / disabled based on the class loader,
      * package or class default at runtime.
-     *
-     * @return the assertion status for the class represented by this {@code
-     *         Class}.
      */
     public native boolean desiredAssertionStatus();
 
     /**
-     * Casts this {@code Class} to represent a subclass of the specified class.
+     * Casts this {@code Class} to represent a subclass of the given class.
      * If successful, this {@code Class} is returned; otherwise a {@code
      * ClassCastException} is thrown.
      *
-     * @param clazz
-     *            the required type.
-     * @return this {@code Class} cast as a subclass of the given type.
      * @throws ClassCastException
-     *             if this {@code Class} cannot be cast to the specified type.
+     *             if this {@code Class} cannot be cast to the given type.
      */
     @SuppressWarnings("unchecked")
-    public <U> Class<? extends U> asSubclass(Class<U> clazz) {
-        if (clazz.isAssignableFrom(this)) {
+    public <U> Class<? extends U> asSubclass(Class<U> c) {
+        if (c.isAssignableFrom(this)) {
             return (Class<? extends U>)this;
         }
         String actualClassName = this.getName();
-        String desiredClassName = clazz.getName();
+        String desiredClassName = c.getName();
         throw new ClassCastException(actualClassName + " cannot be cast to " + desiredClassName);
     }
 
     /**
-     * Casts the specified object to the type represented by this {@code Class}.
+     * Casts the given object to the type represented by this {@code Class}.
      * If the object is {@code null} then the result is also {@code null}.
      *
-     * @param obj
-     *            the object to cast.
-     * @return the object that has been cast.
      * @throws ClassCastException
-     *             if the object cannot be cast to the specified type.
+     *             if the object cannot be cast to the given type.
      */
     @SuppressWarnings("unchecked")
     public T cast(Object obj) {
diff --git a/luni/src/main/java/java/lang/Math.java b/luni/src/main/java/java/lang/Math.java
index 22c89ce..f8d22ed 100644
--- a/luni/src/main/java/java/lang/Math.java
+++ b/luni/src/main/java/java/lang/Math.java
@@ -200,7 +200,7 @@
 
     /**
      * Returns the double conversion of the most negative (closest to negative
-     * infinity) integer value which is greater than the argument.
+     * infinity) integer value greater than or equal to the argument.
      * <p>
      * Special cases:
      * <ul>
@@ -211,10 +211,6 @@
      * <li>{@code ceil(-infinity) = -infinity}</li>
      * <li>{@code ceil(NaN) = NaN}</li>
      * </ul>
-     *
-     * @param d
-     *            the value whose closest integer value has to be computed.
-     * @return the ceiling of the argument.
      */
     public static native double ceil(double d);
 
@@ -301,7 +297,7 @@
 
     /**
      * Returns the double conversion of the most positive (closest to positive
-     * infinity) integer value which is less than the argument.
+     * infinity) integer value less than or equal to the argument.
      * <p>
      * Special cases:
      * <ul>
@@ -311,10 +307,6 @@
      * <li>{@code floor(-infinity) = -infinity}</li>
      * <li>{@code floor(NaN) = NaN}</li>
      * </ul>
-     *
-     * @param d
-     *            the value whose closest integer value has to be computed.
-     * @return the floor of the argument.
      */
     public static native double floor(double d);
 
diff --git a/luni/src/main/java/java/lang/Package.java b/luni/src/main/java/java/lang/Package.java
index 00cc81c..7e30883 100644
--- a/luni/src/main/java/java/lang/Package.java
+++ b/luni/src/main/java/java/lang/Package.java
@@ -73,7 +73,7 @@
     }
 
     /**
-     * Gets the annotation associated with the specified annotation type and
+     * Returns the annotation associated with the specified annotation type and
      * this package, if present.
      *
      * @param annotationType
@@ -92,17 +92,23 @@
     }
 
     /**
-     * Returns an empty array. Package annotations are not supported on Android.
+     * Returns an array of this package's annotations.
      */
     public Annotation[] getAnnotations() {
-        return NO_ANNOTATIONS;
+        try {
+            Class<?> c = Class.forName(getName() + ".package-info");
+            return c.getAnnotations();
+        } catch (Exception ex) {
+            return NO_ANNOTATIONS;
+        }
     }
 
     /**
-     * Returns an empty array. Package annotations are not supported on Android.
+     * Returns an array of this package's declared annotations. Package annotations aren't
+     * inherited, so this is equivalent to {@link #getAnnotations}.
      */
     public Annotation[] getDeclaredAnnotations() {
-        return NO_ANNOTATIONS;
+        return getAnnotations();
     }
 
     /**
diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java
index a2debfd..5ded981 100644
--- a/luni/src/main/java/java/lang/Runtime.java
+++ b/luni/src/main/java/java/lang/Runtime.java
@@ -32,6 +32,7 @@
 
 package java.lang;
 
+import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMDebug;
 import dalvik.system.VMStack;
 import java.io.File;
@@ -44,8 +45,9 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
+import libcore.io.IoUtils;
 import libcore.io.Libcore;
-import static libcore.io.OsConstants._SC_NPROCESSORS_ONLN;
+import static libcore.io.OsConstants._SC_NPROCESSORS_CONF;
 
 /**
  * Allows Java applications to interface with the environment in which they are
@@ -90,7 +92,7 @@
     /**
      * Prevent this class from being instantiated.
      */
-    private Runtime(){
+    private Runtime() {
         String pathList = System.getProperty("java.library.path", ".");
         String pathSep = System.getProperty("path.separator", ":");
         String fileSep = System.getProperty("file.separator", "/");
@@ -242,14 +244,12 @@
     }
 
     /**
-     * Causes the VM to stop running and the program to exit. If
-     * {@link #runFinalizersOnExit(boolean)} has been previously invoked with a
+     * Causes the VM to stop running and the program to exit.
+     * If {@link #runFinalizersOnExit(boolean)} has been previously invoked with a
      * {@code true} argument, then all objects will be properly
      * garbage-collected and finalized first.
-     *
-     * @param code
-     *            the return code. By convention, non-zero return codes indicate
-     *            abnormal terminations.
+     * Use 0 to signal success to the calling process and 1 to signal failure.
+     * This method is unlikely to be useful to an Android application.
      */
     public void exit(int code) {
         // Make sure we don't try this several times
@@ -290,14 +290,6 @@
     }
 
     /**
-     * Returns the amount of free memory resources which are available to the
-     * running program.
-     *
-     * @return the approximate amount of free memory, measured in bytes.
-     */
-    public native long freeMemory();
-
-    /**
      * Indicates to the VM that it would be a good time to run the
      * garbage collector. Note that this is a hint only. There is no guarantee
      * that the garbage collector will actually be run.
@@ -305,9 +297,7 @@
     public native void gc();
 
     /**
-     * Returns the single {@code Runtime} instance.
-     *
-     * @return the {@code Runtime} object for the current application.
+     * Returns the single {@code Runtime} instance for the current application.
      */
     public static Runtime getRuntime() {
         return mRuntime;
@@ -329,13 +319,13 @@
     }
 
     /*
-     * Loads and links a library without security checks.
+     * Loads and links the given library without security checks.
      */
     void load(String pathName, ClassLoader loader) {
         if (pathName == null) {
             throw new NullPointerException("pathName == null");
         }
-        String error = nativeLoad(pathName, loader);
+        String error = doLoad(pathName, loader);
         if (error != null) {
             throw new UnsatisfiedLinkError(error);
         }
@@ -356,17 +346,17 @@
     }
 
     /*
-     * Loads and links a library without security checks.
+     * Searches for a library, then loads and links it without security checks.
      */
     void loadLibrary(String libraryName, ClassLoader loader) {
         if (loader != null) {
             String filename = loader.findLibrary(libraryName);
             if (filename == null) {
-                throw new UnsatisfiedLinkError("Couldn't load " + libraryName
-                                               + " from loader " + loader
-                                               + ": findLibrary returned null");
+                throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
+                                               " from loader " + loader +
+                                               ": findLibrary returned null");
             }
-            String error = nativeLoad(filename, loader);
+            String error = doLoad(filename, loader);
             if (error != null) {
                 throw new UnsatisfiedLinkError(error);
             }
@@ -379,8 +369,9 @@
         for (String directory : mLibPaths) {
             String candidate = directory + filename;
             candidates.add(candidate);
-            if (new File(candidate).exists()) {
-                String error = nativeLoad(candidate, loader);
+
+            if (IoUtils.canOpenReadOnly(candidate)) {
+                String error = doLoad(candidate, loader);
                 if (error == null) {
                     return; // We successfully loaded the library. Job done.
                 }
@@ -396,7 +387,40 @@
 
     private static native void nativeExit(int code);
 
-    private static native String nativeLoad(String filename, ClassLoader loader);
+    private String doLoad(String name, ClassLoader loader) {
+        // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
+        // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
+
+        // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
+        // libraries with no dependencies just fine, but an app that has multiple libraries that
+        // depend on each other needed to load them in most-dependent-first order.
+
+        // We added API to Android's dynamic linker so we can update the library path used for
+        // the currently-running process. We pull the desired path out of the ClassLoader here
+        // and pass it to nativeLoad so that it can call the private dynamic linker API.
+
+        // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
+        // beginning because multiple apks can run in the same process and third party code can
+        // use its own BaseDexClassLoader.
+
+        // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
+        // dlopen(3) calls made from a .so's JNI_OnLoad to work too.
+
+        // So, find out what the native library search path is for the ClassLoader in question...
+        String ldLibraryPath = null;
+        if (loader != null && loader instanceof BaseDexClassLoader) {
+            ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
+        }
+        // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
+        // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
+        // internal natives.
+        synchronized (this) {
+            return nativeLoad(name, loader, ldLibraryPath);
+        }
+    }
+
+    // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
+    private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);
 
     /**
      * Provides a hint to the VM that it would be useful to attempt
@@ -427,30 +451,14 @@
     }
 
     /**
-     * Returns the total amount of memory which is available to the running
-     * program.
-     *
-     * @return the total amount of memory, measured in bytes.
-     */
-    public native long totalMemory();
-
-    /**
      * Switches the output of debug information for instructions on or off.
      * On Android, this method does nothing.
-     *
-     * @param enable
-     *            {@code true} to switch tracing on, {@code false} to switch it
-     *            off.
      */
     public void traceInstructions(boolean enable) {
     }
 
     /**
      * Switches the output of debug information for methods on or off.
-     *
-     * @param enable
-     *            {@code true} to switch tracing on, {@code false} to switch it
-     *            off.
      */
     public void traceMethodCalls(boolean enable) {
         if (enable != tracingMethods) {
@@ -584,15 +592,10 @@
     }
 
     /**
-     * Causes the VM to stop running, and the program to exit.
-     * Neither shutdown hooks nor finalizers are run before.
-     *
-     * @param code
-     *            the return code. By convention, non-zero return codes indicate
-     *            abnormal terminations.
-     * @see #addShutdownHook(Thread)
-     * @see #removeShutdownHook(Thread)
-     * @see #runFinalizersOnExit(boolean)
+     * Causes the VM to stop running, and the program to exit with the given return code.
+     * Use 0 to signal success to the calling process and 1 to signal failure.
+     * Neither shutdown hooks nor finalizers are run before exiting.
+     * This method is unlikely to be useful to an Android application.
      */
     public void halt(int code) {
         // Get out of here...
@@ -600,18 +603,36 @@
     }
 
     /**
-     * Returns the number of processors available to the VM, at least 1.
+     * Returns the number of processor cores available to the VM, at least 1.
+     * Traditionally this returned the number currently online,
+     * but many mobile devices are able to take unused cores offline to
+     * save power, so releases newer than Android 4.2 (Jelly Bean) return the maximum number of
+     * cores that could be made available if there were no power or heat
+     * constraints.
      */
     public int availableProcessors() {
-        return (int) Libcore.os.sysconf(_SC_NPROCESSORS_ONLN);
+        return (int) Libcore.os.sysconf(_SC_NPROCESSORS_CONF);
     }
 
     /**
-     * Returns the maximum amount of memory that may be used by the virtual
-     * machine, or {@code Long.MAX_VALUE} if there is no such limit.
-     *
-     * @return the maximum amount of memory that the VM will try to
-     *         allocate, measured in bytes.
+     * Returns the number of bytes currently available on the heap without expanding the heap. See
+     * {@link #totalMemory} for the heap's current size. When these bytes are exhausted, the heap
+     * may expand. See {@link #maxMemory} for that limit.
+     */
+    public native long freeMemory();
+
+    /**
+     * Returns the number of bytes taken by the heap at its current size. The heap may expand or
+     * contract over time, as the number of live objects increases or decreases. See
+     * {@link #maxMemory} for the maximum heap size, and {@link #freeMemory} for an idea of how much
+     * the heap could currently contract.
+     */
+    public native long totalMemory();
+
+    /**
+     * Returns the maximum number of bytes the heap can expand to. See {@link #totalMemory} for the
+     * current number of bytes taken by the heap, and {@link #freeMemory} for the current number of
+     * those bytes actually used by live objects.
      */
     public native long maxMemory();
 }
diff --git a/luni/src/main/java/java/lang/StrictMath.java b/luni/src/main/java/java/lang/StrictMath.java
index 6571b2d..f409c06 100644
--- a/luni/src/main/java/java/lang/StrictMath.java
+++ b/luni/src/main/java/java/lang/StrictMath.java
@@ -215,7 +215,7 @@
 
     /**
      * Returns the double conversion of the most negative (closest to negative
-     * infinity) integer value which is greater than the argument.
+     * infinity) integer value greater than or equal to the argument.
      * <p>
      * Special cases:
      * <ul>
@@ -226,10 +226,6 @@
      * <li>{@code ceil(-infinity) = -infinity}</li>
      * <li>{@code ceil(NaN) = NaN}</li>
      * </ul>
-     *
-     * @param d
-     *            the value whose closest integer value has to be computed.
-     * @return the ceiling of the argument.
      */
     public static native double ceil(double d);
 
@@ -309,7 +305,7 @@
 
     /**
      * Returns the double conversion of the most positive (closest to
-     * positive infinity) integer value which is less than the argument.
+     * positive infinity) integer less than or equal to the argument.
      * <p>
      * Special cases:
      * <ul>
@@ -319,9 +315,6 @@
      * <li>{@code floor(-infinity) = -infinity}</li>
      * <li>{@code floor(NaN) = NaN}</li>
      * </ul>
-     *
-     * @param d the value whose closest integer value has to be computed.
-     * @return the floor of the argument.
      */
     public static native double floor(double d);
 
diff --git a/luni/src/main/java/java/lang/String.java b/luni/src/main/java/java/lang/String.java
index f3aeb64..89691e7 100644
--- a/luni/src/main/java/java/lang/String.java
+++ b/luni/src/main/java/java/lang/String.java
@@ -282,14 +282,14 @@
 
                     if (idx + utfCount > last) {
                         v[s++] = REPLACEMENT_CHAR;
-                        break;
+                        continue;
                     }
 
                     // Extract usable bits from b0
                     int val = b0 & (0x1f >> (utfCount - 1));
-                    for (int i = 0; i < utfCount; i++) {
+                    for (int i = 0; i < utfCount; ++i) {
                         byte b = d[idx++];
-                        if ((b & 0xC0) != 0x80) {
+                        if ((b & 0xc0) != 0x80) {
                             v[s++] = REPLACEMENT_CHAR;
                             idx--; // Put the input char back
                             continue outer;
diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java
index df84c61..98ce6cb 100644
--- a/luni/src/main/java/java/lang/System.java
+++ b/luni/src/main/java/java/lang/System.java
@@ -135,6 +135,11 @@
      * starting at offset {@code srcPos}, into the array {@code dst},
      * starting at offset {@code dstPos}.
      *
+     * <p>The source and destination arrays can be the same array,
+     * in which case copying is performed as if the source elements
+     * are first copied into a temporary array and then into the
+     * destination array.
+     *
      * @param src
      *            the source array to copy the content.
      * @param srcPos
@@ -149,22 +154,27 @@
     public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length);
 
     /**
-     * Returns the current system time in milliseconds since January 1, 1970
-     * 00:00:00 UTC. This method shouldn't be used for measuring timeouts or
-     * other elapsed time measurements, as changing the system time can affect
-     * the results.
+     * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
      *
-     * @return the local system time in milliseconds.
+     * <p>This method always returns UTC times, regardless of the system's time zone.
+     * This is often called "Unix time" or "epoch time".
+     * Use a {@link java.text.DateFormat} instance to format this time for display to a human.
+     *
+     * <p>This method shouldn't be used for measuring timeouts or
+     * other elapsed time measurements, as changing the system time can affect
+     * the results. Use {@link #nanoTime} for that.
      */
     public static native long currentTimeMillis();
 
     /**
      * Returns the current timestamp of the most precise timer available on the
-     * local system. This timestamp can only be used to measure an elapsed
-     * period by comparing it against another timestamp. It cannot be used as a
-     * very exact system time expression.
+     * local system.
      *
-     * @return the current timestamp in nanoseconds.
+     * <p>This timestamp should only be used to measure a duration by comparing it
+     * against another timestamp from the same process on the same device.
+     * Values returned by this method do not have a defined correspondence to
+     * wall clock times; the zero value is typically whenever the device last booted.
+     * Use {@link #currentTimeMillis} if you want to know what time it is.
      */
     public static native long nanoTime();
 
@@ -211,13 +221,6 @@
         return (value != null) ? value : defaultValue;
     }
 
-    /*
-     * Returns an environment variable. No security checks are performed.
-     * @param var the name of the environment variable
-     * @return the value of the specified environment variable
-     */
-    private static native String getEnvByName(String name);
-
     /**
      * Returns an unmodifiable map of all available environment variables.
      *
@@ -321,6 +324,7 @@
         // Undocumented Android-only properties.
         p.put("android.icu.library.version", ICU.getIcuVersion());
         p.put("android.icu.unicode.version", ICU.getUnicodeVersion());
+        p.put("android.icu.cldr.version", ICU.getCldrVersion());
         // TODO: it would be nice to have this but currently it causes circularity.
         // p.put("android.tzdata.version", ZoneInfoDB.getVersion());
         parsePropertyAssignments(p, specialProperties());
@@ -409,58 +413,44 @@
     /**
      * Returns the value of a particular system property. The {@code
      * defaultValue} will be returned if no such property has been found.
-     *
-     * @param prop
-     *            the name of the system property to look up.
-     * @param defaultValue
-     *            the return value if the system property with the given name
-     *            does not exist.
-     * @return the value of the specified system property or the {@code
-     *         defaultValue} if the property does not exist.
      */
-    public static String getProperty(String prop, String defaultValue) {
-        if (prop.isEmpty()) {
-            throw new IllegalArgumentException();
-        }
-        return getProperties().getProperty(prop, defaultValue);
+    public static String getProperty(String name, String defaultValue) {
+        checkPropertyName(name);
+        return getProperties().getProperty(name, defaultValue);
     }
 
     /**
      * Sets the value of a particular system property.
      *
-     * @param prop
-     *            the name of the system property to be changed.
-     * @param value
-     *            the value to associate with the given property {@code prop}.
      * @return the old value of the property or {@code null} if the property
      *         didn't exist.
      */
-    public static String setProperty(String prop, String value) {
-        if (prop.isEmpty()) {
-            throw new IllegalArgumentException();
-        }
-        return (String) getProperties().setProperty(prop, value);
+    public static String setProperty(String name, String value) {
+        checkPropertyName(name);
+        return (String) getProperties().setProperty(name, value);
     }
 
     /**
      * Removes a specific system property.
      *
-     * @param key
-     *            the name of the system property to be removed.
      * @return the property value or {@code null} if the property didn't exist.
      * @throws NullPointerException
-     *             if the argument {@code key} is {@code null}.
+     *             if the argument is {@code null}.
      * @throws IllegalArgumentException
-     *             if the argument {@code key} is empty.
+     *             if the argument is empty.
      */
-    public static String clearProperty(String key) {
-        if (key == null) {
-            throw new NullPointerException("key == null");
+    public static String clearProperty(String name) {
+        checkPropertyName(name);
+        return (String) getProperties().remove(name);
+    }
+
+    private static void checkPropertyName(String name) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
         }
-        if (key.isEmpty()) {
-            throw new IllegalArgumentException();
+        if (name.isEmpty()) {
+            throw new IllegalArgumentException("name is empty");
         }
-        return (String) getProperties().remove(key);
     }
 
     /**
diff --git a/luni/src/main/java/java/lang/Thread.java b/luni/src/main/java/java/lang/Thread.java
index 210b90c..cc0975f 100644
--- a/luni/src/main/java/java/lang/Thread.java
+++ b/luni/src/main/java/java/lang/Thread.java
@@ -41,33 +41,20 @@
 
 /**
  * A {@code Thread} is a concurrent unit of execution. It has its own call stack
- * for methods being invoked, their arguments and local variables. Each virtual
- * machine instance has at least one main {@code Thread} running when it is
- * started; typically, there are several others for housekeeping. The
- * application might decide to launch additional {@code Thread}s for specific
- * purposes.
- * <p>
- * {@code Thread}s in the same VM interact and synchronize by the use of shared
- * objects and monitors associated with these objects. Synchronized methods and
- * part of the API in {@link Object} also allow {@code Thread}s to cooperate.
- * <p>
- * There are basically two main ways of having a {@code Thread} execute
- * application code. One is providing a new class that extends {@code Thread}
- * and overriding its {@link #run()} method. The other is providing a new
- * {@code Thread} instance with a {@link Runnable} object during its creation.
- * In both cases, the {@link #start()} method must be called to actually execute
+ * for methods being invoked, their arguments and local variables. Each application
+ * has at least one thread running when it is started, the main thread, in the main
+ * {@link ThreadGroup}. The runtime keeps its own threads in the system thread
+ * group.
+ *
+ * <p>There are two ways to execute code in a new thread.
+ * You can either subclass {@code Thread} and overriding its {@link #run()} method,
+ * or construct a new {@code Thread} and pass a {@link Runnable} to the constructor.
+ * In either case, the {@link #start()} method must be called to actually execute
  * the new {@code Thread}.
- * <p>
- * Each {@code Thread} has an integer priority that basically determines the
- * amount of CPU time the {@code Thread} gets. It can be set using the
- * {@link #setPriority(int)} method. A {@code Thread} can also be made a daemon,
- * which makes it run in the background. The latter also affects VM termination
- * behavior: the VM does not terminate automatically as long as there are
- * non-daemon threads running.
  *
- * @see java.lang.Object
- * @see java.lang.ThreadGroup
- *
+ * <p>Each {@code Thread} has an integer priority that affect how the thread is
+ * scheduled by the OS. A new thread inherits the priority of its parent.
+ * A thread's priority can be set using the {@link #setPriority(int)} method.
  */
 public class Thread implements Runnable {
     private static final int NANOS_PER_MILLI = 1000000;
@@ -117,16 +104,23 @@
 
     /**
      * The maximum priority value allowed for a thread.
+     * This corresponds to (but does not have the same value as)
+     * {@code android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY}.
      */
     public static final int MAX_PRIORITY = 10;
 
     /**
      * The minimum priority value allowed for a thread.
+     * This corresponds to (but does not have the same value as)
+     * {@code android.os.Process.THREAD_PRIORITY_LOWEST}.
      */
     public static final int MIN_PRIORITY = 1;
 
     /**
-     * The normal (default) priority value assigned to threads.
+     * The normal (default) priority value assigned to the main thread.
+     * This corresponds to (but does not have the same value as)
+     * {@code android.os.Process.THREAD_PRIORITY_DEFAULT}.
+
      */
     public static final int NORM_PRIORITY = 5;
 
@@ -477,13 +471,12 @@
     }
 
     /**
-     * Destroys the receiver without any monitor cleanup.
-     *
+     * Throws {@code UnsupportedOperationException}.
      * @deprecated Not implemented.
      */
     @Deprecated
     public void destroy() {
-        throw new NoSuchMethodError("Thread.destroy()"); // TODO Externalize???
+        throw new UnsupportedOperationException();
     }
 
     /**
@@ -567,8 +560,6 @@
 
     /**
      * Returns the name of the Thread.
-     *
-     * @return the Thread's name
      */
     public final String getName() {
         return name;
@@ -576,9 +567,6 @@
 
     /**
      * Returns the priority of the Thread.
-     *
-     * @return the Thread's priority
-     * @see Thread#setPriority
      */
     public final int getPriority() {
         return priority;
@@ -669,16 +657,19 @@
      * @see Thread#isInterrupted
      */
     public void interrupt() {
+        // Interrupt this thread before running actions so that other
+        // threads that observe the interrupt as a result of an action
+        // will see that this thread is in the interrupted state.
+        VMThread vmt = this.vmThread;
+        if (vmt != null) {
+            vmt.interrupt();
+        }
+
         synchronized (interruptActions) {
             for (int i = interruptActions.size() - 1; i >= 0; i--) {
                 interruptActions.get(i).run();
             }
         }
-
-        VMThread vmt = this.vmThread;
-        if (vmt != null) {
-            vmt.interrupt();
-        }
     }
 
     /**
@@ -710,14 +701,10 @@
     }
 
     /**
-     * Returns a <code>boolean</code> indicating whether the receiver is a
-     * daemon Thread (<code>true</code>) or not (<code>false</code>) A
-     * daemon Thread only runs as long as there are non-daemon Threads running.
-     * When the last non-daemon Thread ends, the whole program ends no matter if
-     * it had daemon Threads still running or not.
-     *
-     * @return a <code>boolean</code> indicating whether the Thread is a daemon
-     * @see Thread#setDaemon
+     * Tests whether this is a daemon thread.
+     * A daemon thread only runs as long as there are non-daemon threads running.
+     * When the last non-daemon thread ends, the runtime will exit. This is not
+     * normally relevant to applications with a UI.
      */
     public final boolean isDaemon() {
         return daemon;
@@ -792,7 +779,7 @@
      */
     public final void join(long millis, int nanos) throws InterruptedException {
         if (millis < 0 || nanos < 0 || nanos >= NANOS_PER_MILLI) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("bad timeout: millis=" + millis + ",nanos=" + nanos);
         }
 
         // avoid overflow: if total > 292,277 years, just wait forever
@@ -836,9 +823,7 @@
 
     /**
      * Throws {@code UnsupportedOperationException}.
-     *
-     * @see Thread#suspend()
-     * @deprecated Used with deprecated method {@link Thread#suspend}
+     * @deprecated Only useful in conjunction with deprecated method {@link Thread#suspend}.
      */
     @Deprecated
     public final void resume() {
@@ -868,23 +853,25 @@
     }
 
     /**
-     * Set if the receiver is a daemon Thread or not. This can only be done
-     * before the Thread starts running.
-     *
-     * @param isDaemon
-     *            indicates whether the Thread should be daemon or not
-     * @see Thread#isDaemon
+     * Marks this thread as a daemon thread.
+     * A daemon thread only runs as long as there are non-daemon threads running.
+     * When the last non-daemon thread ends, the runtime will exit. This is not
+     * normally relevant to applications with a UI.
+     * @throws IllegalThreadStateException - if this thread has already started.
      */
     public final void setDaemon(boolean isDaemon) {
-        if (hasBeenStarted) {
-            throw new IllegalThreadStateException("Thread already started."); // TODO Externalize?
-        }
-
+        checkNotStarted();
         if (vmThread == null) {
             daemon = isDaemon;
         }
     }
 
+    private void checkNotStarted() {
+        if (hasBeenStarted) {
+            throw new IllegalThreadStateException("Thread already started");
+        }
+    }
+
     /**
      * Sets the default uncaught exception handler. This handler is invoked in
      * case any Thread dies due to an unhandled exception.
@@ -955,21 +942,16 @@
     }
 
     /**
-     * Sets the priority of the Thread. Note that the final priority set may not
-     * be the parameter that was passed - it will depend on the receiver's
-     * ThreadGroup. The priority cannot be set to be higher than the receiver's
-     * ThreadGroup's maxPriority().
+     * Sets the priority of this thread. If the requested priority is greater than the
+     * parent thread group's {@link java.lang.ThreadGroup#getMaxPriority}, the group's maximum
+     * priority will be used instead.
      *
-     * @param priority
-     *            new priority for the Thread
-     * @throws IllegalArgumentException
-     *             if the new priority is greater than Thread.MAX_PRIORITY or
-     *             less than Thread.MIN_PRIORITY
-     * @see Thread#getPriority
+     * @throws IllegalArgumentException - if the new priority is greater than {@link #MAX_PRIORITY}
+     *     or less than {@link #MIN_PRIORITY}
      */
     public final void setPriority(int priority) {
         if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
-            throw new IllegalArgumentException("Priority out of range"); // TODO Externalize?
+            throw new IllegalArgumentException("Priority out of range: " + priority);
         }
 
         if (priority > group.getMaxPriority()) {
@@ -1036,14 +1018,11 @@
      * the receiver will be called by the receiver Thread itself (and not the
      * Thread calling <code>start()</code>).
      *
-     * @throws IllegalThreadStateException if the Thread has been started before
-     *
+     * @throws IllegalThreadStateException - if this thread has already started.
      * @see Thread#run
      */
     public synchronized void start() {
-        if (hasBeenStarted) {
-            throw new IllegalThreadStateException("Thread already started."); // TODO Externalize?
-        }
+        checkNotStarted();
 
         hasBeenStarted = true;
 
@@ -1065,9 +1044,6 @@
 
     /**
      * Throws {@code UnsupportedOperationException}.
-     *
-     * @throws NullPointerException if <code>throwable()</code> is
-     *         <code>null</code>
      * @deprecated because stopping a thread in this manner is unsafe and can
      * leave your application and the VM in an unpredictable state.
      */
@@ -1078,8 +1054,6 @@
 
     /**
      * Throws {@code UnsupportedOperationException}.
-     *
-     * @see Thread#resume()
      * @deprecated May cause deadlocks.
      */
     @Deprecated
diff --git a/luni/src/main/java/java/lang/ref/Reference.java b/luni/src/main/java/java/lang/ref/Reference.java
index 9cf49a7..bd63535 100644
--- a/luni/src/main/java/java/lang/ref/Reference.java
+++ b/luni/src/main/java/java/lang/ref/Reference.java
@@ -39,6 +39,61 @@
  * also not desirable to do so, since references require very close cooperation
  * with the system's garbage collector. The existing, specialized reference
  * classes should be used instead.
+ *
+ * <p>Three different type of references exist, each being weaker than the preceding one:
+ * {@link java.lang.ref.SoftReference}, {@link java.lang.ref.WeakReference}, and
+ * {@link java.lang.ref.PhantomReference}. "Weakness" here means that less restrictions are
+ * being imposed on the garbage collector as to when it is allowed to
+ * actually garbage-collect the referenced object.
+ *
+ * <p>In order to use reference objects properly it is important to understand
+ * the different types of reachability that trigger their clearing and
+ * enqueueing. The following table lists these, from strongest to weakest.
+ * For each row, an object is said to have the reachability on the left side
+ * if (and only if) it fulfills all of the requirements on the right side. In
+ * all rows, consider the <em>root set</em> to be a set of references that
+ * are "resistant" to garbage collection (that is, running threads, method
+ * parameters, local variables, static fields and the like).
+ *
+ * <p><table>
+ * <tr>
+ * <td>Strongly reachable</td>
+ * <td> <ul>
+ * <li>There exists at least one path from the root set to the object that does not traverse any
+ * instance of a {@code java.lang.ref.Reference} subclass.
+ * </li>
+ * </ul> </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Softly reachable</td>
+ * <td> <ul>
+ * <li>The object is not strongly reachable.</li>
+ * <li>There exists at least one path from the root set to the object that does traverse
+ * a {@code java.lang.ref.SoftReference} instance, but no {@code java.lang.ref.WeakReference}
+ * or {@code java.lang.ref.PhantomReference} instances.</li>
+ * </ul> </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Weakly reachable</td>
+ * <td> <ul>
+ * <li>The object is neither strongly nor softly reachable.</li>
+ * <li>There exists at least one path from the root set to the object that does traverse a
+ * {@code java.lang.ref.WeakReference} instance, but no {@code java.lang.ref.PhantomReference}
+ * instances.</li>
+ * </ul> </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Phantom-reachable</td>
+ * <td> <ul>
+ * <li>The object is neither strongly, softly, nor weakly reachable.</li>
+ * <li>The object is referenced by a {@code java.lang.ref.PhantomReference} instance.</li>
+ * <li>The object has already been finalized.</li>
+ * </ul> </td>
+ * </tr>
+ * </table>
  */
 public abstract class Reference<T> {
 
diff --git a/luni/src/main/java/java/lang/ref/package.html b/luni/src/main/java/java/lang/ref/package.html
deleted file mode 100644
index d3d8e8b..0000000
--- a/luni/src/main/java/java/lang/ref/package.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<html>
-  <body>
-    <p>
-      Provides the system's {@link java.lang.ref.ReferenceQueue} implementation as well as
-      different forms of reference objects which impose special behavior on the
-      garbage collector. The behavior depends on the type of {@link java.lang.ref.Reference}
-      being used. Three different type of references exist, each being weaker
-      than the preceding one: {@link java.lang.ref.SoftReference}, {@link java.lang.ref.WeakReference}, and
-      {@link java.lang.ref.PhantomReference}. "Weakness" here means that less restrictions are
-      being imposed on the garbage collector as to when it is allowed to
-      actually garbage-collect the referenced object.
-    </p>
-    <p>
-      In order to use reference objects properly it is important to understand
-      the different types of reachability that trigger their clearing and
-      enqueueing. The following table lists these, from strongest to weakest.
-      For each row, an object is said to have the reachability on the left side
-      if (and only if) it fulfills all of the requirements on the right side. In
-      all rows, consider the <em>root set</em> to be a set of references that
-      are "resistant" to garbage collection (that is, running threads, method
-      parameters, local variables, static fields and the like).  
-    </p>
-    <p>
-      <a name="definitions"></a> 
-      <table>
-        <tr>
-          <td>
-            Strongly reachable
-          </td>
-          <td>
-            <ul>
-              <li>
-                There exists at least one path from the root set to the object
-                that does not traverse any instance of a {@link java.lang.ref.Reference}
-                subclass.
-              </li>
-            </ul>
-          </td>
-        </tr>
-
-        <tr>
-          <td>
-            Softly reachable
-          </td>
-          <td>
-            <ul>
-              <li>
-                The object is not strongly reachable.
-              </li>
-              <li>
-                There exists at least one path from the root set to the object
-                that does traverse a {@link java.lang.ref.SoftReference} instance, but no
-                {@link java.lang.ref.WeakReference} or {@link java.lang.ref.PhantomReference} instances.
-              </li>
-            </ul>
-          </td>
-        </tr>
-
-        <tr>
-          <td>
-            Weakly reachable
-          </td>
-          <td>
-            <ul>
-              <li>
-                The object is neither strongly nor softly reachable.
-              </li>
-              <li>
-                There exists at least one path from the root set to the object
-                that does traverse a {@link java.lang.ref.WeakReference} instance, but no
-                {@link java.lang.ref.PhantomReference} instances.
-              </li>
-            </ul>
-          </td>
-        </tr>
-       
-        <tr>
-          <td>
-            Phantom-reachable
-          </td>
-          <td>
-            <ul>
-              <li>
-                The object is neither strongly, softly nor weakly reachable.
-              </li>
-              <li>
-                The object is referenced by a {@link java.lang.ref.PhantomReference} instance.
-              </li>
-              <li>
-                The object has already been finalized.
-              </li>
-            </ul>
-          </td>
-        </tr>
-      </table>
-    </p>
-  </body>
-</html>
diff --git a/luni/src/main/java/java/lang/reflect/Method.java b/luni/src/main/java/java/lang/reflect/Method.java
index 044dbff..b2f970a 100644
--- a/luni/src/main/java/java/lang/reflect/Method.java
+++ b/luni/src/main/java/java/lang/reflect/Method.java
@@ -33,6 +33,7 @@
 package java.lang.reflect;
 
 import java.lang.annotation.Annotation;
+import java.util.Arrays;
 import java.util.Comparator;
 import libcore.util.EmptyArray;
 import org.apache.harmony.kernel.vm.StringUtils;
@@ -379,7 +380,20 @@
      */
     @Override
     public boolean equals(Object object) {
-        return object instanceof Method && toString().equals(object.toString());
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof Method)) {
+            return false;
+        }
+        Method rhs = (Method) object;
+        // We don't compare exceptionTypes because two methods
+        // can't differ only by their declared exceptions.
+        return declaringClass.equals(rhs.declaringClass) &&
+            name.equals(rhs.name) &&
+            getModifiers() == rhs.getModifiers() &&
+            returnType.equals(rhs.returnType) &&
+            Arrays.equals(parameterTypes, rhs.parameterTypes);
     }
 
     /**
diff --git a/luni/src/main/java/java/math/BigDecimal.java b/luni/src/main/java/java/math/BigDecimal.java
index 426a9d2..013809d 100644
--- a/luni/src/main/java/java/math/BigDecimal.java
+++ b/luni/src/main/java/java/math/BigDecimal.java
@@ -346,10 +346,6 @@
         } else {
             setUnscaledValue(new BigInteger(unscaledBuffer.toString()));
         }
-        precision = unscaledBuffer.length() - counter;
-        if (unscaledBuffer.charAt(0) == '-') {
-            precision --;
-        }
     }
 
     /**
@@ -1783,13 +1779,11 @@
      * @return the precision of this {@code BigDecimal}.
      */
     public int precision() {
-        // Checking if the precision already was calculated
-        if (precision > 0) {
+        // Return the cached value if we have one.
+        if (precision != 0) {
             return precision;
         }
 
-        int bitLength = this.bitLength;
-
         if (bitLength == 0) {
             precision = 1;
         } else if (bitLength < 64) {
diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java
index c01f3af..790ebc9 100644
--- a/luni/src/main/java/java/net/DatagramSocket.java
+++ b/luni/src/main/java/java/net/DatagramSocket.java
@@ -614,7 +614,7 @@
     public void setTrafficClass(int value) throws SocketException {
         checkOpen();
         if (value < 0 || value > 255) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Value doesn't fit in an unsigned byte: " + value);
         }
         impl.setOption(SocketOptions.IP_TOS, Integer.valueOf(value));
     }
diff --git a/luni/src/main/java/java/net/HttpCookie.java b/luni/src/main/java/java/net/HttpCookie.java
index 057afef..ce1a8d2 100644
--- a/luni/src/main/java/java/net/HttpCookie.java
+++ b/luni/src/main/java/java/net/HttpCookie.java
@@ -445,7 +445,7 @@
     public HttpCookie(String name, String value) {
         String ntrim = name.trim(); // erase leading and trailing whitespace
         if (!isValidName(ntrim)) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Invalid name: " + name);
         }
 
         this.name = ntrim;
@@ -650,11 +650,11 @@
      *
      * @throws IllegalArgumentException if v is neither 0 nor 1
      */
-    public void setVersion(int v) {
-        if (v != 0 && v != 1) {
-            throw new IllegalArgumentException();
+    public void setVersion(int newVersion) {
+        if (newVersion != 0 && newVersion != 1) {
+            throw new IllegalArgumentException("Bad version: " + newVersion);
         }
-        version = v;
+        version = newVersion;
     }
 
     @Override public Object clone() {
diff --git a/luni/src/main/java/java/net/InetSocketAddress.java b/luni/src/main/java/java/net/InetSocketAddress.java
index 49dcfc4..4f4a348 100644
--- a/luni/src/main/java/java/net/InetSocketAddress.java
+++ b/luni/src/main/java/java/net/InetSocketAddress.java
@@ -131,18 +131,14 @@
     }
 
     /**
-     * Gets the port number of this socket.
-     *
-     * @return the socket endpoint port number.
+     * Returns this socket address' port.
      */
     public final int getPort() {
         return port;
     }
 
     /**
-     * Gets the address of this socket.
-     *
-     * @return the socket endpoint address.
+     * Returns this socket address' address.
      */
     public final InetAddress getAddress() {
         return addr;
diff --git a/luni/src/main/java/java/net/InetUnixAddress.java b/luni/src/main/java/java/net/InetUnixAddress.java
new file mode 100644
index 0000000..95bb097
--- /dev/null
+++ b/luni/src/main/java/java/net/InetUnixAddress.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.net;
+
+import java.nio.charset.Charsets;
+
+import static libcore.io.OsConstants.*;
+
+/**
+ * An AF_UNIX address. See {@link InetAddress}.
+ * @hide
+ */
+public final class InetUnixAddress extends InetAddress {
+  /**
+   * Constructs an AF_UNIX InetAddress for the given path.
+   */
+  public InetUnixAddress(String path) {
+    this(path.getBytes(Charsets.UTF_8));
+  }
+
+  /**
+   * Constructs an AF_UNIX InetAddress for the given path.
+   */
+  public InetUnixAddress(byte[] path) {
+    super(AF_UNIX, path, null);
+  }
+
+  /**
+   * Returns a string form of this InetAddress.
+   */
+  @Override public String toString() {
+    return "InetUnixAddress[" + new String(ipaddress, Charsets.UTF_8) + "]";
+  }
+}
diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java
index 20dfcc2..e247ac3 100644
--- a/luni/src/main/java/java/net/PlainSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainSocketImpl.java
@@ -104,7 +104,7 @@
             newImpl.address = peerAddress.getAddress();
             newImpl.port = peerAddress.getPort();
         } catch (ErrnoException errnoException) {
-            if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+            if (errnoException.errno == EAGAIN) {
                 throw new SocketTimeoutException(errnoException);
             }
             throw errnoException.rethrowAsSocketException();
diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java
index 7661f28..2d80586 100644
--- a/luni/src/main/java/java/net/Socket.java
+++ b/luni/src/main/java/java/net/Socket.java
@@ -482,7 +482,7 @@
     }
 
     /**
-     * Sets this socket's {@link SocketOptions#SO_SNDBUF receive buffer size}.
+     * Sets this socket's {@link SocketOptions#SO_RCVBUF receive buffer size}.
      */
     public synchronized void setReceiveBufferSize(int size) throws SocketException {
         checkOpenAndCreate(true);
@@ -909,7 +909,7 @@
     public void setTrafficClass(int value) throws SocketException {
         checkOpenAndCreate(true);
         if (value < 0 || value > 255) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Value doesn't fit in an unsigned byte: " + value);
         }
         impl.setOption(SocketOptions.IP_TOS, Integer.valueOf(value));
     }
diff --git a/luni/src/main/java/java/net/URISyntaxException.java b/luni/src/main/java/java/net/URISyntaxException.java
index 957ea31..70fe6a1 100644
--- a/luni/src/main/java/java/net/URISyntaxException.java
+++ b/luni/src/main/java/java/net/URISyntaxException.java
@@ -56,7 +56,7 @@
         }
 
         if (index < -1) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad index: " + index);
         }
 
         this.input = input;
diff --git a/luni/src/main/java/java/net/URLDecoder.java b/luni/src/main/java/java/net/URLDecoder.java
index 175175d..f21de7d 100644
--- a/luni/src/main/java/java/net/URLDecoder.java
+++ b/luni/src/main/java/java/net/URLDecoder.java
@@ -47,23 +47,16 @@
 
     /**
      * Decodes the argument which is assumed to be encoded in the {@code
-     * x-www-form-urlencoded} MIME content type using the specified encoding
-     * scheme.
-     * <p>
-     *'+' will be converted to space, '%' and two following hex digit
+     * x-www-form-urlencoded} MIME content type, assuming the given {@code charsetName}.
+     *
+     *'<p>+' will be converted to space, '%' and two following hex digit
      * characters are converted to the equivalent byte value. All other
      * characters are passed through unmodified. For example "A+B+C %24%25" ->
      * "A B C $%".
      *
-     * @param s
-     *            the encoded string.
-     * @param encoding
-     *            the encoding scheme to be used.
-     * @return the decoded clear-text representation of the given string.
-     * @throws UnsupportedEncodingException
-     *             if the specified encoding scheme is invalid.
+     * @throws UnsupportedEncodingException if {@code charsetName} is not supported.
      */
-    public static String decode(String s, String encoding) throws UnsupportedEncodingException {
-        return UriCodec.decode(s, true, Charset.forName(encoding), true);
+    public static String decode(String s, String charsetName) throws UnsupportedEncodingException {
+        return UriCodec.decode(s, true, Charset.forName(charsetName), true);
     }
 }
diff --git a/luni/src/main/java/java/nio/BaseByteBuffer.java b/luni/src/main/java/java/nio/BaseByteBuffer.java
deleted file mode 100644
index d6cfb93..0000000
--- a/luni/src/main/java/java/nio/BaseByteBuffer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.nio;
-
-/**
- * Serves as the root of other byte buffer impl classes, implements common
- * methods that can be shared by child classes.
- */
-abstract class BaseByteBuffer extends ByteBuffer {
-
-    protected BaseByteBuffer(int capacity, MemoryBlock block) {
-        super(capacity, block);
-    }
-
-    @Override
-    public final CharBuffer asCharBuffer() {
-        return CharToByteBufferAdapter.asCharBuffer(this);
-    }
-
-    @Override
-    public final DoubleBuffer asDoubleBuffer() {
-        return DoubleToByteBufferAdapter.asDoubleBuffer(this);
-    }
-
-    @Override
-    public final FloatBuffer asFloatBuffer() {
-        return FloatToByteBufferAdapter.asFloatBuffer(this);
-    }
-
-    @Override
-    public final IntBuffer asIntBuffer() {
-        return IntToByteBufferAdapter.asIntBuffer(this);
-    }
-
-    @Override
-    public final LongBuffer asLongBuffer() {
-        return LongToByteBufferAdapter.asLongBuffer(this);
-    }
-
-    @Override
-    public final ShortBuffer asShortBuffer() {
-        return ShortToByteBufferAdapter.asShortBuffer(this);
-    }
-
-    @Override
-    public char getChar() {
-        return (char) getShort();
-    }
-
-    @Override
-    public char getChar(int index) {
-        return (char) getShort(index);
-    }
-
-    @Override
-    public ByteBuffer putChar(char value) {
-        return putShort((short) value);
-    }
-
-    @Override
-    public ByteBuffer putChar(int index, char value) {
-        return putShort(index, (short) value);
-    }
-}
diff --git a/luni/src/main/java/java/nio/Buffer.java b/luni/src/main/java/java/nio/Buffer.java
index b90744b..9b7be52 100644
--- a/luni/src/main/java/java/nio/Buffer.java
+++ b/luni/src/main/java/java/nio/Buffer.java
@@ -87,7 +87,7 @@
      * This is set in the constructor.
      * TODO: make this final at the cost of loads of extra constructors? [how many?]
      */
-    int effectiveDirectAddress;
+    long effectiveDirectAddress;
 
     /**
      * For direct buffers, the underlying MemoryBlock; null otherwise.
@@ -300,14 +300,6 @@
      *                if <code>newLimit</code> is invalid.
      */
     public final Buffer limit(int newLimit) {
-        limitImpl(newLimit);
-        return this;
-    }
-
-    /**
-     * Subverts the fact that limit(int) is final, for the benefit of MappedByteBufferAdapter.
-     */
-    void limitImpl(int newLimit) {
         if (newLimit < 0 || newLimit > capacity) {
             throw new IllegalArgumentException("Bad limit (capacity " + capacity + "): " + newLimit);
         }
@@ -319,6 +311,7 @@
         if ((mark != UNSET_MARK) && (mark > newLimit)) {
             mark = UNSET_MARK;
         }
+        return this;
     }
 
     /**
@@ -409,15 +402,11 @@
         return this;
     }
 
+    /**
+     * Returns a string describing this buffer.
+     */
     @Override public String toString() {
-        StringBuilder buf = new StringBuilder();
-        buf.append(getClass().getName());
-        buf.append(", status: capacity=");
-        buf.append(capacity);
-        buf.append(" position=");
-        buf.append(position);
-        buf.append(" limit=");
-        buf.append(limit);
-        return buf.toString();
+        return getClass().getName() +
+            "[position=" + position + ",limit=" + limit + ",capacity=" + capacity + "]";
     }
 }
diff --git a/luni/src/main/java/java/nio/ByteArrayBuffer.java b/luni/src/main/java/java/nio/ByteArrayBuffer.java
new file mode 100644
index 0000000..e8d7ecc
--- /dev/null
+++ b/luni/src/main/java/java/nio/ByteArrayBuffer.java
@@ -0,0 +1,435 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package java.nio;
+
+import libcore.io.SizeOf;
+import libcore.io.Memory;
+
+/**
+ * ByteArrayBuffer implements byte[]-backed ByteBuffers.
+ */
+final class ByteArrayBuffer extends ByteBuffer {
+
+  /**
+   * These fields are non-private for NioUtils.unsafeArray.
+   */
+  final byte[] backingArray;
+  final int arrayOffset;
+
+  private final boolean isReadOnly;
+
+  ByteArrayBuffer(byte[] backingArray) {
+    this(backingArray.length, backingArray, 0, false);
+  }
+
+  private ByteArrayBuffer(int capacity, byte[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity, null);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+    if (arrayOffset + capacity > backingArray.length) {
+      throw new IndexOutOfBoundsException("backingArray.length=" + backingArray.length +
+                                              ", capacity=" + capacity + ", arrayOffset=" + arrayOffset);
+    }
+  }
+
+  private static ByteArrayBuffer copy(ByteArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    ByteArrayBuffer buf = new ByteArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public ByteBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+  @Override public ByteBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
+
+  @Override public ByteBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
+
+  @Override public ByteBuffer slice() {
+    return new ByteArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
+
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  @Override byte[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    return backingArray;
+  }
+
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    return arrayOffset;
+  }
+
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override public final ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
+    checkGetBounds(1, dst.length, dstOffset, byteCount);
+    System.arraycopy(backingArray, arrayOffset + position, dst, dstOffset, byteCount);
+    position += byteCount;
+    return this;
+  }
+
+  final void get(char[] dst, int dstOffset, int charCount) {
+    int byteCount = checkGetBounds(SizeOf.CHAR, dst.length, dstOffset, charCount);
+    Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, arrayOffset + position, SizeOf.CHAR, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void get(double[] dst, int dstOffset, int doubleCount) {
+    int byteCount = checkGetBounds(SizeOf.DOUBLE, dst.length, dstOffset, doubleCount);
+    Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, arrayOffset + position, SizeOf.DOUBLE, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void get(float[] dst, int dstOffset, int floatCount) {
+    int byteCount = checkGetBounds(SizeOf.FLOAT, dst.length, dstOffset, floatCount);
+    Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, arrayOffset + position, SizeOf.FLOAT, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void get(int[] dst, int dstOffset, int intCount) {
+    int byteCount = checkGetBounds(SizeOf.INT, dst.length, dstOffset, intCount);
+    Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, arrayOffset + position, SizeOf.INT, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void get(long[] dst, int dstOffset, int longCount) {
+    int byteCount = checkGetBounds(SizeOf.LONG, dst.length, dstOffset, longCount);
+    Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, arrayOffset + position, SizeOf.LONG, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void get(short[] dst, int dstOffset, int shortCount) {
+    int byteCount = checkGetBounds(SizeOf.SHORT, dst.length, dstOffset, shortCount);
+    Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, arrayOffset + position, SizeOf.SHORT, order.needsSwap);
+    position += byteCount;
+  }
+
+  @Override public final byte get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
+    }
+    return backingArray[arrayOffset + position++];
+  }
+
+  @Override public final byte get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
+
+  @Override public final char getChar() {
+    int newPosition = position + SizeOf.CHAR;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    char result = (char) Memory.peekShort(backingArray, arrayOffset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final char getChar(int index) {
+    checkIndex(index, SizeOf.CHAR);
+    return (char) Memory.peekShort(backingArray, arrayOffset + index, order);
+  }
+
+  @Override public final double getDouble() {
+    return Double.longBitsToDouble(getLong());
+  }
+
+  @Override public final double getDouble(int index) {
+    return Double.longBitsToDouble(getLong(index));
+  }
+
+  @Override public final float getFloat() {
+    return Float.intBitsToFloat(getInt());
+  }
+
+  @Override public final float getFloat(int index) {
+    return Float.intBitsToFloat(getInt(index));
+  }
+
+  @Override public final int getInt() {
+    int newPosition = position + SizeOf.INT;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    int result = Memory.peekInt(backingArray, arrayOffset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final int getInt(int index) {
+    checkIndex(index, SizeOf.INT);
+    return Memory.peekInt(backingArray, arrayOffset + index, order);
+  }
+
+  @Override public final long getLong() {
+    int newPosition = position + SizeOf.LONG;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    long result = Memory.peekLong(backingArray, arrayOffset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final long getLong(int index) {
+    checkIndex(index, SizeOf.LONG);
+    return Memory.peekLong(backingArray, arrayOffset + index, order);
+  }
+
+  @Override public final short getShort() {
+    int newPosition = position + SizeOf.SHORT;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    short result = Memory.peekShort(backingArray, arrayOffset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final short getShort(int index) {
+    checkIndex(index, SizeOf.SHORT);
+    return Memory.peekShort(backingArray, arrayOffset + index, order);
+  }
+
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public ByteBuffer put(byte b) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (position == limit) {
+      throw new BufferOverflowException();
+    }
+    backingArray[arrayOffset + position++] = b;
+    return this;
+  }
+
+  @Override public ByteBuffer put(int index, byte b) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = b;
+    return this;
+  }
+
+  @Override public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkPutBounds(1, src.length, srcOffset, byteCount);
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, byteCount);
+    position += byteCount;
+    return this;
+  }
+
+  final void put(char[] src, int srcOffset, int charCount) {
+    int byteCount = checkPutBounds(SizeOf.CHAR, src.length, srcOffset, charCount);
+    Memory.unsafeBulkPut(backingArray, arrayOffset + position, byteCount, src, srcOffset, SizeOf.CHAR, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(double[] src, int srcOffset, int doubleCount) {
+    int byteCount = checkPutBounds(SizeOf.DOUBLE, src.length, srcOffset, doubleCount);
+    Memory.unsafeBulkPut(backingArray, arrayOffset + position, byteCount, src, srcOffset, SizeOf.DOUBLE, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(float[] src, int srcOffset, int floatCount) {
+    int byteCount = checkPutBounds(SizeOf.FLOAT, src.length, srcOffset, floatCount);
+    Memory.unsafeBulkPut(backingArray, arrayOffset + position, byteCount, src, srcOffset, SizeOf.FLOAT, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(int[] src, int srcOffset, int intCount) {
+    int byteCount = checkPutBounds(SizeOf.INT, src.length, srcOffset, intCount);
+    Memory.unsafeBulkPut(backingArray, arrayOffset + position, byteCount, src, srcOffset, SizeOf.INT, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(long[] src, int srcOffset, int longCount) {
+    int byteCount = checkPutBounds(SizeOf.LONG, src.length, srcOffset, longCount);
+    Memory.unsafeBulkPut(backingArray, arrayOffset + position, byteCount, src, srcOffset, SizeOf.LONG, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(short[] src, int srcOffset, int shortCount) {
+    int byteCount = checkPutBounds(SizeOf.SHORT, src.length, srcOffset, shortCount);
+    Memory.unsafeBulkPut(backingArray, arrayOffset + position, byteCount, src, srcOffset, SizeOf.SHORT, order.needsSwap);
+    position += byteCount;
+  }
+
+  @Override public ByteBuffer putChar(int index, char value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.CHAR);
+    Memory.pokeShort(backingArray, arrayOffset + index, (short) value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putChar(char value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.CHAR;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    Memory.pokeShort(backingArray, arrayOffset + position, (short) value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putDouble(double value) {
+    return putLong(Double.doubleToRawLongBits(value));
+  }
+
+  @Override public ByteBuffer putDouble(int index, double value) {
+    return putLong(index, Double.doubleToRawLongBits(value));
+  }
+
+  @Override public ByteBuffer putFloat(float value) {
+    return putInt(Float.floatToRawIntBits(value));
+  }
+
+  @Override public ByteBuffer putFloat(int index, float value) {
+    return putInt(index, Float.floatToRawIntBits(value));
+  }
+
+  @Override public ByteBuffer putInt(int value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.INT;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    Memory.pokeInt(backingArray, arrayOffset + position, value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putInt(int index, int value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.INT);
+    Memory.pokeInt(backingArray, arrayOffset + index, value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putLong(int index, long value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.LONG);
+    Memory.pokeLong(backingArray, arrayOffset + index, value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putLong(long value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.LONG;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    Memory.pokeLong(backingArray, arrayOffset + position, value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putShort(int index, short value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.SHORT);
+    Memory.pokeShort(backingArray, arrayOffset + index, value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putShort(short value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.SHORT;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    Memory.pokeShort(backingArray, arrayOffset + position, value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public final CharBuffer asCharBuffer() {
+    return ByteBufferAsCharBuffer.asCharBuffer(this);
+  }
+
+  @Override public final DoubleBuffer asDoubleBuffer() {
+    return ByteBufferAsDoubleBuffer.asDoubleBuffer(this);
+  }
+
+  @Override public final FloatBuffer asFloatBuffer() {
+    return ByteBufferAsFloatBuffer.asFloatBuffer(this);
+  }
+
+  @Override public final IntBuffer asIntBuffer() {
+    return ByteBufferAsIntBuffer.asIntBuffer(this);
+  }
+
+  @Override public final LongBuffer asLongBuffer() {
+    return ByteBufferAsLongBuffer.asLongBuffer(this);
+  }
+
+  @Override public final ShortBuffer asShortBuffer() {
+    return ByteBufferAsShortBuffer.asShortBuffer(this);
+  }
+}
diff --git a/luni/src/main/java/java/nio/ByteBuffer.java b/luni/src/main/java/java/nio/ByteBuffer.java
index 6a3624a..cd39a53 100644
--- a/luni/src/main/java/java/nio/ByteBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBuffer.java
@@ -35,6 +35,10 @@
  *
  */
 public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
+    /**
+     * The byte order of this buffer, default is {@code BIG_ENDIAN}.
+     */
+    ByteOrder order = ByteOrder.BIG_ENDIAN;
 
     /**
      * Creates a byte buffer based on a newly allocated byte array.
@@ -49,7 +53,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteHeapByteBuffer(capacity);
+        return new ByteArrayBuffer(new byte[capacity]);
     }
 
     /**
@@ -65,7 +69,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteDirectByteBuffer(capacity);
+        return new DirectByteBuffer(MemoryBlock.allocate(capacity), capacity, 0, false, null);
     }
 
     /**
@@ -79,7 +83,7 @@
      * @return the created byte buffer.
      */
     public static ByteBuffer wrap(byte[] array) {
-        return new ReadWriteHeapByteBuffer(array);
+        return new ByteArrayBuffer(array);
     }
 
     /**
@@ -102,17 +106,12 @@
      */
     public static ByteBuffer wrap(byte[] array, int start, int byteCount) {
         Arrays.checkOffsetAndCount(array.length, start, byteCount);
-        ByteBuffer buf = new ReadWriteHeapByteBuffer(array);
+        ByteBuffer buf = new ByteArrayBuffer(array);
         buf.position = start;
         buf.limit = start + byteCount;
         return buf;
     }
 
-    /**
-     * The byte order of this buffer, default is {@code BIG_ENDIAN}.
-     */
-    ByteOrder order = ByteOrder.BIG_ENDIAN;
-
     ByteBuffer(int capacity, MemoryBlock block) {
         super(0, capacity, block);
     }
@@ -644,18 +643,11 @@
      * @see ByteOrder
      */
     public final ByteBuffer order(ByteOrder byteOrder) {
-        orderImpl(byteOrder);
-        return this;
-    }
-
-    /**
-     * Subverts the fact that order(ByteOrder) is final, for the benefit of MappedByteBufferAdapter.
-     */
-    void orderImpl(ByteOrder byteOrder) {
         if (byteOrder == null) {
             byteOrder = ByteOrder.LITTLE_ENDIAN;
         }
         order = byteOrder;
+        return this;
     }
 
     /**
@@ -761,6 +753,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public ByteBuffer put(ByteBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
diff --git a/luni/src/main/java/java/nio/CharToByteBufferAdapter.java b/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
similarity index 87%
rename from luni/src/main/java/java/nio/CharToByteBufferAdapter.java
rename to luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
index b9100a2..ef5c52d 100644
--- a/luni/src/main/java/java/nio/CharToByteBufferAdapter.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java
@@ -31,17 +31,17 @@
  * </p>
  *
  */
-final class CharToByteBufferAdapter extends CharBuffer {
+final class ByteBufferAsCharBuffer extends CharBuffer {
 
     private final ByteBuffer byteBuffer;
 
     static CharBuffer asCharBuffer(ByteBuffer byteBuffer) {
         ByteBuffer slice = byteBuffer.slice();
         slice.order(byteBuffer.order());
-        return new CharToByteBufferAdapter(slice);
+        return new ByteBufferAsCharBuffer(slice);
     }
 
-    private CharToByteBufferAdapter(ByteBuffer byteBuffer) {
+    private ByteBufferAsCharBuffer(ByteBuffer byteBuffer) {
         super(byteBuffer.capacity() / SizeOf.CHAR);
         this.byteBuffer = byteBuffer;
         this.byteBuffer.clear();
@@ -50,7 +50,7 @@
 
     @Override
     public CharBuffer asReadOnlyBuffer() {
-        CharToByteBufferAdapter buf = new CharToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
+        ByteBufferAsCharBuffer buf = new ByteBufferAsCharBuffer(byteBuffer.asReadOnlyBuffer());
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -76,7 +76,7 @@
     @Override
     public CharBuffer duplicate() {
         ByteBuffer bb = byteBuffer.duplicate().order(byteBuffer.order());
-        CharToByteBufferAdapter buf = new CharToByteBufferAdapter(bb);
+        ByteBufferAsCharBuffer buf = new ByteBufferAsCharBuffer(bb);
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -104,7 +104,7 @@
         if (byteBuffer instanceof DirectByteBuffer) {
             ((DirectByteBuffer) byteBuffer).get(dst, dstOffset, charCount);
         } else {
-            ((HeapByteBuffer) byteBuffer).get(dst, dstOffset, charCount);
+            ((ByteArrayBuffer) byteBuffer).get(dst, dstOffset, charCount);
         }
         this.position += charCount;
         return this;
@@ -157,10 +157,10 @@
     public CharBuffer put(char[] src, int srcOffset, int charCount) {
         byteBuffer.limit(limit * SizeOf.CHAR);
         byteBuffer.position(position * SizeOf.CHAR);
-        if (byteBuffer instanceof ReadWriteDirectByteBuffer) {
-            ((ReadWriteDirectByteBuffer) byteBuffer).put(src, srcOffset, charCount);
+        if (byteBuffer instanceof DirectByteBuffer) {
+            ((DirectByteBuffer) byteBuffer).put(src, srcOffset, charCount);
         } else {
-            ((ReadWriteHeapByteBuffer) byteBuffer).put(src, srcOffset, charCount);
+            ((ByteArrayBuffer) byteBuffer).put(src, srcOffset, charCount);
         }
         this.position += charCount;
         return this;
@@ -171,7 +171,7 @@
         byteBuffer.limit(limit * SizeOf.CHAR);
         byteBuffer.position(position * SizeOf.CHAR);
         ByteBuffer bb = byteBuffer.slice().order(byteBuffer.order());
-        CharBuffer result = new CharToByteBufferAdapter(bb);
+        CharBuffer result = new ByteBufferAsCharBuffer(bb);
         byteBuffer.clear();
         return result;
     }
diff --git a/luni/src/main/java/java/nio/DoubleToByteBufferAdapter.java b/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
similarity index 86%
rename from luni/src/main/java/java/nio/DoubleToByteBufferAdapter.java
rename to luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
index 8b1e084..044bf59 100644
--- a/luni/src/main/java/java/nio/DoubleToByteBufferAdapter.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java
@@ -31,17 +31,17 @@
  * </p>
  *
  */
-final class DoubleToByteBufferAdapter extends DoubleBuffer {
+final class ByteBufferAsDoubleBuffer extends DoubleBuffer {
 
     private final ByteBuffer byteBuffer;
 
     static DoubleBuffer asDoubleBuffer(ByteBuffer byteBuffer) {
         ByteBuffer slice = byteBuffer.slice();
         slice.order(byteBuffer.order());
-        return new DoubleToByteBufferAdapter(slice);
+        return new ByteBufferAsDoubleBuffer(slice);
     }
 
-    private DoubleToByteBufferAdapter(ByteBuffer byteBuffer) {
+    private ByteBufferAsDoubleBuffer(ByteBuffer byteBuffer) {
         super(byteBuffer.capacity() / SizeOf.DOUBLE);
         this.byteBuffer = byteBuffer;
         this.byteBuffer.clear();
@@ -50,7 +50,7 @@
 
     @Override
     public DoubleBuffer asReadOnlyBuffer() {
-        DoubleToByteBufferAdapter buf = new DoubleToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
+        ByteBufferAsDoubleBuffer buf = new ByteBufferAsDoubleBuffer(byteBuffer.asReadOnlyBuffer());
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -76,7 +76,7 @@
     @Override
     public DoubleBuffer duplicate() {
         ByteBuffer bb = byteBuffer.duplicate().order(byteBuffer.order());
-        DoubleToByteBufferAdapter buf = new DoubleToByteBufferAdapter(bb);
+        ByteBufferAsDoubleBuffer buf = new ByteBufferAsDoubleBuffer(bb);
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -104,7 +104,7 @@
         if (byteBuffer instanceof DirectByteBuffer) {
             ((DirectByteBuffer) byteBuffer).get(dst, dstOffset, doubleCount);
         } else {
-            ((HeapByteBuffer) byteBuffer).get(dst, dstOffset, doubleCount);
+            ((ByteArrayBuffer) byteBuffer).get(dst, dstOffset, doubleCount);
         }
         this.position += doubleCount;
         return this;
@@ -157,10 +157,10 @@
     public DoubleBuffer put(double[] src, int srcOffset, int doubleCount) {
         byteBuffer.limit(limit * SizeOf.DOUBLE);
         byteBuffer.position(position * SizeOf.DOUBLE);
-        if (byteBuffer instanceof ReadWriteDirectByteBuffer) {
-            ((ReadWriteDirectByteBuffer) byteBuffer).put(src, srcOffset, doubleCount);
+        if (byteBuffer instanceof DirectByteBuffer) {
+            ((DirectByteBuffer) byteBuffer).put(src, srcOffset, doubleCount);
         } else {
-            ((ReadWriteHeapByteBuffer) byteBuffer).put(src, srcOffset, doubleCount);
+            ((ByteArrayBuffer) byteBuffer).put(src, srcOffset, doubleCount);
         }
         this.position += doubleCount;
         return this;
@@ -171,7 +171,7 @@
         byteBuffer.limit(limit * SizeOf.DOUBLE);
         byteBuffer.position(position * SizeOf.DOUBLE);
         ByteBuffer bb = byteBuffer.slice().order(byteBuffer.order());
-        DoubleBuffer result = new DoubleToByteBufferAdapter(bb);
+        DoubleBuffer result = new ByteBufferAsDoubleBuffer(bb);
         byteBuffer.clear();
         return result;
     }
diff --git a/luni/src/main/java/java/nio/FloatToByteBufferAdapter.java b/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
similarity index 86%
rename from luni/src/main/java/java/nio/FloatToByteBufferAdapter.java
rename to luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
index 0ed944b..a67affe 100644
--- a/luni/src/main/java/java/nio/FloatToByteBufferAdapter.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java
@@ -30,17 +30,17 @@
  * </ul>
  * </p>
  */
-final class FloatToByteBufferAdapter extends FloatBuffer {
+final class ByteBufferAsFloatBuffer extends FloatBuffer {
 
     private final ByteBuffer byteBuffer;
 
     static FloatBuffer asFloatBuffer(ByteBuffer byteBuffer) {
         ByteBuffer slice = byteBuffer.slice();
         slice.order(byteBuffer.order());
-        return new FloatToByteBufferAdapter(slice);
+        return new ByteBufferAsFloatBuffer(slice);
     }
 
-    FloatToByteBufferAdapter(ByteBuffer byteBuffer) {
+    ByteBufferAsFloatBuffer(ByteBuffer byteBuffer) {
         super(byteBuffer.capacity() / SizeOf.FLOAT);
         this.byteBuffer = byteBuffer;
         this.byteBuffer.clear();
@@ -49,7 +49,7 @@
 
     @Override
     public FloatBuffer asReadOnlyBuffer() {
-        FloatToByteBufferAdapter buf = new FloatToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
+        ByteBufferAsFloatBuffer buf = new ByteBufferAsFloatBuffer(byteBuffer.asReadOnlyBuffer());
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -75,7 +75,7 @@
     @Override
     public FloatBuffer duplicate() {
         ByteBuffer bb = byteBuffer.duplicate().order(byteBuffer.order());
-        FloatToByteBufferAdapter buf = new FloatToByteBufferAdapter(bb);
+        ByteBufferAsFloatBuffer buf = new ByteBufferAsFloatBuffer(bb);
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -103,7 +103,7 @@
         if (byteBuffer instanceof DirectByteBuffer) {
             ((DirectByteBuffer) byteBuffer).get(dst, dstOffset, floatCount);
         } else {
-            ((HeapByteBuffer) byteBuffer).get(dst, dstOffset, floatCount);
+            ((ByteArrayBuffer) byteBuffer).get(dst, dstOffset, floatCount);
         }
         this.position += floatCount;
         return this;
@@ -156,10 +156,10 @@
     public FloatBuffer put(float[] src, int srcOffset, int floatCount) {
         byteBuffer.limit(limit * SizeOf.FLOAT);
         byteBuffer.position(position * SizeOf.FLOAT);
-        if (byteBuffer instanceof ReadWriteDirectByteBuffer) {
-            ((ReadWriteDirectByteBuffer) byteBuffer).put(src, srcOffset, floatCount);
+        if (byteBuffer instanceof DirectByteBuffer) {
+            ((DirectByteBuffer) byteBuffer).put(src, srcOffset, floatCount);
         } else {
-            ((ReadWriteHeapByteBuffer) byteBuffer).put(src, srcOffset, floatCount);
+            ((ByteArrayBuffer) byteBuffer).put(src, srcOffset, floatCount);
         }
         this.position += floatCount;
         return this;
@@ -170,7 +170,7 @@
         byteBuffer.limit(limit * SizeOf.FLOAT);
         byteBuffer.position(position * SizeOf.FLOAT);
         ByteBuffer bb = byteBuffer.slice().order(byteBuffer.order());
-        FloatBuffer result = new FloatToByteBufferAdapter(bb);
+        FloatBuffer result = new ByteBufferAsFloatBuffer(bb);
         byteBuffer.clear();
         return result;
     }
diff --git a/luni/src/main/java/java/nio/IntToByteBufferAdapter.java b/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
similarity index 87%
rename from luni/src/main/java/java/nio/IntToByteBufferAdapter.java
rename to luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
index 1af5f86..a3211b8 100644
--- a/luni/src/main/java/java/nio/IntToByteBufferAdapter.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java
@@ -31,17 +31,17 @@
  * </p>
  *
  */
-final class IntToByteBufferAdapter extends IntBuffer {
+final class ByteBufferAsIntBuffer extends IntBuffer {
 
     private final ByteBuffer byteBuffer;
 
     static IntBuffer asIntBuffer(ByteBuffer byteBuffer) {
         ByteBuffer slice = byteBuffer.slice();
         slice.order(byteBuffer.order());
-        return new IntToByteBufferAdapter(slice);
+        return new ByteBufferAsIntBuffer(slice);
     }
 
-    private IntToByteBufferAdapter(ByteBuffer byteBuffer) {
+    private ByteBufferAsIntBuffer(ByteBuffer byteBuffer) {
         super(byteBuffer.capacity() / SizeOf.INT);
         this.byteBuffer = byteBuffer;
         this.byteBuffer.clear();
@@ -50,7 +50,7 @@
 
     @Override
     public IntBuffer asReadOnlyBuffer() {
-        IntToByteBufferAdapter buf = new IntToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
+        ByteBufferAsIntBuffer buf = new ByteBufferAsIntBuffer(byteBuffer.asReadOnlyBuffer());
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -76,7 +76,7 @@
     @Override
     public IntBuffer duplicate() {
         ByteBuffer bb = byteBuffer.duplicate().order(byteBuffer.order());
-        IntToByteBufferAdapter buf = new IntToByteBufferAdapter(bb);
+        ByteBufferAsIntBuffer buf = new ByteBufferAsIntBuffer(bb);
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -104,7 +104,7 @@
         if (byteBuffer instanceof DirectByteBuffer) {
             ((DirectByteBuffer) byteBuffer).get(dst, dstOffset, intCount);
         } else {
-            ((HeapByteBuffer) byteBuffer).get(dst, dstOffset, intCount);
+            ((ByteArrayBuffer) byteBuffer).get(dst, dstOffset, intCount);
         }
         this.position += intCount;
         return this;
@@ -157,10 +157,10 @@
     public IntBuffer put(int[] src, int srcOffset, int intCount) {
         byteBuffer.limit(limit * SizeOf.INT);
         byteBuffer.position(position * SizeOf.INT);
-        if (byteBuffer instanceof ReadWriteDirectByteBuffer) {
-            ((ReadWriteDirectByteBuffer) byteBuffer).put(src, srcOffset, intCount);
+        if (byteBuffer instanceof DirectByteBuffer) {
+            ((DirectByteBuffer) byteBuffer).put(src, srcOffset, intCount);
         } else {
-            ((ReadWriteHeapByteBuffer) byteBuffer).put(src, srcOffset, intCount);
+            ((ByteArrayBuffer) byteBuffer).put(src, srcOffset, intCount);
         }
         this.position += intCount;
         return this;
@@ -171,7 +171,7 @@
         byteBuffer.limit(limit * SizeOf.INT);
         byteBuffer.position(position * SizeOf.INT);
         ByteBuffer bb = byteBuffer.slice().order(byteBuffer.order());
-        IntBuffer result = new IntToByteBufferAdapter(bb);
+        IntBuffer result = new ByteBufferAsIntBuffer(bb);
         byteBuffer.clear();
         return result;
     }
diff --git a/luni/src/main/java/java/nio/LongToByteBufferAdapter.java b/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
similarity index 86%
rename from luni/src/main/java/java/nio/LongToByteBufferAdapter.java
rename to luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
index e8bf8df..550c675 100644
--- a/luni/src/main/java/java/nio/LongToByteBufferAdapter.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java
@@ -31,17 +31,17 @@
  * </p>
  *
  */
-final class LongToByteBufferAdapter extends LongBuffer {
+final class ByteBufferAsLongBuffer extends LongBuffer {
 
     private final ByteBuffer byteBuffer;
 
     static LongBuffer asLongBuffer(ByteBuffer byteBuffer) {
         ByteBuffer slice = byteBuffer.slice();
         slice.order(byteBuffer.order());
-        return new LongToByteBufferAdapter(slice);
+        return new ByteBufferAsLongBuffer(slice);
     }
 
-    private LongToByteBufferAdapter(ByteBuffer byteBuffer) {
+    private ByteBufferAsLongBuffer(ByteBuffer byteBuffer) {
         super(byteBuffer.capacity() / SizeOf.LONG);
         this.byteBuffer = byteBuffer;
         this.byteBuffer.clear();
@@ -50,7 +50,7 @@
 
     @Override
     public LongBuffer asReadOnlyBuffer() {
-        LongToByteBufferAdapter buf = new LongToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
+        ByteBufferAsLongBuffer buf = new ByteBufferAsLongBuffer(byteBuffer.asReadOnlyBuffer());
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -76,7 +76,7 @@
     @Override
     public LongBuffer duplicate() {
         ByteBuffer bb = byteBuffer.duplicate().order(byteBuffer.order());
-        LongToByteBufferAdapter buf = new LongToByteBufferAdapter(bb);
+        ByteBufferAsLongBuffer buf = new ByteBufferAsLongBuffer(bb);
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -104,7 +104,7 @@
         if (byteBuffer instanceof DirectByteBuffer) {
             ((DirectByteBuffer) byteBuffer).get(dst, dstOffset, longCount);
         } else {
-            ((HeapByteBuffer) byteBuffer).get(dst, dstOffset, longCount);
+            ((ByteArrayBuffer) byteBuffer).get(dst, dstOffset, longCount);
         }
         this.position += longCount;
         return this;
@@ -157,10 +157,10 @@
     public LongBuffer put(long[] src, int srcOffset, int longCount) {
         byteBuffer.limit(limit * SizeOf.LONG);
         byteBuffer.position(position * SizeOf.LONG);
-        if (byteBuffer instanceof ReadWriteDirectByteBuffer) {
-            ((ReadWriteDirectByteBuffer) byteBuffer).put(src, srcOffset, longCount);
+        if (byteBuffer instanceof DirectByteBuffer) {
+            ((DirectByteBuffer) byteBuffer).put(src, srcOffset, longCount);
         } else {
-            ((ReadWriteHeapByteBuffer) byteBuffer).put(src, srcOffset, longCount);
+            ((ByteArrayBuffer) byteBuffer).put(src, srcOffset, longCount);
         }
         this.position += longCount;
         return this;
@@ -171,7 +171,7 @@
         byteBuffer.limit(limit * SizeOf.LONG);
         byteBuffer.position(position * SizeOf.LONG);
         ByteBuffer bb = byteBuffer.slice().order(byteBuffer.order());
-        LongBuffer result = new LongToByteBufferAdapter(bb);
+        LongBuffer result = new ByteBufferAsLongBuffer(bb);
         byteBuffer.clear();
         return result;
     }
diff --git a/luni/src/main/java/java/nio/ShortToByteBufferAdapter.java b/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
similarity index 86%
rename from luni/src/main/java/java/nio/ShortToByteBufferAdapter.java
rename to luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
index ee36709..ff81409 100644
--- a/luni/src/main/java/java/nio/ShortToByteBufferAdapter.java
+++ b/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java
@@ -30,17 +30,17 @@
  * </ul>
  * </p>
  */
-final class ShortToByteBufferAdapter extends ShortBuffer {
+final class ByteBufferAsShortBuffer extends ShortBuffer {
 
     private final ByteBuffer byteBuffer;
 
     static ShortBuffer asShortBuffer(ByteBuffer byteBuffer) {
         ByteBuffer slice = byteBuffer.slice();
         slice.order(byteBuffer.order());
-        return new ShortToByteBufferAdapter(slice);
+        return new ByteBufferAsShortBuffer(slice);
     }
 
-    private ShortToByteBufferAdapter(ByteBuffer byteBuffer) {
+    private ByteBufferAsShortBuffer(ByteBuffer byteBuffer) {
         super(byteBuffer.capacity() / SizeOf.SHORT);
         this.byteBuffer = byteBuffer;
         this.byteBuffer.clear();
@@ -49,7 +49,7 @@
 
     @Override
     public ShortBuffer asReadOnlyBuffer() {
-        ShortToByteBufferAdapter buf = new ShortToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
+        ByteBufferAsShortBuffer buf = new ByteBufferAsShortBuffer(byteBuffer.asReadOnlyBuffer());
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -75,7 +75,7 @@
     @Override
     public ShortBuffer duplicate() {
         ByteBuffer bb = byteBuffer.duplicate().order(byteBuffer.order());
-        ShortToByteBufferAdapter buf = new ShortToByteBufferAdapter(bb);
+        ByteBufferAsShortBuffer buf = new ByteBufferAsShortBuffer(bb);
         buf.limit = limit;
         buf.position = position;
         buf.mark = mark;
@@ -103,7 +103,7 @@
         if (byteBuffer instanceof DirectByteBuffer) {
             ((DirectByteBuffer) byteBuffer).get(dst, dstOffset, shortCount);
         } else {
-            ((HeapByteBuffer) byteBuffer).get(dst, dstOffset, shortCount);
+            ((ByteArrayBuffer) byteBuffer).get(dst, dstOffset, shortCount);
         }
         this.position += shortCount;
         return this;
@@ -156,10 +156,10 @@
     public ShortBuffer put(short[] src, int srcOffset, int shortCount) {
         byteBuffer.limit(limit * SizeOf.SHORT);
         byteBuffer.position(position * SizeOf.SHORT);
-        if (byteBuffer instanceof ReadWriteDirectByteBuffer) {
-            ((ReadWriteDirectByteBuffer) byteBuffer).put(src, srcOffset, shortCount);
+        if (byteBuffer instanceof DirectByteBuffer) {
+            ((DirectByteBuffer) byteBuffer).put(src, srcOffset, shortCount);
         } else {
-            ((ReadWriteHeapByteBuffer) byteBuffer).put(src, srcOffset, shortCount);
+            ((ByteArrayBuffer) byteBuffer).put(src, srcOffset, shortCount);
         }
         this.position += shortCount;
         return this;
@@ -170,7 +170,7 @@
         byteBuffer.limit(limit * SizeOf.SHORT);
         byteBuffer.position(position * SizeOf.SHORT);
         ByteBuffer bb = byteBuffer.slice().order(byteBuffer.order());
-        ShortBuffer result = new ShortToByteBufferAdapter(bb);
+        ShortBuffer result = new ByteBufferAsShortBuffer(bb);
         byteBuffer.clear();
         return result;
     }
diff --git a/luni/src/main/java/java/nio/CharArrayBuffer.java b/luni/src/main/java/java/nio/CharArrayBuffer.java
index 53d7fb6..43a75a6 100644
--- a/luni/src/main/java/java/nio/CharArrayBuffer.java
+++ b/luni/src/main/java/java/nio/CharArrayBuffer.java
@@ -18,82 +18,153 @@
 package java.nio;
 
 /**
- * CharArrayBuffer, ReadWriteCharArrayBuffer and ReadOnlyCharArrayBuffer compose
- * the implementation of array based char buffers.
- * <p>
- * CharArrayBuffer implements all the shared readonly methods and is extended by
- * the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
+ * CharArrayBuffer implements char[]-based CharBuffers.
  */
-abstract class CharArrayBuffer extends CharBuffer {
+final class CharArrayBuffer extends CharBuffer {
 
-    protected final char[] backingArray;
+  private final char[] backingArray;
 
-    protected final int offset;
+  private final int arrayOffset;
 
-    CharArrayBuffer(char[] array) {
-        this(array.length, array, 0);
+  private final boolean isReadOnly;
+
+  CharArrayBuffer(char[] array) {
+    this(array.length, array, 0, false);
+  }
+
+  private CharArrayBuffer(int capacity, char[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+  }
+
+  private static CharArrayBuffer copy(CharArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    CharArrayBuffer buf = new CharArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public CharBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+  @Override public CharBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    CharArrayBuffer(int capacity) {
-        this(capacity, new char[capacity], 0);
-    }
+  @Override public CharBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
 
-    CharArrayBuffer(int capacity, char[] backingArray, int offset) {
-        super(capacity);
-        this.backingArray = backingArray;
-        this.offset = offset;
-    }
+  @Override public CharBuffer slice() {
+    return new CharArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
 
-    @Override
-    public final char get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
-    }
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
 
-    @Override
-    public final char get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
+  @Override char[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return backingArray;
+  }
 
-    @Override
-    public final CharBuffer get(char[] dst, int srcOffset, int charCount) {
-        if (charCount > remaining()) {
-            throw new BufferUnderflowException();
-        }
-        System.arraycopy(backingArray, offset + position, dst, srcOffset, charCount);
-        position += charCount;
-        return this;
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return arrayOffset;
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return false;
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
     }
+    return true;
+  }
 
-    @Override
-    public final ByteOrder order() {
-        return ByteOrder.nativeOrder();
+  @Override public final char get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return backingArray[arrayOffset + position++];
+  }
 
-    @Override
-    public final CharSequence subSequence(int start, int end) {
-        checkStartEndRemaining(start, end);
-        CharBuffer result = duplicate();
-        result.limit(position + end);
-        result.position(position + start);
-        return result;
-    }
+  @Override public final char get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
 
-    @Override
-    public final String toString() {
-        return String.copyValueOf(backingArray, offset + position, remaining());
+  @Override public final CharBuffer get(char[] dst, int srcOffset, int charCount) {
+    if (charCount > remaining()) {
+      throw new BufferUnderflowException();
     }
+    System.arraycopy(backingArray, arrayOffset + position, dst, srcOffset, charCount);
+    position += charCount;
+    return this;
+  }
+
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public final ByteOrder order() {
+    return ByteOrder.nativeOrder();
+  }
+
+  @Override public final CharSequence subSequence(int start, int end) {
+    checkStartEndRemaining(start, end);
+    CharBuffer result = duplicate();
+    result.limit(position + end);
+    result.position(position + start);
+    return result;
+  }
+
+  @Override public CharBuffer put(char c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (position == limit) {
+      throw new BufferOverflowException();
+    }
+    backingArray[arrayOffset + position++] = c;
+    return this;
+  }
+
+  @Override public CharBuffer put(int index, char c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = c;
+    return this;
+  }
+
+  @Override public CharBuffer put(char[] src, int srcOffset, int charCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (charCount > remaining()) {
+      throw new BufferOverflowException();
+    }
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, charCount);
+    position += charCount;
+    return this;
+  }
+
+  @Override public final String toString() {
+    return String.copyValueOf(backingArray, arrayOffset + position, remaining());
+  }
 }
diff --git a/luni/src/main/java/java/nio/CharBuffer.java b/luni/src/main/java/java/nio/CharBuffer.java
index 6429ae2..5c6f0fc 100644
--- a/luni/src/main/java/java/nio/CharBuffer.java
+++ b/luni/src/main/java/java/nio/CharBuffer.java
@@ -51,7 +51,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteCharArrayBuffer(capacity);
+        return new CharArrayBuffer(new char[capacity]);
     }
 
     /**
@@ -88,7 +88,7 @@
      */
     public static CharBuffer wrap(char[] array, int start, int charCount) {
         Arrays.checkOffsetAndCount(array.length, start, charCount);
-        CharBuffer buf = new ReadWriteCharArrayBuffer(array);
+        CharBuffer buf = new CharArrayBuffer(array);
         buf.position = start;
         buf.limit = start + charCount;
         return buf;
@@ -499,6 +499,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public CharBuffer put(CharBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
@@ -568,6 +571,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public CharBuffer put(String str, int start, int end) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (start < 0 || end < start || end > str.length()) {
             throw new IndexOutOfBoundsException("str.length()=" + str.length() +
                     ", start=" + start + ", end=" + end);
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index 4d2fc5a..a23010e 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -253,7 +253,8 @@
         }
 
         if (isConnected() && !connectAddress.equals(isa)) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Connected to " + connectAddress +
+                                               ", not " + socketAddress);
         }
 
         synchronized (writeLock) {
diff --git a/luni/src/main/java/java/nio/DirectByteBuffer.java b/luni/src/main/java/java/nio/DirectByteBuffer.java
index 0ddef14..43db9e0 100644
--- a/luni/src/main/java/java/nio/DirectByteBuffer.java
+++ b/luni/src/main/java/java/nio/DirectByteBuffer.java
@@ -17,208 +17,464 @@
 
 package java.nio;
 
+import java.nio.channels.FileChannel.MapMode;
+
+import libcore.io.Memory;
 import libcore.io.SizeOf;
 
-abstract class DirectByteBuffer extends BaseByteBuffer {
-    // This is the offset into {@code Buffer.block} at which this buffer logically starts.
-    // TODO: rewrite this so we set 'block' to an OffsetMemoryBlock?
-    protected final int offset;
+class DirectByteBuffer extends MappedByteBuffer {
+  // This is the offset into {@code Buffer.block} at which this buffer logically starts.
+  // TODO: rewrite this so we set 'block' to an OffsetMemoryBlock?
+  protected final int offset;
 
-    protected DirectByteBuffer(MemoryBlock block, int capacity, int offset) {
-        super(capacity, block);
+  private final boolean isReadOnly;
 
-        long baseSize = block.getSize();
-        if (baseSize >= 0 && (capacity + offset) > baseSize) {
-            throw new IllegalArgumentException("capacity + offset > baseSize");
-        }
+  protected DirectByteBuffer(MemoryBlock block, int capacity, int offset, boolean isReadOnly, MapMode mapMode) {
+    super(block, capacity, mapMode);
 
-        this.offset = offset;
-        this.effectiveDirectAddress = block.toInt() + offset;
+    long baseSize = block.getSize();
+    if (baseSize >= 0 && (capacity + offset) > baseSize) {
+      throw new IllegalArgumentException("capacity + offset > baseSize");
     }
 
-    @Override
-    public final ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
-        checkGetBounds(1, dst.length, dstOffset, byteCount);
-        this.block.peekByteArray(offset + position, dst, dstOffset, byteCount);
-        position += byteCount;
-        return this;
-    }
+    this.effectiveDirectAddress = block.toLong() + offset;
 
-    final void get(char[] dst, int dstOffset, int charCount) {
-        int byteCount = checkGetBounds(SizeOf.CHAR, dst.length, dstOffset, charCount);
-        this.block.peekCharArray(offset + position, dst, dstOffset, charCount, order.needsSwap);
-        position += byteCount;
-    }
+    this.offset = offset;
+    this.isReadOnly = isReadOnly;
+  }
 
-    final void get(double[] dst, int dstOffset, int doubleCount) {
-        int byteCount = checkGetBounds(SizeOf.DOUBLE, dst.length, dstOffset, doubleCount);
-        this.block.peekDoubleArray(offset + position, dst, dstOffset, doubleCount, order.needsSwap);
-        position += byteCount;
-    }
+  // Used by the JNI NewDirectByteBuffer function.
+  DirectByteBuffer(int address, int capacity) {
+    this(MemoryBlock.wrapFromJni(address, capacity), capacity, 0, false, null);
+  }
 
-    final void get(float[] dst, int dstOffset, int floatCount) {
-        int byteCount = checkGetBounds(SizeOf.FLOAT, dst.length, dstOffset, floatCount);
-        this.block.peekFloatArray(offset + position, dst, dstOffset, floatCount, order.needsSwap);
-        position += byteCount;
-    }
+  private static DirectByteBuffer copy(DirectByteBuffer other, int markOfOther, boolean isReadOnly) {
+    DirectByteBuffer buf = new DirectByteBuffer(other.block, other.capacity(), other.offset, isReadOnly, other.mapMode);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
 
-    final void get(int[] dst, int dstOffset, int intCount) {
-        int byteCount = checkGetBounds(SizeOf.INT, dst.length, dstOffset, intCount);
-        this.block.peekIntArray(offset + position, dst, dstOffset, intCount, order.needsSwap);
-        position += byteCount;
-    }
+  @Override public ByteBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
 
-    final void get(long[] dst, int dstOffset, int longCount) {
-        int byteCount = checkGetBounds(SizeOf.LONG, dst.length, dstOffset, longCount);
-        this.block.peekLongArray(offset + position, dst, dstOffset, longCount, order.needsSwap);
-        position += byteCount;
+  @Override public ByteBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    Memory.memmove(this, 0, this, position, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    final void get(short[] dst, int dstOffset, int shortCount) {
-        int byteCount = checkGetBounds(SizeOf.SHORT, dst.length, dstOffset, shortCount);
-        this.block.peekShortArray(offset + position, dst, dstOffset, shortCount, order.needsSwap);
-        position += byteCount;
-    }
+  @Override public ByteBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
 
-    @Override
-    public final byte get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return this.block.peekByte(offset + position++);
-    }
+  @Override public ByteBuffer slice() {
+    return new DirectByteBuffer(block, remaining(), offset + position, isReadOnly, mapMode);
+  }
 
-    @Override
-    public final byte get(int index) {
-        checkIndex(index);
-        return this.block.peekByte(offset + index);
-    }
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
 
-    @Override
-    public final char getChar() {
-        int newPosition = position + SizeOf.CHAR;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        char result = (char) this.block.peekShort(offset + position, order);
-        position = newPosition;
-        return result;
+  @Override byte[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    byte[] array = this.block.array();
+    if (array == null) {
+      throw new UnsupportedOperationException();
+    }
+    return array;
+  }
 
-    @Override
-    public final char getChar(int index) {
-        checkIndex(index, SizeOf.CHAR);
-        return (char) this.block.peekShort(offset + index, order);
-    }
+  @Override int protectedArrayOffset() {
+    protectedArray(); // Throw if we don't have an array or are read-only.
+    return offset;
+  }
 
-    @Override
-    public final double getDouble() {
-        int newPosition = position + SizeOf.DOUBLE;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        double result = Double.longBitsToDouble(this.block.peekLong(offset + position, order));
-        position = newPosition;
-        return result;
-    }
+  @Override boolean protectedHasArray() {
+    return !isReadOnly && (block.array() != null);
+  }
 
-    @Override
-    public final double getDouble(int index) {
-        checkIndex(index, SizeOf.DOUBLE);
-        return Double.longBitsToDouble(this.block.peekLong(offset + index, order));
-    }
+  @Override public final ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
+    checkGetBounds(1, dst.length, dstOffset, byteCount);
+    this.block.peekByteArray(offset + position, dst, dstOffset, byteCount);
+    position += byteCount;
+    return this;
+  }
 
-    @Override
-    public final float getFloat() {
-        int newPosition = position + SizeOf.FLOAT;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        float result = Float.intBitsToFloat(this.block.peekInt(offset + position, order));
-        position = newPosition;
-        return result;
-    }
+  final void get(char[] dst, int dstOffset, int charCount) {
+    int byteCount = checkGetBounds(SizeOf.CHAR, dst.length, dstOffset, charCount);
+    this.block.peekCharArray(offset + position, dst, dstOffset, charCount, order.needsSwap);
+    position += byteCount;
+  }
 
-    @Override
-    public final float getFloat(int index) {
-        checkIndex(index, SizeOf.FLOAT);
-        return Float.intBitsToFloat(this.block.peekInt(offset + index, order));
-    }
+  final void get(double[] dst, int dstOffset, int doubleCount) {
+    int byteCount = checkGetBounds(SizeOf.DOUBLE, dst.length, dstOffset, doubleCount);
+    this.block.peekDoubleArray(offset + position, dst, dstOffset, doubleCount, order.needsSwap);
+    position += byteCount;
+  }
 
-    @Override
-    public final int getInt() {
-        int newPosition = position + SizeOf.INT;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        int result = this.block.peekInt(offset + position, order);
-        position = newPosition;
-        return result;
-    }
+  final void get(float[] dst, int dstOffset, int floatCount) {
+    int byteCount = checkGetBounds(SizeOf.FLOAT, dst.length, dstOffset, floatCount);
+    this.block.peekFloatArray(offset + position, dst, dstOffset, floatCount, order.needsSwap);
+    position += byteCount;
+  }
 
-    @Override
-    public final int getInt(int index) {
-        checkIndex(index, SizeOf.INT);
-        return this.block.peekInt(offset + index, order);
-    }
+  final void get(int[] dst, int dstOffset, int intCount) {
+    int byteCount = checkGetBounds(SizeOf.INT, dst.length, dstOffset, intCount);
+    this.block.peekIntArray(offset + position, dst, dstOffset, intCount, order.needsSwap);
+    position += byteCount;
+  }
 
-    @Override
-    public final long getLong() {
-        int newPosition = position + SizeOf.LONG;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        long result = this.block.peekLong(offset + position, order);
-        position = newPosition;
-        return result;
-    }
+  final void get(long[] dst, int dstOffset, int longCount) {
+    int byteCount = checkGetBounds(SizeOf.LONG, dst.length, dstOffset, longCount);
+    this.block.peekLongArray(offset + position, dst, dstOffset, longCount, order.needsSwap);
+    position += byteCount;
+  }
 
-    @Override
-    public final long getLong(int index) {
-        checkIndex(index, SizeOf.LONG);
-        return this.block.peekLong(offset + index, order);
-    }
+  final void get(short[] dst, int dstOffset, int shortCount) {
+    int byteCount = checkGetBounds(SizeOf.SHORT, dst.length, dstOffset, shortCount);
+    this.block.peekShortArray(offset + position, dst, dstOffset, shortCount, order.needsSwap);
+    position += byteCount;
+  }
 
-    @Override
-    public final short getShort() {
-        int newPosition = position + SizeOf.SHORT;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        short result = this.block.peekShort(offset + position, order);
-        position = newPosition;
-        return result;
+  @Override public final byte get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return this.block.peekByte(offset + position++);
+  }
 
-    @Override
-    public final short getShort(int index) {
-        checkIndex(index, SizeOf.SHORT);
-        return this.block.peekShort(offset + index, order);
-    }
+  @Override public final byte get(int index) {
+    checkIndex(index);
+    return this.block.peekByte(offset + index);
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return true;
+  @Override public final char getChar() {
+    int newPosition = position + SizeOf.CHAR;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
     }
+    char result = (char) this.block.peekShort(offset + position, order);
+    position = newPosition;
+    return result;
+  }
 
-    public final void free() {
-        block.free();
-    }
+  @Override public final char getChar(int index) {
+    checkIndex(index, SizeOf.CHAR);
+    return (char) this.block.peekShort(offset + index, order);
+  }
 
-    @Override byte[] protectedArray() {
-        byte[] array = this.block.array();
-        if (array == null) {
-            throw new UnsupportedOperationException();
-        }
-        return array;
+  @Override public final double getDouble() {
+    int newPosition = position + SizeOf.DOUBLE;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
     }
+    double result = Double.longBitsToDouble(this.block.peekLong(offset + position, order));
+    position = newPosition;
+    return result;
+  }
 
-    @Override int protectedArrayOffset() {
-        protectedArray(); // Throw if we don't have an array.
-        return offset;
-    }
+  @Override public final double getDouble(int index) {
+    checkIndex(index, SizeOf.DOUBLE);
+    return Double.longBitsToDouble(this.block.peekLong(offset + index, order));
+  }
 
-    @Override boolean protectedHasArray() {
-        return this.block.array() != null;
+  @Override public final float getFloat() {
+    int newPosition = position + SizeOf.FLOAT;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
     }
+    float result = Float.intBitsToFloat(this.block.peekInt(offset + position, order));
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final float getFloat(int index) {
+    checkIndex(index, SizeOf.FLOAT);
+    return Float.intBitsToFloat(this.block.peekInt(offset + index, order));
+  }
+
+  @Override public final int getInt() {
+    int newPosition = position + SizeOf.INT;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    int result = this.block.peekInt(offset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final int getInt(int index) {
+    checkIndex(index, SizeOf.INT);
+    return this.block.peekInt(offset + index, order);
+  }
+
+  @Override public final long getLong() {
+    int newPosition = position + SizeOf.LONG;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    long result = this.block.peekLong(offset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final long getLong(int index) {
+    checkIndex(index, SizeOf.LONG);
+    return this.block.peekLong(offset + index, order);
+  }
+
+  @Override public final short getShort() {
+    int newPosition = position + SizeOf.SHORT;
+    if (newPosition > limit) {
+      throw new BufferUnderflowException();
+    }
+    short result = this.block.peekShort(offset + position, order);
+    position = newPosition;
+    return result;
+  }
+
+  @Override public final short getShort(int index) {
+    checkIndex(index, SizeOf.SHORT);
+    return this.block.peekShort(offset + index, order);
+  }
+
+  @Override public final boolean isDirect() {
+    return true;
+  }
+
+  public final void free() {
+    block.free();
+  }
+
+  @Override public final CharBuffer asCharBuffer() {
+    return ByteBufferAsCharBuffer.asCharBuffer(this);
+  }
+
+  @Override public final DoubleBuffer asDoubleBuffer() {
+    return ByteBufferAsDoubleBuffer.asDoubleBuffer(this);
+  }
+
+  @Override public final FloatBuffer asFloatBuffer() {
+    return ByteBufferAsFloatBuffer.asFloatBuffer(this);
+  }
+
+  @Override public final IntBuffer asIntBuffer() {
+    return ByteBufferAsIntBuffer.asIntBuffer(this);
+  }
+
+  @Override public final LongBuffer asLongBuffer() {
+    return ByteBufferAsLongBuffer.asLongBuffer(this);
+  }
+
+  @Override public final ShortBuffer asShortBuffer() {
+    return ByteBufferAsShortBuffer.asShortBuffer(this);
+  }
+
+  @Override public ByteBuffer put(byte value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (position == limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeByte(offset + position++, value);
+    return this;
+  }
+
+  @Override public ByteBuffer put(int index, byte value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    this.block.pokeByte(offset + index, value);
+    return this;
+  }
+
+  @Override public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkPutBounds(1, src.length, srcOffset, byteCount);
+    this.block.pokeByteArray(offset + position, src, srcOffset, byteCount);
+    position += byteCount;
+    return this;
+  }
+
+  final void put(char[] src, int srcOffset, int charCount) {
+    int byteCount = checkPutBounds(SizeOf.CHAR, src.length, srcOffset, charCount);
+    this.block.pokeCharArray(offset + position, src, srcOffset, charCount, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(double[] src, int srcOffset, int doubleCount) {
+    int byteCount = checkPutBounds(SizeOf.DOUBLE, src.length, srcOffset, doubleCount);
+    this.block.pokeDoubleArray(offset + position, src, srcOffset, doubleCount, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(float[] src, int srcOffset, int floatCount) {
+    int byteCount = checkPutBounds(SizeOf.FLOAT, src.length, srcOffset, floatCount);
+    this.block.pokeFloatArray(offset + position, src, srcOffset, floatCount, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(int[] src, int srcOffset, int intCount) {
+    int byteCount = checkPutBounds(SizeOf.INT, src.length, srcOffset, intCount);
+    this.block.pokeIntArray(offset + position, src, srcOffset, intCount, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(long[] src, int srcOffset, int longCount) {
+    int byteCount = checkPutBounds(SizeOf.LONG, src.length, srcOffset, longCount);
+    this.block.pokeLongArray(offset + position, src, srcOffset, longCount, order.needsSwap);
+    position += byteCount;
+  }
+
+  final void put(short[] src, int srcOffset, int shortCount) {
+    int byteCount = checkPutBounds(SizeOf.SHORT, src.length, srcOffset, shortCount);
+    this.block.pokeShortArray(offset + position, src, srcOffset, shortCount, order.needsSwap);
+    position += byteCount;
+  }
+
+  @Override public ByteBuffer putChar(char value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.CHAR;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeShort(offset + position, (short) value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putChar(int index, char value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.CHAR);
+    this.block.pokeShort(offset + index, (short) value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putDouble(double value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.DOUBLE;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeLong(offset + position, Double.doubleToRawLongBits(value), order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putDouble(int index, double value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.DOUBLE);
+    this.block.pokeLong(offset + index, Double.doubleToRawLongBits(value), order);
+    return this;
+  }
+
+  @Override public ByteBuffer putFloat(float value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.FLOAT;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeInt(offset + position, Float.floatToRawIntBits(value), order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putFloat(int index, float value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.FLOAT);
+    this.block.pokeInt(offset + index, Float.floatToRawIntBits(value), order);
+    return this;
+  }
+
+  @Override public ByteBuffer putInt(int value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.INT;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeInt(offset + position, value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putInt(int index, int value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.INT);
+    this.block.pokeInt(offset + index, value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putLong(long value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.LONG;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeLong(offset + position, value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putLong(int index, long value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.LONG);
+    this.block.pokeLong(offset + index, value, order);
+    return this;
+  }
+
+  @Override public ByteBuffer putShort(short value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    int newPosition = position + SizeOf.SHORT;
+    if (newPosition > limit) {
+      throw new BufferOverflowException();
+    }
+    this.block.pokeShort(offset + position, value, order);
+    position = newPosition;
+    return this;
+  }
+
+  @Override public ByteBuffer putShort(int index, short value) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index, SizeOf.SHORT);
+    this.block.pokeShort(offset + index, value, order);
+    return this;
+  }
 }
diff --git a/luni/src/main/java/java/nio/DoubleArrayBuffer.java b/luni/src/main/java/java/nio/DoubleArrayBuffer.java
index ec32a80..f8e4e59 100644
--- a/luni/src/main/java/java/nio/DoubleArrayBuffer.java
+++ b/luni/src/main/java/java/nio/DoubleArrayBuffer.java
@@ -18,69 +18,141 @@
 package java.nio;
 
 /**
- * DoubleArrayBuffer, ReadWriteDoubleArrayBuffer and ReadOnlyDoubleArrayBuffer
- * compose the implementation of array based double buffers.
- * <p>
- * DoubleArrayBuffer implements all the shared readonly methods and is extended
- * by the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
+ * DoubleArrayBuffer implements double[]-based DoubleBuffers.
  */
-abstract class DoubleArrayBuffer extends DoubleBuffer {
+final class DoubleArrayBuffer extends DoubleBuffer {
 
-    protected final double[] backingArray;
+  private final double[] backingArray;
 
-    protected final int offset;
+  private final int arrayOffset;
 
-    DoubleArrayBuffer(double[] array) {
-        this(array.length, array, 0);
+  private final boolean isReadOnly;
+
+  DoubleArrayBuffer(double[] array) {
+    this(array.length, array, 0, false);
+  }
+
+  private DoubleArrayBuffer(int capacity, double[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+  }
+
+  private static DoubleArrayBuffer copy(DoubleArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    DoubleArrayBuffer buf = new DoubleArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public DoubleBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+  @Override public DoubleBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    DoubleArrayBuffer(int capacity) {
-        this(capacity, new double[capacity], 0);
+  @Override public DoubleBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
+
+  @Override public DoubleBuffer slice() {
+    return new DoubleArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
+
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  @Override double[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return backingArray;
+  }
 
-    DoubleArrayBuffer(int capacity, double[] backingArray, int offset) {
-        super(capacity);
-        this.backingArray = backingArray;
-        this.offset = offset;
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return arrayOffset;
+  }
 
-    @Override
-    public final double get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
     }
+    return true;
+  }
 
-    @Override
-    public final double get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
+  @Override public final double get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return backingArray[arrayOffset + position++];
+  }
 
-    @Override
-    public final DoubleBuffer get(double[] dst, int dstOffset, int doubleCount) {
-        if (doubleCount > remaining()) {
-            throw new BufferUnderflowException();
-        }
-        System.arraycopy(backingArray, offset + position, dst, dstOffset, doubleCount);
-        position += doubleCount;
-        return this;
+  @Override public final double get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
+
+  @Override public final DoubleBuffer get(double[] dst, int dstOffset, int doubleCount) {
+    if (doubleCount > remaining()) {
+      throw new BufferUnderflowException();
     }
+    System.arraycopy(backingArray, arrayOffset + position, dst, dstOffset, doubleCount);
+    position += doubleCount;
+    return this;
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return false;
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public final ByteOrder order() {
+    return ByteOrder.nativeOrder();
+  }
+
+  @Override public DoubleBuffer put(double c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
-
-    @Override
-    public final ByteOrder order() {
-        return ByteOrder.nativeOrder();
+    if (position == limit) {
+      throw new BufferOverflowException();
     }
+    backingArray[arrayOffset + position++] = c;
+    return this;
+  }
 
+  @Override public DoubleBuffer put(int index, double c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = c;
+    return this;
+  }
+
+  @Override public DoubleBuffer put(double[] src, int srcOffset, int doubleCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (doubleCount > remaining()) {
+      throw new BufferOverflowException();
+    }
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, doubleCount);
+    position += doubleCount;
+    return this;
+  }
 }
diff --git a/luni/src/main/java/java/nio/DoubleBuffer.java b/luni/src/main/java/java/nio/DoubleBuffer.java
index 8d90f89..401536d 100644
--- a/luni/src/main/java/java/nio/DoubleBuffer.java
+++ b/luni/src/main/java/java/nio/DoubleBuffer.java
@@ -49,7 +49,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteDoubleArrayBuffer(capacity);
+        return new DoubleArrayBuffer(new double[capacity]);
     }
 
     /**
@@ -86,7 +86,7 @@
      */
     public static DoubleBuffer wrap(double[] array, int start, int doubleCount) {
         Arrays.checkOffsetAndCount(array.length, start, doubleCount);
-        DoubleBuffer buf = new ReadWriteDoubleArrayBuffer(array);
+        DoubleBuffer buf = new DoubleArrayBuffer(array);
         buf.position = start;
         buf.limit = start + doubleCount;
         return buf;
@@ -437,6 +437,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public DoubleBuffer put(DoubleBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java
index 075243a..14e06b7 100644
--- a/luni/src/main/java/java/nio/FileChannelImpl.java
+++ b/luni/src/main/java/java/nio/FileChannelImpl.java
@@ -33,7 +33,6 @@
 import java.util.SortedSet;
 import java.util.TreeSet;
 import libcore.io.ErrnoException;
-import libcore.io.IoUtils;
 import libcore.io.Libcore;
 import libcore.io.StructFlock;
 import libcore.util.MutableLong;
@@ -236,13 +235,19 @@
             try {
                 Libcore.os.ftruncate(fd, position + size);
             } catch (ErrnoException errnoException) {
-                throw errnoException.rethrowAsIOException();
+                // EINVAL can be thrown if we're dealing with non-regular
+                // files, for example, character devices such as /dev/zero.
+                // In those cases, we ignore the failed truncation and
+                // continue on.
+                if (errnoException.errno != EINVAL) {
+                    throw errnoException.rethrowAsIOException();
+                }
             }
         }
         long alignment = position - position % Libcore.os.sysconf(_SC_PAGE_SIZE);
         int offset = (int) (position - alignment);
         MemoryBlock block = MemoryBlock.mmap(fd, alignment, size + offset, mapMode);
-        return new MappedByteBufferAdapter(block, (int) size, offset, mapMode);
+        return new DirectByteBuffer(block, (int) size, offset, (mapMode == MapMode.READ_ONLY), mapMode);
     }
 
     public long position() throws IOException {
diff --git a/luni/src/main/java/java/nio/FloatArrayBuffer.java b/luni/src/main/java/java/nio/FloatArrayBuffer.java
index c47ee21..698174c 100644
--- a/luni/src/main/java/java/nio/FloatArrayBuffer.java
+++ b/luni/src/main/java/java/nio/FloatArrayBuffer.java
@@ -18,69 +18,142 @@
 package java.nio;
 
 /**
- * FloatArrayBuffer, ReadWriteFloatArrayBuffer and ReadOnlyFloatArrayBuffer
- * compose the implementation of array based float buffers.
- * <p>
- * FloatArrayBuffer implements all the shared readonly methods and is extended
- * by the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
+ * FloatArrayBuffer implements float[]-based FloatBuffers.
  */
-abstract class FloatArrayBuffer extends FloatBuffer {
+final class FloatArrayBuffer extends FloatBuffer {
 
-    protected final float[] backingArray;
+  private final float[] backingArray;
 
-    protected final int offset;
+  private final int arrayOffset;
 
-    FloatArrayBuffer(float[] array) {
-        this(array.length, array, 0);
+  private final boolean isReadOnly;
+
+  FloatArrayBuffer(float[] array) {
+    this(array.length, array, 0, false);
+  }
+
+  private FloatArrayBuffer(int capacity, float[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+  }
+
+  private static FloatArrayBuffer copy(FloatArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    FloatArrayBuffer buf = new FloatArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public FloatBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+
+  @Override public FloatBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    FloatArrayBuffer(int capacity) {
-        this(capacity, new float[capacity], 0);
+  @Override public FloatBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
+
+  @Override public FloatBuffer slice() {
+    return new FloatArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
+
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  @Override float[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return backingArray;
+  }
 
-    FloatArrayBuffer(int capacity, float[] backingArray, int offset) {
-        super(capacity);
-        this.backingArray = backingArray;
-        this.offset = offset;
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return arrayOffset;
+  }
 
-    @Override
-    public final float get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
     }
+    return true;
+  }
 
-    @Override
-    public final float get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
+  @Override public final float get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return backingArray[arrayOffset + position++];
+  }
 
-    @Override
-    public final FloatBuffer get(float[] dst, int dstOffset, int floatCount) {
-        if (floatCount > remaining()) {
-            throw new BufferUnderflowException();
-        }
-        System.arraycopy(backingArray, offset + position, dst, dstOffset, floatCount);
-        position += floatCount;
-        return this;
+  @Override public final float get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
+
+  @Override public final FloatBuffer get(float[] dst, int dstOffset, int floatCount) {
+    if (floatCount > remaining()) {
+      throw new BufferUnderflowException();
     }
+    System.arraycopy(backingArray, arrayOffset + position, dst, dstOffset, floatCount);
+    position += floatCount;
+    return this;
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return false;
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public final ByteOrder order() {
+    return ByteOrder.nativeOrder();
+  }
+
+  @Override public FloatBuffer put(float c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
-
-    @Override
-    public final ByteOrder order() {
-        return ByteOrder.nativeOrder();
+    if (position == limit) {
+      throw new BufferOverflowException();
     }
+    backingArray[arrayOffset + position++] = c;
+    return this;
+  }
 
+  @Override public FloatBuffer put(int index, float c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = c;
+    return this;
+  }
+
+  @Override public FloatBuffer put(float[] src, int srcOffset, int floatCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (floatCount > remaining()) {
+      throw new BufferOverflowException();
+    }
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, floatCount);
+    position += floatCount;
+    return this;
+  }
 }
diff --git a/luni/src/main/java/java/nio/FloatBuffer.java b/luni/src/main/java/java/nio/FloatBuffer.java
index 814eb53..ac5e572 100644
--- a/luni/src/main/java/java/nio/FloatBuffer.java
+++ b/luni/src/main/java/java/nio/FloatBuffer.java
@@ -48,7 +48,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteFloatArrayBuffer(capacity);
+        return new FloatArrayBuffer(new float[capacity]);
     }
 
     /**
@@ -87,7 +87,7 @@
      */
     public static FloatBuffer wrap(float[] array, int start, int floatCount) {
         Arrays.checkOffsetAndCount(array.length, start, floatCount);
-        FloatBuffer buf = new ReadWriteFloatArrayBuffer(array);
+        FloatBuffer buf = new FloatArrayBuffer(array);
         buf.position = start;
         buf.limit = start + floatCount;
         return buf;
@@ -436,6 +436,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public FloatBuffer put(FloatBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
diff --git a/luni/src/main/java/java/nio/HeapByteBuffer.java b/luni/src/main/java/java/nio/HeapByteBuffer.java
deleted file mode 100644
index 6b65ec1..0000000
--- a/luni/src/main/java/java/nio/HeapByteBuffer.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-import libcore.io.SizeOf;
-import libcore.io.Memory;
-
-/**
- * HeapByteBuffer, ReadWriteHeapByteBuffer and ReadOnlyHeapByteBuffer compose
- * the implementation of array based byte buffers.
- * <p>
- * HeapByteBuffer implements all the shared readonly methods and is extended by
- * the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
- */
-abstract class HeapByteBuffer extends BaseByteBuffer {
-
-    /**
-     * These fields are non-private for NioUtils.unsafeArray.
-     */
-    final byte[] backingArray;
-    final int offset;
-
-    HeapByteBuffer(byte[] backingArray) {
-        this(backingArray, backingArray.length, 0);
-    }
-
-    HeapByteBuffer(int capacity) {
-        this(new byte[capacity], capacity, 0);
-    }
-
-    HeapByteBuffer(byte[] backingArray, int capacity, int offset) {
-        super(capacity, null);
-        this.backingArray = backingArray;
-        this.offset = offset;
-        if (offset + capacity > backingArray.length) {
-            throw new IndexOutOfBoundsException("backingArray.length=" + backingArray.length +
-                    ", capacity=" + capacity + ", offset=" + offset);
-        }
-    }
-
-    @Override
-    public final ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
-        checkGetBounds(1, dst.length, dstOffset, byteCount);
-        System.arraycopy(backingArray, offset + position, dst, dstOffset, byteCount);
-        position += byteCount;
-        return this;
-    }
-
-    final void get(char[] dst, int dstOffset, int charCount) {
-        int byteCount = checkGetBounds(SizeOf.CHAR, dst.length, dstOffset, charCount);
-        Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, offset + position, SizeOf.CHAR, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void get(double[] dst, int dstOffset, int doubleCount) {
-        int byteCount = checkGetBounds(SizeOf.DOUBLE, dst.length, dstOffset, doubleCount);
-        Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, offset + position, SizeOf.DOUBLE, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void get(float[] dst, int dstOffset, int floatCount) {
-        int byteCount = checkGetBounds(SizeOf.FLOAT, dst.length, dstOffset, floatCount);
-        Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, offset + position, SizeOf.FLOAT, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void get(int[] dst, int dstOffset, int intCount) {
-        int byteCount = checkGetBounds(SizeOf.INT, dst.length, dstOffset, intCount);
-        Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, offset + position, SizeOf.INT, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void get(long[] dst, int dstOffset, int longCount) {
-        int byteCount = checkGetBounds(SizeOf.LONG, dst.length, dstOffset, longCount);
-        Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, offset + position, SizeOf.LONG, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void get(short[] dst, int dstOffset, int shortCount) {
-        int byteCount = checkGetBounds(SizeOf.SHORT, dst.length, dstOffset, shortCount);
-        Memory.unsafeBulkGet(dst, dstOffset, byteCount, backingArray, offset + position, SizeOf.SHORT, order.needsSwap);
-        position += byteCount;
-    }
-
-    @Override
-    public final byte get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
-    }
-
-    @Override
-    public final byte get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
-    }
-
-    @Override
-    public final char getChar() {
-        int newPosition = position + SizeOf.CHAR;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        char result = (char) Memory.peekShort(backingArray, offset + position, order);
-        position = newPosition;
-        return result;
-    }
-
-    @Override
-    public final char getChar(int index) {
-        checkIndex(index, SizeOf.CHAR);
-        return (char) Memory.peekShort(backingArray, offset + index, order);
-    }
-
-    @Override
-    public final double getDouble() {
-        return Double.longBitsToDouble(getLong());
-    }
-
-    @Override
-    public final double getDouble(int index) {
-        return Double.longBitsToDouble(getLong(index));
-    }
-
-    @Override
-    public final float getFloat() {
-        return Float.intBitsToFloat(getInt());
-    }
-
-    @Override
-    public final float getFloat(int index) {
-        return Float.intBitsToFloat(getInt(index));
-    }
-
-    @Override
-    public final int getInt() {
-        int newPosition = position + SizeOf.INT;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        int result = Memory.peekInt(backingArray, offset + position, order);
-        position = newPosition;
-        return result;
-    }
-
-    @Override
-    public final int getInt(int index) {
-        checkIndex(index, SizeOf.INT);
-        return Memory.peekInt(backingArray, offset + index, order);
-    }
-
-    @Override
-    public final long getLong() {
-        int newPosition = position + SizeOf.LONG;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        long result = Memory.peekLong(backingArray, offset + position, order);
-        position = newPosition;
-        return result;
-    }
-
-    @Override
-    public final long getLong(int index) {
-        checkIndex(index, SizeOf.LONG);
-        return Memory.peekLong(backingArray, offset + index, order);
-    }
-
-    @Override
-    public final short getShort() {
-        int newPosition = position + SizeOf.SHORT;
-        if (newPosition > limit) {
-            throw new BufferUnderflowException();
-        }
-        short result = Memory.peekShort(backingArray, offset + position, order);
-        position = newPosition;
-        return result;
-    }
-
-    @Override
-    public final short getShort(int index) {
-        checkIndex(index, SizeOf.SHORT);
-        return Memory.peekShort(backingArray, offset + index, order);
-    }
-
-    @Override
-    public final boolean isDirect() {
-        return false;
-    }
-}
diff --git a/luni/src/main/java/java/nio/IntArrayBuffer.java b/luni/src/main/java/java/nio/IntArrayBuffer.java
index d031415..a5f9f39 100644
--- a/luni/src/main/java/java/nio/IntArrayBuffer.java
+++ b/luni/src/main/java/java/nio/IntArrayBuffer.java
@@ -18,69 +18,141 @@
 package java.nio;
 
 /**
- * IntArrayBuffer, ReadWriteIntArrayBuffer and ReadOnlyIntArrayBuffer compose
- * the implementation of array based int buffers.
- * <p>
- * IntArrayBuffer implements all the shared readonly methods and is extended by
- * the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
+ * IntArrayBuffer implements int[]-based IntBuffers.
  */
-abstract class IntArrayBuffer extends IntBuffer {
+final class IntArrayBuffer extends IntBuffer {
 
-    protected final int[] backingArray;
+  private final int[] backingArray;
 
-    protected final int offset;
+  private final int arrayOffset;
 
-    IntArrayBuffer(int[] array) {
-        this(array.length, array, 0);
+  private final boolean isReadOnly;
+
+  IntArrayBuffer(int[] array) {
+    this(array.length, array, 0, false);
+  }
+
+  private IntArrayBuffer(int capacity, int[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+  }
+
+  private static IntArrayBuffer copy(IntArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    IntArrayBuffer buf = new IntArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public IntBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+  @Override public IntBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    IntArrayBuffer(int capacity) {
-        this(capacity, new int[capacity], 0);
+  @Override public IntBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
+
+  @Override public IntBuffer slice() {
+    return new IntArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
+
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  @Override int[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return backingArray;
+  }
 
-    IntArrayBuffer(int capacity, int[] backingArray, int offset) {
-        super(capacity);
-        this.backingArray = backingArray;
-        this.offset = offset;
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return arrayOffset;
+  }
 
-    @Override
-    public final int get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
     }
+    return true;
+  }
 
-    @Override
-    public final int get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
+  @Override public final int get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return backingArray[arrayOffset + position++];
+  }
 
-    @Override
-    public final IntBuffer get(int[] dst, int dstOffset, int intCount) {
-        if (intCount > remaining()) {
-            throw new BufferUnderflowException();
-        }
-        System.arraycopy(backingArray, offset + position, dst, dstOffset, intCount);
-        position += intCount;
-        return this;
+  @Override public final int get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
+
+  @Override public final IntBuffer get(int[] dst, int dstOffset, int intCount) {
+    if (intCount > remaining()) {
+      throw new BufferUnderflowException();
     }
+    System.arraycopy(backingArray, arrayOffset + position, dst, dstOffset, intCount);
+    position += intCount;
+    return this;
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return false;
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public final ByteOrder order() {
+    return ByteOrder.nativeOrder();
+  }
+
+  @Override public IntBuffer put(int c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
-
-    @Override
-    public final ByteOrder order() {
-        return ByteOrder.nativeOrder();
+    if (position == limit) {
+      throw new BufferOverflowException();
     }
+    backingArray[arrayOffset + position++] = c;
+    return this;
+  }
 
+  @Override public IntBuffer put(int index, int c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = c;
+    return this;
+  }
+
+  @Override public IntBuffer put(int[] src, int srcOffset, int intCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (intCount > remaining()) {
+      throw new BufferOverflowException();
+    }
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, intCount);
+    position += intCount;
+    return this;
+  }
 }
diff --git a/luni/src/main/java/java/nio/IntBuffer.java b/luni/src/main/java/java/nio/IntBuffer.java
index 0ff758a..bbcc2e3 100644
--- a/luni/src/main/java/java/nio/IntBuffer.java
+++ b/luni/src/main/java/java/nio/IntBuffer.java
@@ -46,7 +46,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteIntArrayBuffer(capacity);
+        return new IntArrayBuffer(new int[capacity]);
     }
 
     /**
@@ -83,7 +83,7 @@
      */
     public static IntBuffer wrap(int[] array, int start, int intCount) {
         Arrays.checkOffsetAndCount(array.length, start, intCount);
-        IntBuffer buf = new ReadWriteIntArrayBuffer(array);
+        IntBuffer buf = new IntArrayBuffer(array);
         buf.position = start;
         buf.limit = start + intCount;
         return buf;
@@ -395,6 +395,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public IntBuffer put(int[] src, int srcOffset, int intCount) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         Arrays.checkOffsetAndCount(src.length, srcOffset, intCount);
         if (intCount > remaining()) {
             throw new BufferOverflowException();
@@ -422,6 +425,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public IntBuffer put(IntBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
diff --git a/luni/src/main/java/java/nio/LongArrayBuffer.java b/luni/src/main/java/java/nio/LongArrayBuffer.java
index 8dd0cfb..6419d73 100644
--- a/luni/src/main/java/java/nio/LongArrayBuffer.java
+++ b/luni/src/main/java/java/nio/LongArrayBuffer.java
@@ -18,69 +18,142 @@
 package java.nio;
 
 /**
- * LongArrayBuffer, ReadWriteLongArrayBuffer and ReadOnlyLongArrayBuffer compose
- * the implementation of array based long buffers.
- * <p>
- * LongArrayBuffer implements all the shared readonly methods and is extended by
- * the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
+ * LongArrayBuffer implements long[]-based LongBuffers.
  */
-abstract class LongArrayBuffer extends LongBuffer {
+final class LongArrayBuffer extends LongBuffer {
 
-    protected final long[] backingArray;
+  private final long[] backingArray;
 
-    protected final int offset;
+  private final int arrayOffset;
 
-    LongArrayBuffer(long[] array) {
-        this(array.length, array, 0);
+  private final boolean isReadOnly;
+
+  LongArrayBuffer(long[] array) {
+    this(array.length, array, 0, false);
+  }
+
+  private LongArrayBuffer(int capacity, long[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+  }
+
+  private static LongArrayBuffer copy(LongArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    LongArrayBuffer buf = new LongArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public LongBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+  @Override public LongBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    LongArrayBuffer(int capacity) {
-        this(capacity, new long[capacity], 0);
-    }
+  @Override public LongBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
 
-    LongArrayBuffer(int capacity, long[] backingArray, int offset) {
-        super(capacity);
-        this.backingArray = backingArray;
-        this.offset = offset;
-    }
+  @Override public LongBuffer slice() {
+    return new LongArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
 
-    @Override
-    public final long get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
-    }
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
 
-    @Override
-    public final long get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
+  @Override long[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return backingArray;
+  }
 
-    @Override
-    public final LongBuffer get(long[] dst, int dstOffset, int longCount) {
-        if (longCount > remaining()) {
-            throw new BufferUnderflowException();
-        }
-        System.arraycopy(backingArray, offset + position, dst, dstOffset, longCount);
-        position += longCount;
-        return this;
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return arrayOffset;
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return false;
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
     }
+    return true;
+  }
 
-    @Override
-    public final ByteOrder order() {
-        return ByteOrder.nativeOrder();
+  @Override public final long get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return backingArray[arrayOffset + position++];
+  }
+
+  @Override public final long get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
+
+  @Override public final LongBuffer get(long[] dst, int dstOffset, int longCount) {
+    if (longCount > remaining()) {
+      throw new BufferUnderflowException();
+    }
+    System.arraycopy(backingArray, arrayOffset + position, dst, dstOffset, longCount);
+    position += longCount;
+    return this;
+  }
+
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public final ByteOrder order() {
+    return ByteOrder.nativeOrder();
+  }
+
+  @Override public LongBuffer put(long c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (position == limit) {
+      throw new BufferOverflowException();
+    }
+    backingArray[arrayOffset + position++] = c;
+    return this;
+  }
+
+  @Override public LongBuffer put(int index, long c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = c;
+    return this;
+  }
+
+  @Override public LongBuffer put(long[] src, int srcOffset, int longCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (longCount > remaining()) {
+      throw new BufferOverflowException();
+    }
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, longCount);
+    position += longCount;
+    return this;
+  }
 
 }
diff --git a/luni/src/main/java/java/nio/LongBuffer.java b/luni/src/main/java/java/nio/LongBuffer.java
index 1254ddb..58d7518 100644
--- a/luni/src/main/java/java/nio/LongBuffer.java
+++ b/luni/src/main/java/java/nio/LongBuffer.java
@@ -48,7 +48,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteLongArrayBuffer(capacity);
+        return new LongArrayBuffer(new long[capacity]);
     }
 
     /**
@@ -85,7 +85,7 @@
      */
     public static LongBuffer wrap(long[] array, int start, int longCount) {
         Arrays.checkOffsetAndCount(array.length, start, longCount);
-        LongBuffer buf = new ReadWriteLongArrayBuffer(array);
+        LongBuffer buf = new LongArrayBuffer(array);
         buf.position = start;
         buf.limit = start + longCount;
         return buf;
@@ -426,6 +426,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public LongBuffer put(LongBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
diff --git a/luni/src/main/java/java/nio/MappedByteBuffer.java b/luni/src/main/java/java/nio/MappedByteBuffer.java
index 0e8bf09..2d44d0f 100644
--- a/luni/src/main/java/java/nio/MappedByteBuffer.java
+++ b/luni/src/main/java/java/nio/MappedByteBuffer.java
@@ -37,92 +37,88 @@
  * {@code MappedByteBuffer} is undefined.
  */
 public abstract class MappedByteBuffer extends ByteBuffer {
+  final MapMode mapMode;
 
-    final DirectByteBuffer wrapped;
+  MappedByteBuffer(MemoryBlock block, int capacity, MapMode mapMode) {
+    super(capacity, block);
+    this.mapMode = mapMode;
+  }
 
-    private final MapMode mapMode;
+  /**
+   * Returns true if there is a high probability that every page of this buffer is currently
+   * loaded in RAM, meaning that accesses will not cause a page fault. It is impossible to give
+   * a strong guarantee since this is only a snapshot of a dynamic situation.
+   */
+  public final boolean isLoaded() {
+    checkIsMapped();
 
-    MappedByteBuffer(ByteBuffer directBuffer) {
-        super(directBuffer.capacity, directBuffer.block);
-        if (!directBuffer.isDirect()) {
-            throw new IllegalArgumentException("directBuffer is not a direct buffer: " + directBuffer);
-        }
-        this.wrapped = (DirectByteBuffer) directBuffer;
-        this.mapMode = null;
+    long address = block.toLong();
+    long size = block.getSize();
+    if (size == 0) {
+      return true;
     }
 
-    MappedByteBuffer(MemoryBlock block, int capacity, int offset, MapMode mapMode) {
-        super(capacity, block);
-        this.mapMode = mapMode;
-        if (mapMode == MapMode.READ_ONLY) {
-            wrapped = new ReadOnlyDirectByteBuffer(block, capacity, offset);
-        } else {
-            wrapped = new ReadWriteDirectByteBuffer(block, capacity, offset);
+    try {
+      int pageSize = (int) Libcore.os.sysconf(_SC_PAGE_SIZE);
+      int pageOffset = (int) (address % pageSize);
+      address -= pageOffset;
+      size += pageOffset;
+      int pageCount = (int) ((size + pageSize - 1) / pageSize);
+      byte[] vector = new byte[pageCount];
+      Libcore.os.mincore(address, size, vector);
+      for (int i = 0; i < vector.length; ++i) {
+        if ((vector[i] & 1) != 1) {
+          return false;
         }
+      }
+      return true;
+    } catch (ErrnoException errnoException) {
+      return false;
     }
+  }
 
-    /**
-     * Returns true if there is a high probability that every page of this buffer is currently
-     * loaded in RAM, meaning that accesses will not cause a page fault. It is impossible to give
-     * a strong guarantee since this is only a snapshot of a dynamic situation.
-     */
-    public final boolean isLoaded() {
-        long address = block.toInt();
-        long size = block.getSize();
-        if (size == 0) {
-            return true;
-        }
+  /**
+   * Attempts to load every page of this buffer into RAM. See {@link #isLoaded}.
+   * @return this buffer.
+   */
+  public final MappedByteBuffer load() {
+    checkIsMapped();
 
-        try {
-            int pageSize = (int) Libcore.os.sysconf(_SC_PAGE_SIZE);
-            int pageOffset = (int) (address % pageSize);
-            address -= pageOffset;
-            size += pageOffset;
-            int pageCount = (int) ((size + pageSize - 1) / pageSize);
-            byte[] vector = new byte[pageCount];
-            Libcore.os.mincore(address, size, vector);
-            for (int i = 0; i < vector.length; ++i) {
-                if ((vector[i] & 1) != 1) {
-                    return false;
-                }
-            }
-            return true;
-        } catch (ErrnoException errnoException) {
-            return false;
-        }
+    try {
+      Libcore.os.mlock(block.toLong(), block.getSize());
+      Libcore.os.munlock(block.toLong(), block.getSize());
+    } catch (ErrnoException ignored) {
     }
+    return this;
+  }
 
-    /**
-     * Attempts to load every page of this buffer into RAM. See {@link #isLoaded}.
-     * @return this buffer.
-     */
-    public final MappedByteBuffer load() {
-        try {
-            Libcore.os.mlock(block.toInt(), block.getSize());
-            Libcore.os.munlock(block.toInt(), block.getSize());
-        } catch (ErrnoException ignored) {
-        }
-        return this;
-    }
+  /**
+   * Flushes changes made to the in-memory buffer back to the mapped file.
+   * Unless you call this, changes may not be written back until the finalizer
+   * runs. This method waits for the write to complete before returning.
+   *
+   * @return this buffer.
+   */
+  public final MappedByteBuffer force() {
+    checkIsMapped();
 
-    /**
-     * Writes all changes of the buffer to the mapped file. If the mapped file
-     * is stored on a local device, it is guaranteed that the changes are
-     * written to the file. No such guarantee is given if the file is located on
-     * a remote device.
-     *
-     * @return this buffer.
-     */
-    public final MappedByteBuffer force() {
-        if (mapMode == MapMode.READ_WRITE) {
-            try {
-                Libcore.os.msync(block.toInt(), block.getSize(), MS_SYNC);
-            } catch (ErrnoException errnoException) {
-                // The RI doesn't throw, presumably on the assumption that you can't get into
-                // a state where msync(2) could return an error.
-                throw new AssertionError(errnoException);
-            }
-        }
-        return this;
+    if (mapMode == MapMode.READ_WRITE) {
+      try {
+        Libcore.os.msync(block.toLong(), block.getSize(), MS_SYNC);
+      } catch (ErrnoException errnoException) {
+        // The RI doesn't throw, presumably on the assumption that you can't get into
+        // a state where msync(2) could return an error.
+        throw new AssertionError(errnoException);
+      }
     }
+    return this;
+  }
+
+  // DirectByteBuffer is a subclass of MappedByteBuffer, but not all DirectByteBuffers
+  // actually correspond to an mmap(2)ed region.
+  private void checkIsMapped() {
+    if (mapMode == null) {
+      throw new UnsupportedOperationException();
+    }
+  }
 }
diff --git a/luni/src/main/java/java/nio/MappedByteBufferAdapter.java b/luni/src/main/java/java/nio/MappedByteBufferAdapter.java
deleted file mode 100644
index 59cfe8e..0000000
--- a/luni/src/main/java/java/nio/MappedByteBufferAdapter.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-import java.nio.channels.FileChannel.MapMode;
-import libcore.io.SizeOf;
-
-/**
- * Rather than duplicate all the code from ReadOnlyDirectByteBuffer and
- * ReadWriteDirectByteBuffer (and their superclasses), we delegate to one or the other.
- * The tricky part is that we need to keep our fields in sync with our delegate's fields.
- * There are lots of methods that access the fields directly.
- *
- * The main consequences of this implementation are:
- *
- * 1. we need to explicitly call wrapped.position(int) before any operation on our delegate
- * that makes use of the implicit position.
- *
- * 2. we need to explicitly update position after any operation on our delegate that makes
- * use of the implicit position.
- *
- * This means that, even more than usual, the implicit iteration
- * operations are more expensive than the indexed operations.
- *
- * But we save a ton of code, for classes that no-one really uses because the API's broken
- * by design (disallowing munmap(2) calls). Internally, we can use libcore.io.MemoryMappedFile
- * as a high-performance and more usable replacement for MappedByteBuffer.
- *
- * FIXME: harmony changed their implementation after we diverged, switching to a scheme
- * where DirectByteBuffer extends MappedByteBuffer and this class doesn't exist. That's
- * much better than their original implementation, fossilized here.
- */
-final class MappedByteBufferAdapter extends MappedByteBuffer {
-    private MappedByteBufferAdapter(ByteBuffer buffer) {
-        super(buffer);
-        effectiveDirectAddress = wrapped.effectiveDirectAddress;
-    }
-
-    public MappedByteBufferAdapter(MemoryBlock block, int capacity, int offset, MapMode mode) {
-        super(block, capacity, offset, mode);
-        effectiveDirectAddress = wrapped.effectiveDirectAddress;
-    }
-
-    @Override void limitImpl(int newLimit) {
-        super.limitImpl(newLimit);
-        wrapped.limit(newLimit);
-    }
-
-    @Override void positionImpl(int newPosition) {
-        super.positionImpl(newPosition);
-        wrapped.position(newPosition);
-    }
-
-    @Override
-    public CharBuffer asCharBuffer() {
-        return wrapped.asCharBuffer();
-    }
-
-    @Override
-    public DoubleBuffer asDoubleBuffer() {
-        return wrapped.asDoubleBuffer();
-    }
-
-    @Override
-    public FloatBuffer asFloatBuffer() {
-        return wrapped.asFloatBuffer();
-    }
-
-    @Override
-    public IntBuffer asIntBuffer() {
-        return wrapped.asIntBuffer();
-    }
-
-    @Override
-    public LongBuffer asLongBuffer() {
-        return wrapped.asLongBuffer();
-    }
-
-    @Override
-    public ByteBuffer asReadOnlyBuffer() {
-        MappedByteBufferAdapter result = new MappedByteBufferAdapter(wrapped.asReadOnlyBuffer());
-        result.limit(limit);
-        result.position(position);
-        result.mark = mark;
-        return result;
-    }
-
-    @Override
-    public ShortBuffer asShortBuffer() {
-        return wrapped.asShortBuffer();
-    }
-
-    @Override
-    public ByteBuffer compact() {
-        if (wrapped.isReadOnly()) {
-            throw new ReadOnlyBufferException();
-        }
-        wrapped.compact();
-        limit(capacity);
-        position(wrapped.position());
-        this.mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer duplicate() {
-        MappedByteBufferAdapter result = new MappedByteBufferAdapter(wrapped.duplicate());
-        result.limit(limit);
-        result.position(position);
-        result.mark = mark;
-        return result;
-    }
-
-    @Override
-    public byte get() {
-        wrapped.position(position);
-        byte result = wrapped.get();
-        ++position;
-        return result;
-    }
-
-    @Override
-    public byte get(int index) {
-        return wrapped.get(index);
-    }
-
-    @Override
-    public ByteBuffer get(byte[] dst, int dstOffset, int byteCount) {
-        ByteBuffer result = wrapped.get(dst, dstOffset, byteCount);
-        position += byteCount;
-        return result;
-    }
-
-    @Override
-    public char getChar() {
-        wrapped.position(position);
-        char result = wrapped.getChar();
-        position += SizeOf.CHAR;
-        return result;
-    }
-
-    @Override
-    public char getChar(int index) {
-        return wrapped.getChar(index);
-    }
-
-    @Override
-    public double getDouble() {
-        wrapped.position(position);
-        double result = wrapped.getDouble();
-        position += SizeOf.DOUBLE;
-        return result;
-    }
-
-    @Override
-    public double getDouble(int index) {
-        return wrapped.getDouble(index);
-    }
-
-    @Override
-    public float getFloat() {
-        wrapped.position(position);
-        float result = wrapped.getFloat();
-        position += SizeOf.FLOAT;
-        return result;
-    }
-
-    @Override
-    public float getFloat(int index) {
-        return wrapped.getFloat(index);
-    }
-
-    @Override
-    public int getInt() {
-        wrapped.position(position);
-        int result = wrapped.getInt();
-        position += SizeOf.INT;
-        return result;
-    }
-
-    @Override
-    public int getInt(int index) {
-        return wrapped.getInt(index);
-    }
-
-    @Override
-    public long getLong() {
-        wrapped.position(position);
-        long result = wrapped.getLong();
-        position += SizeOf.LONG;
-        return result;
-    }
-
-    @Override
-    public long getLong(int index) {
-        return wrapped.getLong(index);
-    }
-
-    @Override
-    public short getShort() {
-        wrapped.position(position);
-        short result = wrapped.getShort();
-        position += SizeOf.SHORT;
-        return result;
-    }
-
-    @Override
-    public short getShort(int index) {
-        return wrapped.getShort(index);
-    }
-
-    @Override
-    public boolean isDirect() {
-        return true;
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return wrapped.isReadOnly();
-    }
-
-    @Override void orderImpl(ByteOrder byteOrder) {
-        super.orderImpl(byteOrder);
-        wrapped.order(byteOrder);
-    }
-
-    @Override
-    public ByteBuffer put(byte b) {
-        wrapped.position(this.position);
-        wrapped.put(b);
-        this.position++;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
-        wrapped.position(this.position);
-        wrapped.put(src, srcOffset, byteCount);
-        this.position += byteCount;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer put(int index, byte b) {
-        wrapped.position(this.position);
-        wrapped.put(index, b);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putChar(char value) {
-        wrapped.position(this.position);
-        wrapped.putChar(value);
-        this.position += SizeOf.CHAR;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putChar(int index, char value) {
-        wrapped.position(this.position);
-        wrapped.putChar(index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putDouble(double value) {
-        wrapped.position(this.position);
-        wrapped.putDouble(value);
-        this.position += SizeOf.DOUBLE;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putDouble(int index, double value) {
-        wrapped.position(this.position);
-        wrapped.putDouble(index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putFloat(float value) {
-        wrapped.position(this.position);
-        wrapped.putFloat(value);
-        this.position += SizeOf.FLOAT;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putFloat(int index, float value) {
-        wrapped.position(this.position);
-        wrapped.putFloat(index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putInt(int index, int value) {
-        wrapped.position(this.position);
-        wrapped.putInt(index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putInt(int value) {
-        wrapped.position(this.position);
-        wrapped.putInt(value);
-        this.position += SizeOf.INT;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putLong(int index, long value) {
-        wrapped.position(this.position);
-        wrapped.putLong(index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putLong(long value) {
-        wrapped.position(this.position);
-        wrapped.putLong(value);
-        this.position += SizeOf.LONG;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putShort(int index, short value) {
-        wrapped.position(this.position);
-        wrapped.putShort(index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putShort(short value) {
-        wrapped.position(this.position);
-        wrapped.putShort(value);
-        this.position += SizeOf.SHORT;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer slice() {
-        wrapped.position(this.position);
-        MappedByteBufferAdapter result = new MappedByteBufferAdapter(wrapped.slice());
-        wrapped.clear();
-        return result;
-    }
-
-    @Override byte[] protectedArray() {
-        return wrapped.protectedArray();
-    }
-
-    @Override int protectedArrayOffset() {
-        return wrapped.protectedArrayOffset();
-    }
-
-    @Override boolean protectedHasArray() {
-        return wrapped.protectedHasArray();
-    }
-
-    public final void free() {
-        wrapped.free();
-    }
-}
diff --git a/luni/src/main/java/java/nio/MemoryBlock.java b/luni/src/main/java/java/nio/MemoryBlock.java
index add69c5..718b020 100644
--- a/luni/src/main/java/java/nio/MemoryBlock.java
+++ b/luni/src/main/java/java/nio/MemoryBlock.java
@@ -31,7 +31,7 @@
      * Handles calling munmap(2) on a memory-mapped region.
      */
     private static class MemoryMappedBlock extends MemoryBlock {
-        private MemoryMappedBlock(int address, long byteCount) {
+        private MemoryMappedBlock(long address, long byteCount) {
             super(address, byteCount);
         }
 
@@ -63,7 +63,7 @@
     private static class NonMovableHeapBlock extends MemoryBlock {
         private byte[] array;
 
-        private NonMovableHeapBlock(byte[] array, int address, long byteCount) {
+        private NonMovableHeapBlock(byte[] array, long address, long byteCount) {
             super(address, byteCount);
             this.array = array;
         }
@@ -83,13 +83,12 @@
      * to direct buffers created by the JNI NewDirectByteBuffer function.)
      */
     private static class UnmanagedBlock extends MemoryBlock {
-        private UnmanagedBlock(int address, long byteCount) {
+        private UnmanagedBlock(long address, long byteCount) {
             super(address, byteCount);
         }
     }
 
-    // TODO: should be long on 64-bit devices; int for performance.
-    protected int address;
+    protected long address;
     protected final long size;
 
     public static MemoryBlock mmap(FileDescriptor fd, long offset, long size, MapMode mapMode) throws IOException {
@@ -114,7 +113,7 @@
             flags = MAP_SHARED;
         }
         try {
-            int address = (int) Libcore.os.mmap(0L, size, prot, flags, fd, offset);
+            long address = Libcore.os.mmap(0L, size, prot, flags, fd, offset);
             return new MemoryMappedBlock(address, size);
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
@@ -124,15 +123,15 @@
     public static MemoryBlock allocate(int byteCount) {
         VMRuntime runtime = VMRuntime.getRuntime();
         byte[] array = (byte[]) runtime.newNonMovableArray(byte.class, byteCount);
-        int address = (int) runtime.addressOf(array);
+        long address = runtime.addressOf(array);
         return new NonMovableHeapBlock(array, address, byteCount);
     }
 
-    public static MemoryBlock wrapFromJni(int address, long byteCount) {
+    public static MemoryBlock wrapFromJni(long address, long byteCount) {
         return new UnmanagedBlock(address, byteCount);
     }
 
-    private MemoryBlock(int address, long size) {
+    private MemoryBlock(long address, long size) {
         this.address = address;
         this.size = size;
     }
@@ -233,7 +232,7 @@
         return Memory.peekLong(address + offset, order.needsSwap);
     }
 
-    public final int toInt() {
+    public final long toLong() {
         return address;
     }
 
diff --git a/luni/src/main/java/java/nio/NIOAccess.java b/luni/src/main/java/java/nio/NIOAccess.java
index 36c41cf..12af44d 100644
--- a/luni/src/main/java/java/nio/NIOAccess.java
+++ b/luni/src/main/java/java/nio/NIOAccess.java
@@ -33,7 +33,7 @@
      * position, or 0 if there is none
      */
     static long getBasePointer(Buffer b) {
-        int address = b.effectiveDirectAddress;
+        long address = b.effectiveDirectAddress;
         if (address == 0) {
             return 0L;
         }
diff --git a/luni/src/main/java/java/nio/NioUtils.java b/luni/src/main/java/java/nio/NioUtils.java
index 56b8b8b..a1a46b6 100644
--- a/luni/src/main/java/java/nio/NioUtils.java
+++ b/luni/src/main/java/java/nio/NioUtils.java
@@ -26,36 +26,11 @@
     private NioUtils() {
     }
 
-    /**
-     * Gets the start address of a direct buffer.
-     * <p>
-     * This method corresponds to the JNI function:
-     *
-     * <pre>
-     *    void* GetDirectBufferAddress(JNIEnv* env, jobject buf);
-     * </pre>
-     *
-     * @param buf
-     *            the direct buffer whose address shall be returned must not be
-     *            <code>null</code>.
-     * @return the address of the buffer given, or zero if the buffer is not a
-     *         direct Buffer.
-     */
-    public static int getDirectBufferAddress(Buffer buffer) {
-        return buffer.effectiveDirectAddress;
-    }
-
     public static void freeDirectBuffer(ByteBuffer buffer) {
         if (buffer == null) {
             return;
         }
-        if (buffer instanceof DirectByteBuffer) {
-            ((DirectByteBuffer) buffer).free();
-        } else if (buffer instanceof MappedByteBuffer) {
-            ((MappedByteBufferAdapter) buffer).free();
-        } else {
-            throw new AssertionError();
-        }
+        ((DirectByteBuffer) buffer).free();
     }
 
     /**
@@ -77,7 +52,7 @@
      * Normally, attempting to access the array backing a read-only buffer throws.
      */
     public static byte[] unsafeArray(ByteBuffer b) {
-        return ((HeapByteBuffer) b).backingArray;
+        return ((ByteArrayBuffer) b).backingArray;
     }
 
     /**
@@ -85,6 +60,6 @@
      * even if the ByteBuffer is read-only.
      */
     public static int unsafeArrayOffset(ByteBuffer b) {
-        return ((HeapByteBuffer) b).offset;
+        return ((ByteArrayBuffer) b).arrayOffset;
     }
 }
diff --git a/luni/src/main/java/java/nio/ReadOnlyCharArrayBuffer.java b/luni/src/main/java/java/nio/ReadOnlyCharArrayBuffer.java
deleted file mode 100644
index 75e7b9b..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyCharArrayBuffer.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * CharArrayBuffer, ReadWriteCharArrayBuffer and ReadOnlyCharArrayBuffer compose
- * the implementation of array based char buffers.
- * <p>
- * ReadOnlyCharArrayBuffer extends CharArrayBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyCharArrayBuffer extends CharArrayBuffer {
-
-    static ReadOnlyCharArrayBuffer copy(CharArrayBuffer other, int markOfOther) {
-        ReadOnlyCharArrayBuffer buf =
-                new ReadOnlyCharArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyCharArrayBuffer(int capacity, char[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public CharBuffer asReadOnlyBuffer() {
-        return duplicate();
-    }
-
-    @Override
-    public CharBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public CharBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override char[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public CharBuffer put(char c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public CharBuffer put(int index, char c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final CharBuffer put(char[] src, int srcOffset, int charCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final CharBuffer put(CharBuffer src) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public CharBuffer put(String src, int start, int end) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public CharBuffer slice() {
-        return new ReadOnlyCharArrayBuffer(remaining(), backingArray, offset + position);
-    }
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyDirectByteBuffer.java b/luni/src/main/java/java/nio/ReadOnlyDirectByteBuffer.java
deleted file mode 100644
index 0d61797..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyDirectByteBuffer.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * DirectByteBuffer, ReadWriteDirectByteBuffer and ReadOnlyDirectByteBuffer
- * compose the implementation of platform memory based byte buffers.
- * <p>
- * ReadOnlyDirectByteBuffer extends DirectByteBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- */
-final class ReadOnlyDirectByteBuffer extends DirectByteBuffer {
-    static ReadOnlyDirectByteBuffer copy(DirectByteBuffer other, int markOfOther) {
-        ReadOnlyDirectByteBuffer buf = new ReadOnlyDirectByteBuffer(other.block, other.capacity(), other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    protected ReadOnlyDirectByteBuffer(MemoryBlock block, int capacity, int offset) {
-        super(block, capacity, offset);
-    }
-
-    @Override
-    public ByteBuffer asReadOnlyBuffer() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public ByteBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override
-    public ByteBuffer put(byte value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer put(int index, byte value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putDouble(double value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putDouble(int index, double value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putFloat(float value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putFloat(int index, float value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putInt(int value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putInt(int index, int value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putLong(int index, long value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putLong(long value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putShort(int index, short value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putShort(short value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer put(ByteBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer slice() {
-        return new ReadOnlyDirectByteBuffer(block, remaining(), offset + position);
-    }
-
-    @Override protected byte[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override protected int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override protected boolean protectedHasArray() {
-        return false;
-    }
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyDoubleArrayBuffer.java b/luni/src/main/java/java/nio/ReadOnlyDoubleArrayBuffer.java
deleted file mode 100644
index 9812789..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyDoubleArrayBuffer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * DoubleArrayBuffer, ReadWriteDoubleArrayBuffer and ReadOnlyDoubleArrayBuffer
- * compose the implementation of array based double buffers.
- * <p>
- * ReadOnlyDoubleArrayBuffer extends DoubleArrayBuffer with all the write
- * methods throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyDoubleArrayBuffer extends DoubleArrayBuffer {
-
-    static ReadOnlyDoubleArrayBuffer copy(DoubleArrayBuffer other, int markOfOther) {
-        ReadOnlyDoubleArrayBuffer buf =
-                new ReadOnlyDoubleArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyDoubleArrayBuffer(int capacity, double[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public DoubleBuffer asReadOnlyBuffer() {
-        return duplicate();
-    }
-
-    @Override
-    public DoubleBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public DoubleBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override double[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public DoubleBuffer put(double c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public DoubleBuffer put(int index, double c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final DoubleBuffer put(double[] src, int srcOffset, int byteCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final DoubleBuffer put(DoubleBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public DoubleBuffer slice() {
-        return new ReadOnlyDoubleArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyFloatArrayBuffer.java b/luni/src/main/java/java/nio/ReadOnlyFloatArrayBuffer.java
deleted file mode 100644
index 3c74966..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyFloatArrayBuffer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * FloatArrayBuffer, ReadWriteFloatArrayBuffer and ReadOnlyFloatArrayBuffer
- * compose the implementation of array based float buffers.
- * <p>
- * ReadOnlyFloatArrayBuffer extends FloatArrayBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyFloatArrayBuffer extends FloatArrayBuffer {
-
-    static ReadOnlyFloatArrayBuffer copy(FloatArrayBuffer other, int markOfOther) {
-        ReadOnlyFloatArrayBuffer buf =
-                new ReadOnlyFloatArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyFloatArrayBuffer(int capacity, float[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public FloatBuffer asReadOnlyBuffer() {
-        return duplicate();
-    }
-
-    @Override
-    public FloatBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public FloatBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override float[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public FloatBuffer put(float c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public FloatBuffer put(int index, float c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public FloatBuffer put(FloatBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final FloatBuffer put(float[] src, int srcOffset, int byteCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public FloatBuffer slice() {
-        return new ReadOnlyFloatArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyHeapByteBuffer.java b/luni/src/main/java/java/nio/ReadOnlyHeapByteBuffer.java
deleted file mode 100644
index c5073b0..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyHeapByteBuffer.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * HeapByteBuffer, ReadWriteHeapByteBuffer and ReadOnlyHeapByteBuffer compose
- * the implementation of array based byte buffers.
- * <p>
- * ReadOnlyHeapByteBuffer extends HeapByteBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyHeapByteBuffer extends HeapByteBuffer {
-
-    static ReadOnlyHeapByteBuffer copy(HeapByteBuffer other, int markOfOther) {
-        ReadOnlyHeapByteBuffer buf =
-                new ReadOnlyHeapByteBuffer(other.backingArray, other.capacity(), other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyHeapByteBuffer(byte[] backingArray, int capacity, int arrayOffset) {
-        super(backingArray, capacity, arrayOffset);
-    }
-
-    @Override
-    public ByteBuffer asReadOnlyBuffer() {
-        return ReadOnlyHeapByteBuffer.copy(this, mark);
-    }
-
-    @Override
-    public ByteBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override byte[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public ByteBuffer put(byte b) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer put(int index, byte b) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putDouble(double value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putDouble(int index, double value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putFloat(float value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putFloat(int index, float value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putInt(int value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putInt(int index, int value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putLong(int index, long value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putLong(long value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putShort(int index, short value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer putShort(short value) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer put(ByteBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ByteBuffer slice() {
-        return new ReadOnlyHeapByteBuffer(backingArray, remaining(), offset + position);
-    }
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyIntArrayBuffer.java b/luni/src/main/java/java/nio/ReadOnlyIntArrayBuffer.java
deleted file mode 100644
index ef73251..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyIntArrayBuffer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * IntArrayBuffer, ReadWriteIntArrayBuffer and ReadOnlyIntArrayBuffer compose
- * the implementation of array based int buffers.
- * <p>
- * ReadOnlyIntArrayBuffer extends IntArrayBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyIntArrayBuffer extends IntArrayBuffer {
-
-    static ReadOnlyIntArrayBuffer copy(IntArrayBuffer other, int markOfOther) {
-        ReadOnlyIntArrayBuffer buf =
-                new ReadOnlyIntArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyIntArrayBuffer(int capacity, int[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public IntBuffer asReadOnlyBuffer() {
-        return duplicate();
-    }
-
-    @Override
-    public IntBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public IntBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override int[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public IntBuffer put(int c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public IntBuffer put(int index, int c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public IntBuffer put(IntBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final IntBuffer put(int[] src, int srcOffset, int intCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public IntBuffer slice() {
-        return new ReadOnlyIntArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyLongArrayBuffer.java b/luni/src/main/java/java/nio/ReadOnlyLongArrayBuffer.java
deleted file mode 100644
index a28815d..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyLongArrayBuffer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * LongArrayBuffer, ReadWriteLongArrayBuffer and ReadOnlyLongArrayBuffer compose
- * the implementation of array based long buffers.
- * <p>
- * ReadOnlyLongArrayBuffer extends LongArrayBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyLongArrayBuffer extends LongArrayBuffer {
-
-    static ReadOnlyLongArrayBuffer copy(LongArrayBuffer other, int markOfOther) {
-        ReadOnlyLongArrayBuffer buf =
-                new ReadOnlyLongArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyLongArrayBuffer(int capacity, long[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public LongBuffer asReadOnlyBuffer() {
-        return duplicate();
-    }
-
-    @Override
-    public LongBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public LongBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override long[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public LongBuffer put(long c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public LongBuffer put(int index, long c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public LongBuffer put(LongBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final LongBuffer put(long[] src, int srcOffset, int longCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public LongBuffer slice() {
-        return new ReadOnlyLongArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadOnlyShortArrayBuffer.java b/luni/src/main/java/java/nio/ReadOnlyShortArrayBuffer.java
deleted file mode 100644
index 075ff38..0000000
--- a/luni/src/main/java/java/nio/ReadOnlyShortArrayBuffer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * ShortArrayBuffer, ReadWriteShortArrayBuffer and ReadOnlyShortArrayBuffer
- * compose the implementation of array based short buffers.
- * <p>
- * ReadOnlyShortArrayBuffer extends ShortArrayBuffer with all the write methods
- * throwing read only exception.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadOnlyShortArrayBuffer extends ShortArrayBuffer {
-
-    static ReadOnlyShortArrayBuffer copy(ShortArrayBuffer other, int markOfOther) {
-        ReadOnlyShortArrayBuffer buf =
-                new ReadOnlyShortArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadOnlyShortArrayBuffer(int capacity, short[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public ShortBuffer asReadOnlyBuffer() {
-        return duplicate();
-    }
-
-    @Override
-    public ShortBuffer compact() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ShortBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return true;
-    }
-
-    @Override short[] protectedArray() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override int protectedArrayOffset() {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override boolean protectedHasArray() {
-        return false;
-    }
-
-    @Override
-    public ShortBuffer put(ShortBuffer buf) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ShortBuffer put(short c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ShortBuffer put(int index, short c) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public final ShortBuffer put(short[] src, int srcOffset, int shortCount) {
-        throw new ReadOnlyBufferException();
-    }
-
-    @Override
-    public ShortBuffer slice() {
-        return new ReadOnlyShortArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteCharArrayBuffer.java b/luni/src/main/java/java/nio/ReadWriteCharArrayBuffer.java
deleted file mode 100644
index 58fc5ae..0000000
--- a/luni/src/main/java/java/nio/ReadWriteCharArrayBuffer.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * CharArrayBuffer, ReadWriteCharArrayBuffer and ReadOnlyCharArrayBuffer compose
- * the implementation of array based char buffers.
- * <p>
- * ReadWriteCharArrayBuffer extends CharArrayBuffer with all the write methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteCharArrayBuffer extends CharArrayBuffer {
-
-    static ReadWriteCharArrayBuffer copy(CharArrayBuffer other, int markOfOther) {
-        ReadWriteCharArrayBuffer buf =
-                new ReadWriteCharArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteCharArrayBuffer(char[] array) {
-        super(array);
-    }
-
-    ReadWriteCharArrayBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteCharArrayBuffer(int capacity, char[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public CharBuffer asReadOnlyBuffer() {
-        return ReadOnlyCharArrayBuffer.copy(this, mark);
-    }
-
-    @Override
-    public CharBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public CharBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override char[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public CharBuffer put(char c) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = c;
-        return this;
-    }
-
-    @Override
-    public CharBuffer put(int index, char c) {
-        checkIndex(index);
-        backingArray[offset + index] = c;
-        return this;
-    }
-
-    @Override
-    public CharBuffer put(char[] src, int srcOffset, int charCount) {
-        if (charCount > remaining()) {
-            throw new BufferOverflowException();
-        }
-        System.arraycopy(src, srcOffset, backingArray, offset + position, charCount);
-        position += charCount;
-        return this;
-    }
-
-    @Override
-    public CharBuffer slice() {
-        return new ReadWriteCharArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java b/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
deleted file mode 100644
index 5edce30..0000000
--- a/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-import libcore.io.SizeOf;
-import libcore.io.Memory;
-
-/**
- * DirectByteBuffer, ReadWriteDirectByteBuffer and ReadOnlyDirectByteBuffer
- * compose the implementation of platform memory based byte buffers.
- * <p>
- * ReadWriteDirectByteBuffer extends DirectByteBuffer with all the write
- * methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- */
-final class ReadWriteDirectByteBuffer extends DirectByteBuffer {
-    static ReadWriteDirectByteBuffer copy(DirectByteBuffer other, int markOfOther) {
-        ReadWriteDirectByteBuffer buf =
-                new ReadWriteDirectByteBuffer(other.block, other.capacity(), other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    // Used by ByteBuffer.allocateDirect.
-    ReadWriteDirectByteBuffer(int capacity) {
-        super(MemoryBlock.allocate(capacity), capacity, 0);
-    }
-
-    // Used by the JNI NewDirectByteBuffer function.
-    ReadWriteDirectByteBuffer(int address, int capacity) {
-        super(MemoryBlock.wrapFromJni(address, capacity), capacity, 0);
-    }
-
-    ReadWriteDirectByteBuffer(MemoryBlock block, int capacity, int offset) {
-        super(block, capacity, offset);
-    }
-
-    @Override
-    public ByteBuffer asReadOnlyBuffer() {
-        return ReadOnlyDirectByteBuffer.copy(this, mark);
-    }
-
-    @Override
-    public ByteBuffer compact() {
-        Memory.memmove(this, 0, this, position, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override
-    public ByteBuffer put(byte value) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeByte(offset + position++, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer put(int index, byte value) {
-        checkIndex(index);
-        this.block.pokeByte(offset + index, value);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
-        checkPutBounds(1, src.length, srcOffset, byteCount);
-        this.block.pokeByteArray(offset + position, src, srcOffset, byteCount);
-        position += byteCount;
-        return this;
-    }
-
-    final void put(char[] src, int srcOffset, int charCount) {
-        int byteCount = checkPutBounds(SizeOf.CHAR, src.length, srcOffset, charCount);
-        this.block.pokeCharArray(offset + position, src, srcOffset, charCount, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(double[] src, int srcOffset, int doubleCount) {
-        int byteCount = checkPutBounds(SizeOf.DOUBLE, src.length, srcOffset, doubleCount);
-        this.block.pokeDoubleArray(offset + position, src, srcOffset, doubleCount, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(float[] src, int srcOffset, int floatCount) {
-        int byteCount = checkPutBounds(SizeOf.FLOAT, src.length, srcOffset, floatCount);
-        this.block.pokeFloatArray(offset + position, src, srcOffset, floatCount, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(int[] src, int srcOffset, int intCount) {
-        int byteCount = checkPutBounds(SizeOf.INT, src.length, srcOffset, intCount);
-        this.block.pokeIntArray(offset + position, src, srcOffset, intCount, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(long[] src, int srcOffset, int longCount) {
-        int byteCount = checkPutBounds(SizeOf.LONG, src.length, srcOffset, longCount);
-        this.block.pokeLongArray(offset + position, src, srcOffset, longCount, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(short[] src, int srcOffset, int shortCount) {
-        int byteCount = checkPutBounds(SizeOf.SHORT, src.length, srcOffset, shortCount);
-        this.block.pokeShortArray(offset + position, src, srcOffset, shortCount, order.needsSwap);
-        position += byteCount;
-    }
-
-    @Override
-    public ByteBuffer putChar(char value) {
-        int newPosition = position + SizeOf.CHAR;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeShort(offset + position, (short) value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putChar(int index, char value) {
-        checkIndex(index, SizeOf.CHAR);
-        this.block.pokeShort(offset + index, (short) value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putDouble(double value) {
-        int newPosition = position + SizeOf.DOUBLE;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeLong(offset + position, Double.doubleToRawLongBits(value), order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putDouble(int index, double value) {
-        checkIndex(index, SizeOf.DOUBLE);
-        this.block.pokeLong(offset + index, Double.doubleToRawLongBits(value), order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putFloat(float value) {
-        int newPosition = position + SizeOf.FLOAT;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeInt(offset + position, Float.floatToRawIntBits(value), order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putFloat(int index, float value) {
-        checkIndex(index, SizeOf.FLOAT);
-        this.block.pokeInt(offset + index, Float.floatToRawIntBits(value), order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putInt(int value) {
-        int newPosition = position + SizeOf.INT;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeInt(offset + position, value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putInt(int index, int value) {
-        checkIndex(index, SizeOf.INT);
-        this.block.pokeInt(offset + index, value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putLong(long value) {
-        int newPosition = position + SizeOf.LONG;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeLong(offset + position, value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putLong(int index, long value) {
-        checkIndex(index, SizeOf.LONG);
-        this.block.pokeLong(offset + index, value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putShort(short value) {
-        int newPosition = position + SizeOf.SHORT;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        this.block.pokeShort(offset + position, value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putShort(int index, short value) {
-        checkIndex(index, SizeOf.SHORT);
-        this.block.pokeShort(offset + index, value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer slice() {
-        return new ReadWriteDirectByteBuffer(block, remaining(), offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteDoubleArrayBuffer.java b/luni/src/main/java/java/nio/ReadWriteDoubleArrayBuffer.java
deleted file mode 100644
index f9328d6..0000000
--- a/luni/src/main/java/java/nio/ReadWriteDoubleArrayBuffer.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * DoubleArrayBuffer, ReadWriteDoubleArrayBuffer and ReadOnlyDoubleArrayBuffer
- * compose the implementation of array based double buffers.
- * <p>
- * ReadWriteDoubleArrayBuffer extends DoubleArrayBuffer with all the write
- * methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteDoubleArrayBuffer extends DoubleArrayBuffer {
-
-    static ReadWriteDoubleArrayBuffer copy(DoubleArrayBuffer other, int markOfOther) {
-        ReadWriteDoubleArrayBuffer buf =
-                new ReadWriteDoubleArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteDoubleArrayBuffer(double[] array) {
-        super(array);
-    }
-
-    ReadWriteDoubleArrayBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteDoubleArrayBuffer(int capacity, double[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public DoubleBuffer asReadOnlyBuffer() {
-        return ReadOnlyDoubleArrayBuffer.copy(this, mark);
-    }
-
-    @Override
-    public DoubleBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public DoubleBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override double[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public DoubleBuffer put(double c) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = c;
-        return this;
-    }
-
-    @Override
-    public DoubleBuffer put(int index, double c) {
-        checkIndex(index);
-        backingArray[offset + index] = c;
-        return this;
-    }
-
-    @Override
-    public DoubleBuffer put(double[] src, int srcOffset, int doubleCount) {
-        if (doubleCount > remaining()) {
-            throw new BufferOverflowException();
-        }
-        System.arraycopy(src, srcOffset, backingArray, offset + position, doubleCount);
-        position += doubleCount;
-        return this;
-    }
-
-    @Override
-    public DoubleBuffer slice() {
-        return new ReadWriteDoubleArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteFloatArrayBuffer.java b/luni/src/main/java/java/nio/ReadWriteFloatArrayBuffer.java
deleted file mode 100644
index eee3aa7..0000000
--- a/luni/src/main/java/java/nio/ReadWriteFloatArrayBuffer.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * FloatArrayBuffer, ReadWriteFloatArrayBuffer and ReadOnlyFloatArrayBuffer
- * compose the implementation of array based float buffers.
- * <p>
- * ReadWriteFloatArrayBuffer extends FloatArrayBuffer with all the write
- * methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteFloatArrayBuffer extends FloatArrayBuffer {
-
-    static ReadWriteFloatArrayBuffer copy(FloatArrayBuffer other, int markOfOther) {
-        ReadWriteFloatArrayBuffer buf =
-                new ReadWriteFloatArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteFloatArrayBuffer(float[] array) {
-        super(array);
-    }
-
-    ReadWriteFloatArrayBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteFloatArrayBuffer(int capacity, float[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public FloatBuffer asReadOnlyBuffer() {
-        return ReadOnlyFloatArrayBuffer.copy(this, mark);
-    }
-
-    @Override
-    public FloatBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public FloatBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override float[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public FloatBuffer put(float c) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = c;
-        return this;
-    }
-
-    @Override
-    public FloatBuffer put(int index, float c) {
-        checkIndex(index);
-        backingArray[offset + index] = c;
-        return this;
-    }
-
-    @Override
-    public FloatBuffer put(float[] src, int srcOffset, int floatCount) {
-        if (floatCount > remaining()) {
-            throw new BufferOverflowException();
-        }
-        System.arraycopy(src, srcOffset, backingArray, offset + position, floatCount);
-        position += floatCount;
-        return this;
-    }
-
-    @Override
-    public FloatBuffer slice() {
-        return new ReadWriteFloatArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteHeapByteBuffer.java b/luni/src/main/java/java/nio/ReadWriteHeapByteBuffer.java
deleted file mode 100644
index 02296c6..0000000
--- a/luni/src/main/java/java/nio/ReadWriteHeapByteBuffer.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package java.nio;
-
-import libcore.io.Memory;
-import libcore.io.SizeOf;
-
-/**
- * HeapByteBuffer, ReadWriteHeapByteBuffer and ReadOnlyHeapByteBuffer compose
- * the implementation of array based byte buffers.
- * <p>
- * ReadWriteHeapByteBuffer extends HeapByteBuffer with all the write methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteHeapByteBuffer extends HeapByteBuffer {
-
-    static ReadWriteHeapByteBuffer copy(HeapByteBuffer other, int markOfOther) {
-        ReadWriteHeapByteBuffer buf =
-                new ReadWriteHeapByteBuffer(other.backingArray, other.capacity(), other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteHeapByteBuffer(byte[] backingArray) {
-        super(backingArray);
-    }
-
-    ReadWriteHeapByteBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteHeapByteBuffer(byte[] backingArray, int capacity, int arrayOffset) {
-        super(backingArray, capacity, arrayOffset);
-    }
-
-    @Override
-    public ByteBuffer asReadOnlyBuffer() {
-        return ReadOnlyHeapByteBuffer.copy(this, mark);
-    }
-
-    @Override
-    public ByteBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override byte[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public ByteBuffer put(byte b) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = b;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer put(int index, byte b) {
-        checkIndex(index);
-        backingArray[offset + index] = b;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer put(byte[] src, int srcOffset, int byteCount) {
-        checkPutBounds(1, src.length, srcOffset, byteCount);
-        System.arraycopy(src, srcOffset, backingArray, offset + position, byteCount);
-        position += byteCount;
-        return this;
-    }
-
-    final void put(char[] src, int srcOffset, int charCount) {
-        int byteCount = checkPutBounds(SizeOf.CHAR, src.length, srcOffset, charCount);
-        Memory.unsafeBulkPut(backingArray, offset + position, byteCount, src, srcOffset, SizeOf.CHAR, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(double[] src, int srcOffset, int doubleCount) {
-        int byteCount = checkPutBounds(SizeOf.DOUBLE, src.length, srcOffset, doubleCount);
-        Memory.unsafeBulkPut(backingArray, offset + position, byteCount, src, srcOffset, SizeOf.DOUBLE, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(float[] src, int srcOffset, int floatCount) {
-        int byteCount = checkPutBounds(SizeOf.FLOAT, src.length, srcOffset, floatCount);
-        Memory.unsafeBulkPut(backingArray, offset + position, byteCount, src, srcOffset, SizeOf.FLOAT, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(int[] src, int srcOffset, int intCount) {
-        int byteCount = checkPutBounds(SizeOf.INT, src.length, srcOffset, intCount);
-        Memory.unsafeBulkPut(backingArray, offset + position, byteCount, src, srcOffset, SizeOf.INT, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(long[] src, int srcOffset, int longCount) {
-        int byteCount = checkPutBounds(SizeOf.LONG, src.length, srcOffset, longCount);
-        Memory.unsafeBulkPut(backingArray, offset + position, byteCount, src, srcOffset, SizeOf.LONG, order.needsSwap);
-        position += byteCount;
-    }
-
-    final void put(short[] src, int srcOffset, int shortCount) {
-        int byteCount = checkPutBounds(SizeOf.SHORT, src.length, srcOffset, shortCount);
-        Memory.unsafeBulkPut(backingArray, offset + position, byteCount, src, srcOffset, SizeOf.SHORT, order.needsSwap);
-        position += byteCount;
-    }
-
-    @Override
-    public ByteBuffer putChar(int index, char value) {
-        checkIndex(index, SizeOf.CHAR);
-        Memory.pokeShort(backingArray, offset + index, (short) value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putChar(char value) {
-        int newPosition = position + SizeOf.CHAR;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        Memory.pokeShort(backingArray, offset + position, (short) value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putDouble(double value) {
-        return putLong(Double.doubleToRawLongBits(value));
-    }
-
-    @Override
-    public ByteBuffer putDouble(int index, double value) {
-        return putLong(index, Double.doubleToRawLongBits(value));
-    }
-
-    @Override
-    public ByteBuffer putFloat(float value) {
-        return putInt(Float.floatToRawIntBits(value));
-    }
-
-    @Override
-    public ByteBuffer putFloat(int index, float value) {
-        return putInt(index, Float.floatToRawIntBits(value));
-    }
-
-    @Override
-    public ByteBuffer putInt(int value) {
-        int newPosition = position + SizeOf.INT;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        Memory.pokeInt(backingArray, offset + position, value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putInt(int index, int value) {
-        checkIndex(index, SizeOf.INT);
-        Memory.pokeInt(backingArray, offset + index, value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putLong(int index, long value) {
-        checkIndex(index, SizeOf.LONG);
-        Memory.pokeLong(backingArray, offset + index, value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putLong(long value) {
-        int newPosition = position + SizeOf.LONG;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        Memory.pokeLong(backingArray, offset + position, value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putShort(int index, short value) {
-        checkIndex(index, SizeOf.SHORT);
-        Memory.pokeShort(backingArray, offset + index, value, order);
-        return this;
-    }
-
-    @Override
-    public ByteBuffer putShort(short value) {
-        int newPosition = position + SizeOf.SHORT;
-        if (newPosition > limit) {
-            throw new BufferOverflowException();
-        }
-        Memory.pokeShort(backingArray, offset + position, value, order);
-        position = newPosition;
-        return this;
-    }
-
-    @Override
-    public ByteBuffer slice() {
-        return new ReadWriteHeapByteBuffer(backingArray, remaining(), offset + position);
-    }
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteIntArrayBuffer.java b/luni/src/main/java/java/nio/ReadWriteIntArrayBuffer.java
deleted file mode 100644
index e8e67c2..0000000
--- a/luni/src/main/java/java/nio/ReadWriteIntArrayBuffer.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * IntArrayBuffer, ReadWriteIntArrayBuffer and ReadOnlyIntArrayBuffer compose
- * the implementation of array based int buffers.
- * <p>
- * ReadWriteIntArrayBuffer extends IntArrayBuffer with all the write methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteIntArrayBuffer extends IntArrayBuffer {
-
-    static ReadWriteIntArrayBuffer copy(IntArrayBuffer other, int markOfOther) {
-        ReadWriteIntArrayBuffer buf =
-                new ReadWriteIntArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteIntArrayBuffer(int[] array) {
-        super(array);
-    }
-
-    ReadWriteIntArrayBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteIntArrayBuffer(int capacity, int[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public IntBuffer asReadOnlyBuffer() {
-        return ReadOnlyIntArrayBuffer.copy(this, mark);
-    }
-
-    @Override
-    public IntBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public IntBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override int[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public IntBuffer put(int c) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = c;
-        return this;
-    }
-
-    @Override
-    public IntBuffer put(int index, int c) {
-        checkIndex(index);
-        backingArray[offset + index] = c;
-        return this;
-    }
-
-    @Override
-    public IntBuffer put(int[] src, int srcOffset, int intCount) {
-        if (intCount > remaining()) {
-            throw new BufferOverflowException();
-        }
-        System.arraycopy(src, srcOffset, backingArray, offset + position, intCount);
-        position += intCount;
-        return this;
-    }
-
-    @Override
-    public IntBuffer slice() {
-        return new ReadWriteIntArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteLongArrayBuffer.java b/luni/src/main/java/java/nio/ReadWriteLongArrayBuffer.java
deleted file mode 100644
index df5f09f..0000000
--- a/luni/src/main/java/java/nio/ReadWriteLongArrayBuffer.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * LongArrayBuffer, ReadWriteLongArrayBuffer and ReadOnlyLongArrayBuffer compose
- * the implementation of array based long buffers.
- * <p>
- * ReadWriteLongArrayBuffer extends LongArrayBuffer with all the write methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteLongArrayBuffer extends LongArrayBuffer {
-
-    static ReadWriteLongArrayBuffer copy(LongArrayBuffer other, int markOfOther) {
-        ReadWriteLongArrayBuffer buf =
-                new ReadWriteLongArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteLongArrayBuffer(long[] array) {
-        super(array);
-    }
-
-    ReadWriteLongArrayBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteLongArrayBuffer(int capacity, long[] backingArray, int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public LongBuffer asReadOnlyBuffer() {
-        return ReadOnlyLongArrayBuffer.copy(this, mark);
-    }
-
-    @Override
-    public LongBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public LongBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override long[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public LongBuffer put(long c) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = c;
-        return this;
-    }
-
-    @Override
-    public LongBuffer put(int index, long c) {
-        checkIndex(index);
-        backingArray[offset + index] = c;
-        return this;
-    }
-
-    @Override
-    public LongBuffer put(long[] src, int srcOffset, int longCount) {
-        if (longCount > remaining()) {
-            throw new BufferOverflowException();
-        }
-        System.arraycopy(src, srcOffset, backingArray, offset + position, longCount);
-        position += longCount;
-        return this;
-    }
-
-    @Override
-    public LongBuffer slice() {
-        return new ReadWriteLongArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ReadWriteShortArrayBuffer.java b/luni/src/main/java/java/nio/ReadWriteShortArrayBuffer.java
deleted file mode 100644
index 2a43e91..0000000
--- a/luni/src/main/java/java/nio/ReadWriteShortArrayBuffer.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package java.nio;
-
-/**
- * ShortArrayBuffer, ReadWriteShortArrayBuffer and ReadOnlyShortArrayBuffer
- * compose the implementation of array based short buffers.
- * <p>
- * ReadWriteShortArrayBuffer extends ShortArrayBuffer with all the write
- * methods.
- * </p>
- * <p>
- * This class is marked final for runtime performance.
- * </p>
- *
- */
-final class ReadWriteShortArrayBuffer extends ShortArrayBuffer {
-
-    static ReadWriteShortArrayBuffer copy(ShortArrayBuffer other, int markOfOther) {
-        ReadWriteShortArrayBuffer buf =
-                new ReadWriteShortArrayBuffer(other.capacity(), other.backingArray, other.offset);
-        buf.limit = other.limit;
-        buf.position = other.position();
-        buf.mark = markOfOther;
-        return buf;
-    }
-
-    ReadWriteShortArrayBuffer(short[] array) {
-        super(array);
-    }
-
-    ReadWriteShortArrayBuffer(int capacity) {
-        super(capacity);
-    }
-
-    ReadWriteShortArrayBuffer(int capacity, short[] backingArray,
-            int arrayOffset) {
-        super(capacity, backingArray, arrayOffset);
-    }
-
-    @Override
-    public ShortBuffer asReadOnlyBuffer() {
-        return ReadOnlyShortArrayBuffer.copy(this, mark);
-    }
-
-    @Override
-    public ShortBuffer compact() {
-        System.arraycopy(backingArray, position + offset, backingArray, offset, remaining());
-        position = limit - position;
-        limit = capacity;
-        mark = UNSET_MARK;
-        return this;
-    }
-
-    @Override
-    public ShortBuffer duplicate() {
-        return copy(this, mark);
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override short[] protectedArray() {
-        return backingArray;
-    }
-
-    @Override int protectedArrayOffset() {
-        return offset;
-    }
-
-    @Override boolean protectedHasArray() {
-        return true;
-    }
-
-    @Override
-    public ShortBuffer put(short c) {
-        if (position == limit) {
-            throw new BufferOverflowException();
-        }
-        backingArray[offset + position++] = c;
-        return this;
-    }
-
-    @Override
-    public ShortBuffer put(int index, short c) {
-        checkIndex(index);
-        backingArray[offset + index] = c;
-        return this;
-    }
-
-    @Override
-    public ShortBuffer put(short[] src, int srcOffset, int shortCount) {
-        if (shortCount > remaining()) {
-            throw new BufferOverflowException();
-        }
-        System.arraycopy(src, srcOffset, backingArray, offset + position, shortCount);
-        position += shortCount;
-        return this;
-    }
-
-    @Override
-    public ShortBuffer slice() {
-        return new ReadWriteShortArrayBuffer(remaining(), backingArray, offset + position);
-    }
-
-}
diff --git a/luni/src/main/java/java/nio/ShortArrayBuffer.java b/luni/src/main/java/java/nio/ShortArrayBuffer.java
index bf6f752..a092cb0 100644
--- a/luni/src/main/java/java/nio/ShortArrayBuffer.java
+++ b/luni/src/main/java/java/nio/ShortArrayBuffer.java
@@ -18,69 +18,141 @@
 package java.nio;
 
 /**
- * ShortArrayBuffer, ReadWriteShortArrayBuffer and ReadOnlyShortArrayBuffer
- * compose the implementation of array based short buffers.
- * <p>
- * ShortArrayBuffer implements all the shared readonly methods and is extended
- * by the other two classes.
- * </p>
- * <p>
- * All methods are marked final for runtime performance.
- * </p>
- *
+ * ShortArrayBuffer implements short[]-based ShortBuffers.
  */
-abstract class ShortArrayBuffer extends ShortBuffer {
+final class ShortArrayBuffer extends ShortBuffer {
 
-    protected final short[] backingArray;
+  private final short[] backingArray;
 
-    protected final int offset;
+  private final int arrayOffset;
 
-    ShortArrayBuffer(short[] array) {
-        this(array.length, array, 0);
+  private final boolean isReadOnly;
+
+  ShortArrayBuffer(short[] array) {
+    this(array.length, array, 0, false);
+  }
+
+  private ShortArrayBuffer(int capacity, short[] backingArray, int arrayOffset, boolean isReadOnly) {
+    super(capacity);
+    this.backingArray = backingArray;
+    this.arrayOffset = arrayOffset;
+    this.isReadOnly = isReadOnly;
+  }
+
+  private static ShortArrayBuffer copy(ShortArrayBuffer other, int markOfOther, boolean isReadOnly) {
+    ShortArrayBuffer buf = new ShortArrayBuffer(other.capacity(), other.backingArray, other.arrayOffset, isReadOnly);
+    buf.limit = other.limit;
+    buf.position = other.position();
+    buf.mark = markOfOther;
+    return buf;
+  }
+
+  @Override public ShortBuffer asReadOnlyBuffer() {
+    return copy(this, mark, true);
+  }
+
+  @Override public ShortBuffer compact() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    System.arraycopy(backingArray, position + arrayOffset, backingArray, arrayOffset, remaining());
+    position = limit - position;
+    limit = capacity;
+    mark = UNSET_MARK;
+    return this;
+  }
 
-    ShortArrayBuffer(int capacity) {
-        this(capacity, new short[capacity], 0);
+  @Override public ShortBuffer duplicate() {
+    return copy(this, mark, isReadOnly);
+  }
+
+  @Override public ShortBuffer slice() {
+    return new ShortArrayBuffer(remaining(), backingArray, arrayOffset + position, isReadOnly);
+  }
+
+  @Override public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  @Override short[] protectedArray() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return backingArray;
+  }
 
-    ShortArrayBuffer(int capacity, short[] backingArray, int offset) {
-        super(capacity);
-        this.backingArray = backingArray;
-        this.offset = offset;
+  @Override int protectedArrayOffset() {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
+    return arrayOffset;
+  }
 
-    @Override
-    public final short get() {
-        if (position == limit) {
-            throw new BufferUnderflowException();
-        }
-        return backingArray[offset + position++];
+  @Override boolean protectedHasArray() {
+    if (isReadOnly) {
+      return false;
     }
+    return true;
+  }
 
-    @Override
-    public final short get(int index) {
-        checkIndex(index);
-        return backingArray[offset + index];
+  @Override public final short get() {
+    if (position == limit) {
+      throw new BufferUnderflowException();
     }
+    return backingArray[arrayOffset + position++];
+  }
 
-    @Override
-    public final ShortBuffer get(short[] dst, int dstOffset, int shortCount) {
-        if (shortCount > remaining()) {
-            throw new BufferUnderflowException();
-        }
-        System.arraycopy(backingArray, offset + position, dst, dstOffset, shortCount);
-        position += shortCount;
-        return this;
+  @Override public final short get(int index) {
+    checkIndex(index);
+    return backingArray[arrayOffset + index];
+  }
+
+  @Override public final ShortBuffer get(short[] dst, int dstOffset, int shortCount) {
+    if (shortCount > remaining()) {
+      throw new BufferUnderflowException();
     }
+    System.arraycopy(backingArray, arrayOffset + position, dst, dstOffset, shortCount);
+    position += shortCount;
+    return this;
+  }
 
-    @Override
-    public final boolean isDirect() {
-        return false;
+  @Override public final boolean isDirect() {
+    return false;
+  }
+
+  @Override public final ByteOrder order() {
+    return ByteOrder.nativeOrder();
+  }
+
+  @Override public ShortBuffer put(short c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
     }
-
-    @Override
-    public final ByteOrder order() {
-        return ByteOrder.nativeOrder();
+    if (position == limit) {
+      throw new BufferOverflowException();
     }
+    backingArray[arrayOffset + position++] = c;
+    return this;
+  }
 
+  @Override public ShortBuffer put(int index, short c) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    checkIndex(index);
+    backingArray[arrayOffset + index] = c;
+    return this;
+  }
+
+  @Override public ShortBuffer put(short[] src, int srcOffset, int shortCount) {
+    if (isReadOnly) {
+      throw new ReadOnlyBufferException();
+    }
+    if (shortCount > remaining()) {
+      throw new BufferOverflowException();
+    }
+    System.arraycopy(src, srcOffset, backingArray, arrayOffset + position, shortCount);
+    position += shortCount;
+    return this;
+  }
 }
diff --git a/luni/src/main/java/java/nio/ShortBuffer.java b/luni/src/main/java/java/nio/ShortBuffer.java
index d12a49e..ea20bed 100644
--- a/luni/src/main/java/java/nio/ShortBuffer.java
+++ b/luni/src/main/java/java/nio/ShortBuffer.java
@@ -48,7 +48,7 @@
         if (capacity < 0) {
             throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
-        return new ReadWriteShortArrayBuffer(capacity);
+        return new ShortArrayBuffer(new short[capacity]);
     }
 
     /**
@@ -85,7 +85,7 @@
      */
     public static ShortBuffer wrap(short[] array, int start, int shortCount) {
         Arrays.checkOffsetAndCount(array.length, start, shortCount);
-        ShortBuffer buf = new ReadWriteShortArrayBuffer(array);
+        ShortBuffer buf = new ShortArrayBuffer(array);
         buf.position = start;
         buf.limit = start + shortCount;
         return buf;
@@ -425,6 +425,9 @@
      *                if no changes may be made to the contents of this buffer.
      */
     public ShortBuffer put(ShortBuffer src) {
+        if (isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
         if (src == this) {
             throw new IllegalArgumentException("src == this");
         }
diff --git a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
index ab76e8e..1c7e190 100644
--- a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
+++ b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
@@ -130,7 +130,7 @@
             throw new ClosedChannelException();
         }
         if (!((interestSet & ~validOps()) == 0)) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("no valid ops in interest set: " + interestSet);
         }
 
         synchronized (blockingLock) {
@@ -143,7 +143,7 @@
                     throw new IllegalSelectorException();
                 }
                 // throw NPE exactly to keep consistency
-                throw new NullPointerException();
+                throw new NullPointerException("selector not open");
             }
             SelectionKey key = keyFor(selector);
             if (key == null) {
diff --git a/luni/src/main/java/java/security/AlgorithmParameterGenerator.java b/luni/src/main/java/java/security/AlgorithmParameterGenerator.java
index 3edc167..61548d7 100644
--- a/luni/src/main/java/java/security/AlgorithmParameterGenerator.java
+++ b/luni/src/main/java/java/security/AlgorithmParameterGenerator.java
@@ -146,7 +146,7 @@
     public static AlgorithmParameterGenerator getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (algorithm == null) {
             throw new NullPointerException("algorithm == null");
diff --git a/luni/src/main/java/java/security/KeyFactory.java b/luni/src/main/java/java/security/KeyFactory.java
index 8d39003..3bd05b9 100644
--- a/luni/src/main/java/java/security/KeyFactory.java
+++ b/luni/src/main/java/java/security/KeyFactory.java
@@ -127,7 +127,7 @@
     public static KeyFactory getInstance(String algorithm, Provider provider)
                                  throws NoSuchAlgorithmException {
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (algorithm == null) {
             throw new NullPointerException("algorithm == null");
diff --git a/luni/src/main/java/java/security/KeyPairGenerator.java b/luni/src/main/java/java/security/KeyPairGenerator.java
index 5c17d79..8a713d0 100644
--- a/luni/src/main/java/java/security/KeyPairGenerator.java
+++ b/luni/src/main/java/java/security/KeyPairGenerator.java
@@ -140,7 +140,7 @@
     public static KeyPairGenerator getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (algorithm == null) {
             throw new NullPointerException("algorithm == null");
diff --git a/luni/src/main/java/java/security/KeyStore.java b/luni/src/main/java/java/security/KeyStore.java
index 3d856f7..b0e4945 100644
--- a/luni/src/main/java/java/security/KeyStore.java
+++ b/luni/src/main/java/java/security/KeyStore.java
@@ -55,7 +55,7 @@
     private static final Engine ENGINE = new Engine(SERVICE);
 
     //  Store KeyStore property name
-    private static final String PROPERTYNAME = "keystore.type";
+    private static final String PROPERTY_NAME = "keystore.type";
 
     //  Store default KeyStore type
     private static final String DEFAULT_KEYSTORE_TYPE = "jks";
@@ -179,7 +179,7 @@
     public static KeyStore getInstance(String type, Provider provider) throws KeyStoreException {
         // check parameters
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (type == null) {
             throw new NullPointerException("type == null");
@@ -204,7 +204,7 @@
      * @return the default type for {@code KeyStore} instances
      */
     public static final String getDefaultType() {
-        String dt = Security.getProperty(PROPERTYNAME);
+        String dt = Security.getProperty(PROPERTY_NAME);
         return (dt == null ? DEFAULT_KEYSTORE_TYPE : dt);
     }
 
diff --git a/luni/src/main/java/java/security/MessageDigest.java b/luni/src/main/java/java/security/MessageDigest.java
index 6f1bf21..658b41f 100644
--- a/luni/src/main/java/java/security/MessageDigest.java
+++ b/luni/src/main/java/java/security/MessageDigest.java
@@ -149,7 +149,7 @@
     public static MessageDigest getInstance(String algorithm, Provider provider)
             throws NoSuchAlgorithmException {
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (algorithm == null) {
             throw new NullPointerException("algorithm == null");
diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java
index 899625a..a6de19c 100644
--- a/luni/src/main/java/java/security/Provider.java
+++ b/luni/src/main/java/java/security/Provider.java
@@ -25,7 +25,6 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -72,7 +71,7 @@
     private transient LinkedHashMap<String, Service> propertyAliasTable;
 
     // The properties changed via put()
-    private transient Properties changedProperties;
+    private transient LinkedHashMap<Object, Object> changedProperties;
 
     // For getService(String type, String algorithm) optimization:
     // previous result
@@ -194,7 +193,7 @@
 
     private void myPutAll(Map<?,?> t) {
         if (changedProperties == null) {
-            changedProperties = new Properties();
+            changedProperties = new LinkedHashMap<Object, Object>();
         }
         Iterator<? extends Map.Entry<?, ?>> it = t.entrySet().iterator();
         Object key;
@@ -259,7 +258,7 @@
             removeFromPropertyServiceTable(key);
         }
         if (changedProperties == null) {
-            changedProperties = new Properties();
+            changedProperties = new LinkedHashMap<Object, Object>();
         }
         changedProperties.put(key, value);
         return super.put(key, value);
diff --git a/luni/src/main/java/java/security/SecureRandom.java b/luni/src/main/java/java/security/SecureRandom.java
index 6ed631c..281885b 100644
--- a/luni/src/main/java/java/security/SecureRandom.java
+++ b/luni/src/main/java/java/security/SecureRandom.java
@@ -28,20 +28,26 @@
 /**
  * This class generates cryptographically secure pseudo-random numbers.
  *
- * <h3>Supported Algorithms</h3>
- * <ul>
- *   <li><strong>SHA1PRNG</strong>: Based on <a
- *     href="http://en.wikipedia.org/wiki/SHA-1">SHA-1</a>. Not guaranteed to be
- *     compatible with the SHA1PRNG algorithm on the reference
- *     implementation.</li>
- * </ul>
+ * It is best to invoke {@code SecureRandom} using the default constructor.
+ * This will provide an instance of the most cryptographically strong
+ * provider available:
+ *
+ * <pre>SecureRandom sr = new SecureRandom();
+ * byte[] output = new byte[16];
+ * sr.nextBytes(output);</pre>
  *
  * <p>The default algorithm is defined by the first {@code SecureRandomSpi}
- * provider found in the VM's installed security providers. Use {@link
- * Security} to install custom {@link SecureRandomSpi} providers.
+ * provider found in the installed security providers. Use {@link Security}
+ * to install custom {@link SecureRandomSpi} providers.
  *
- * <a name="insecure_seed"><h3>Seeding {@code SecureRandom} may be
- * insecure</h3></a>
+ * <p>Note that the output of a {@code SecureRandom} instance should never
+ * be relied upon to be deterministic. For deterministic output from a given
+ * input, see {@link MessageDigest} which provides one-way hash functions.
+ * For deriving keys from passwords, see
+ * {@link javax.crypto.SecretKeyFactory}.
+ *
+ * <h3><a name="insecure_seed">Seeding {@code SecureRandom} may be
+ * insecure</a></h3>
  * A seed is an array of bytes used to bootstrap random number generation.
  * To produce cryptographically secure random numbers, both the seed and the
  * algorithm must be secure.
@@ -50,19 +56,11 @@
  * an internal entropy source, such as {@code /dev/urandom}. This seed is
  * unpredictable and appropriate for secure use.
  *
- * <p>You may alternatively specify the initial seed explicitly with the
- * {@link #SecureRandom(byte[]) seeded constructor} or by calling {@link
- * #setSeed} before any random numbers have been generated. Specifying a fixed
- * seed will cause the instance to return a predictable sequence of numbers.
- * This may be useful for testing but it is not appropriate for secure use.
- *
- * <p>It is dangerous to seed {@code SecureRandom} with the current time because
- * that value is more predictable to an attacker than the default seed.
- *
- * <p>Calling {@link #setSeed} on a {@code SecureRandom} <i>after</i> it has
- * been used to generate random numbers (ie. calling {@link #nextBytes}) will
- * supplement the existing seed. This does not cause the instance to return a
- * predictable numbers, nor does it harm the security of the numbers generated.
+ * <p>Using the {@link #SecureRandom(byte[]) seeded constructor} or calling
+ * {@link #setSeed} may completely replace the cryptographically strong
+ * default seed causing the instance to return a predictable sequence of
+ * numbers unfit for secure use. Due to variations between implementations
+ * it is not recommended to use {@code setSeed} at all.
  */
 public class SecureRandom extends Random {
 
@@ -209,7 +207,7 @@
     public static SecureRandom getInstance(String algorithm, Provider provider)
             throws NoSuchAlgorithmException {
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (algorithm == null) {
             throw new NullPointerException("algorithm == null");
diff --git a/luni/src/main/java/java/security/Signature.java b/luni/src/main/java/java/security/Signature.java
index be89654..b2bd122 100644
--- a/luni/src/main/java/java/security/Signature.java
+++ b/luni/src/main/java/java/security/Signature.java
@@ -168,7 +168,7 @@
             throw new NullPointerException("algorithm == null");
         }
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         return getSignatureInstance(algorithm, provider);
     }
diff --git a/luni/src/main/java/java/security/cert/CertPath.java b/luni/src/main/java/java/security/cert/CertPath.java
index d247d30..5c5b67e 100644
--- a/luni/src/main/java/java/security/cert/CertPath.java
+++ b/luni/src/main/java/java/security/cert/CertPath.java
@@ -145,16 +145,13 @@
         throws CertificateEncodingException;
 
     /**
-     * Returns an encoding of the {@code CertPath} using the specified encoding.
+     * Returns an encoding of this {@code CertPath} using the given
+     * {@code encoding} from {@link #getEncodings()}.
      *
-     * @param encoding
-     *            encoding that should be generated.
-     * @return default encoding of the {@code CertPath}.
      * @throws CertificateEncodingException
      *             if the encoding fails.
      */
-    public abstract byte[] getEncoded(String encoding)
-        throws CertificateEncodingException;
+    public abstract byte[] getEncoded(String encoding) throws CertificateEncodingException;
 
     /**
      * Returns an {@code Iterator} over the supported encodings for a
diff --git a/luni/src/main/java/java/security/cert/CertPathBuilder.java b/luni/src/main/java/java/security/cert/CertPathBuilder.java
index 42029e5..0817fc0 100644
--- a/luni/src/main/java/java/security/cert/CertPathBuilder.java
+++ b/luni/src/main/java/java/security/cert/CertPathBuilder.java
@@ -37,11 +37,10 @@
     private static final Engine ENGINE = new Engine(SERVICE);
 
     // Store default property name
-    private static final String PROPERTYNAME = "certpathbuilder.type";
+    private static final String PROPERTY_NAME = "certpathbuilder.type";
 
-    // Default value of CertPathBuilder type. It returns if certpathbuild.type
-    // property is not defined in java.security file
-    private static final String DEFAULTPROPERTY = "PKIX";
+    // Default value of CertPathBuilder type.
+    private static final String DEFAULT_PROPERTY = "PKIX";
 
     // Store used provider
     private final Provider provider;
@@ -192,7 +191,7 @@
      *         determined.
      */
     public static final String getDefaultType() {
-        String defaultType = Security.getProperty(PROPERTYNAME);
-        return (defaultType != null ? defaultType : DEFAULTPROPERTY);
+        String defaultType = Security.getProperty(PROPERTY_NAME);
+        return (defaultType != null ? defaultType : DEFAULT_PROPERTY);
     }
 }
diff --git a/luni/src/main/java/java/security/cert/CertPathValidator.java b/luni/src/main/java/java/security/cert/CertPathValidator.java
index ddf78bf..a3a666a 100644
--- a/luni/src/main/java/java/security/cert/CertPathValidator.java
+++ b/luni/src/main/java/java/security/cert/CertPathValidator.java
@@ -37,11 +37,11 @@
     private static final Engine ENGINE = new Engine(SERVICE);
 
     // Store default property name
-    private static final String PROPERTYNAME = "certpathvalidator.type";
+    private static final String PROPERTY_NAME = "certpathvalidator.type";
 
     // Default value of CertPathBuilder type. It returns if certpathbuild.type
     // property is not defined in java.security file
-    private static final String DEFAULTPROPERTY = "PKIX";
+    private static final String DEFAULT_PROPERTY = "PKIX";
 
     // Store used provider
     private final Provider provider;
@@ -157,7 +157,7 @@
     public static CertPathValidator getInstance(String algorithm,
             Provider provider) throws NoSuchAlgorithmException {
         if (provider == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("provider == null");
         }
         if (algorithm == null) {
             throw new NullPointerException("algorithm == null");
@@ -199,7 +199,7 @@
      *         determined.
      */
     public static final String getDefaultType() {
-        String defaultType = Security.getProperty(PROPERTYNAME);
-        return (defaultType != null ? defaultType : DEFAULTPROPERTY);
+        String defaultType = Security.getProperty(PROPERTY_NAME);
+        return (defaultType != null ? defaultType : DEFAULT_PROPERTY);
     }
 }
diff --git a/luni/src/main/java/java/security/cert/CertStore.java b/luni/src/main/java/java/security/cert/CertStore.java
index 2e28828..72d356f 100644
--- a/luni/src/main/java/java/security/cert/CertStore.java
+++ b/luni/src/main/java/java/security/cert/CertStore.java
@@ -39,11 +39,11 @@
     private static final Engine ENGINE = new Engine(SERVICE);
 
     // Store default property name
-    private static final String PROPERTYNAME = "certstore.type";
+    private static final String PROPERTY_NAME = "certstore.type";
 
     // Default value of CertStore type. It returns if certpathbuild.type
     // property is not defined in java.security file
-    private static final String DEFAULTPROPERTY = "LDAP";
+    private static final String DEFAULT_PROPERTY = "LDAP";
 
     // Store used provider
     private final Provider provider;
@@ -266,7 +266,7 @@
      *         determined.
      */
     public static final String getDefaultType() {
-        String defaultType = Security.getProperty(PROPERTYNAME);
-        return (defaultType == null ? DEFAULTPROPERTY : defaultType);
+        String defaultType = Security.getProperty(PROPERTY_NAME);
+        return (defaultType == null ? DEFAULT_PROPERTY : defaultType);
     }
 }
diff --git a/luni/src/main/java/java/security/cert/CertificateFactory.java b/luni/src/main/java/java/security/cert/CertificateFactory.java
index 83d40d3..f882d52 100644
--- a/luni/src/main/java/java/security/cert/CertificateFactory.java
+++ b/luni/src/main/java/java/security/cert/CertificateFactory.java
@@ -225,23 +225,18 @@
     }
 
     /**
-     * Generates a {@code CertPath} (a certificate chain) from the provided
-     * {@code InputStream} and the specified encoding scheme.
+     * Generates a {@code CertPath} (a certificate chain) from the given
+     * {@code inputStream}, assuming the given {@code encoding} from
+     * {@link #getCertPathEncodings()}.
      *
-     * @param inStream
-     *            {@code InputStream} containing certificate path data in
-     *            specified encoding.
-     * @param encoding
-     *            encoding of the data in the input stream.
-     * @return a {@code CertPath} initialized from the provided data.
      * @throws CertificateException
      *             if parsing problems are detected.
      * @throws UnsupportedOperationException
      *             if the provider does not implement this method.
      */
-    public final CertPath generateCertPath(InputStream inStream, String encoding)
+    public final CertPath generateCertPath(InputStream inputStream, String encoding)
             throws CertificateException {
-        return spiImpl.engineGenerateCertPath(inStream, encoding);
+        return spiImpl.engineGenerateCertPath(inputStream, encoding);
     }
 
     /**
diff --git a/luni/src/main/java/java/security/cert/CertificateFactorySpi.java b/luni/src/main/java/java/security/cert/CertificateFactorySpi.java
index 321afa7..117ef65 100644
--- a/luni/src/main/java/java/security/cert/CertificateFactorySpi.java
+++ b/luni/src/main/java/java/security/cert/CertificateFactorySpi.java
@@ -105,15 +105,10 @@
     }
 
     /**
-     * Generates a {@code CertPath} from the provided {@code
-     * InputStream} in the specified encoding.
+     * Generates a {@code CertPath} (a certificate chain) from the given
+     * {@code inputStream}, assuming the given {@code encoding} from
+     * {@link #engineGetCertPathEncodings()}.
      *
-     * @param inStream
-     *            an input stream containing certificate path data in specified
-     *            encoding.
-     * @param encoding
-     *            the encoding of the data in the input stream.
-     * @return a {@code CertPath} initialized from the provided data
      * @throws CertificateException
      *             if parsing problems are detected.
      * @throws UnsupportedOperationException
diff --git a/luni/src/main/java/java/security/cert/X509Certificate.java b/luni/src/main/java/java/security/cert/X509Certificate.java
index 90737d7..ac4be18 100644
--- a/luni/src/main/java/java/security/cert/X509Certificate.java
+++ b/luni/src/main/java/java/security/cert/X509Certificate.java
@@ -364,9 +364,11 @@
      * Returns the path length of the certificate constraints from the {@code
      * BasicContraints} extension.
      *
-     * @return the path length of the certificate constraints if the extension
-     *         is present or {@code -1} if the extension is not present. {@code
-     *         Integer.MAX_VALUE} if there's not limit.
+     * If the certificate has no basic constraints or is not a
+     * certificate authority, {@code -1} is returned. If the
+     * certificate is a certificate authority without a path length,
+     * {@code Integer.MAX_VALUE} is returned. Otherwise, the
+     * certificate authority's path length is returned.
      */
     public abstract int getBasicConstraints();
 
diff --git a/luni/src/main/java/java/security/package.html b/luni/src/main/java/java/security/package.html
index b2e7e08..a0a09f4 100644
--- a/luni/src/main/java/java/security/package.html
+++ b/luni/src/main/java/java/security/package.html
@@ -4,9 +4,12 @@
 </head>
 <html>
 <body>
-<p>This package is for compatibility
-with legacy code only, and should not be used or expected to do
-anything useful.
+<p>
+Extensible cryptographic <i>service provider infrastructure</i> (SPI) for using
+and defining services such as {@link java.security.Certificate Certificates},
+{@link java.security.Key Keys}, {@link java.security.KeyStore KeyStores}, {@link
+java.security.MessageDigest MessageDigests}, and {@link java.security.Signature
+Signatures}.
 </p>
 </body>
 </html>
diff --git a/luni/src/main/java/java/security/security.properties b/luni/src/main/java/java/security/security.properties
index 361e2ad..b124271 100644
--- a/luni/src/main/java/java/security/security.properties
+++ b/luni/src/main/java/java/security/security.properties
@@ -55,8 +55,8 @@
 # See specification for
 # javax/net/ssl/KeyManagerFactory.html#getDefaultAlgorithm()
 # javax/net/ssl/TrustManagerFactory.html#getDefaultAlgorithm()
-ssl.KeyManagerFactory.algorithm=X509
-ssl.TrustManagerFactory.algorithm=X509
+ssl.KeyManagerFactory.algorithm=PKIX
+ssl.TrustManagerFactory.algorithm=PKIX
 
 # system.scope is used to specify implementation class of IdentityScope
 system.scope=org.apache.harmony.security.SystemScope
diff --git a/luni/src/main/java/java/security/spec/ECParameterSpec.java b/luni/src/main/java/java/security/spec/ECParameterSpec.java
index 6a033e4..9860ac0 100644
--- a/luni/src/main/java/java/security/spec/ECParameterSpec.java
+++ b/luni/src/main/java/java/security/spec/ECParameterSpec.java
@@ -31,6 +31,8 @@
     private final BigInteger order;
     // Cofactor
     private final int cofactor;
+    // Name of curve if available.
+    private final String curveName;
 
     /**
      * Creates a new {@code ECParameterSpec} with the specified elliptic curve,
@@ -50,10 +52,23 @@
      */
     public ECParameterSpec(EllipticCurve curve, ECPoint generator,
             BigInteger order, int cofactor) {
+        this(curve, generator, order, cofactor, null);
+    }
+
+    /**
+     * Creates a new {@code ECParameterSpec} with the specified named curve
+     * and all of its parameters.
+     *
+     * @see #ECParameterSpec(EllipticCurve, ECPoint, BigInteger, int)
+     * @hide
+     */
+    public ECParameterSpec(EllipticCurve curve, ECPoint generator,
+            BigInteger order, int cofactor, String curveName) {
         this.curve = curve;
         this.generator = generator;
         this.order = order;
         this.cofactor = cofactor;
+        this.curveName = curveName;
         // throw NullPointerException if curve, generator or order is null
         if (this.curve == null) {
             throw new NullPointerException("curve == null");
@@ -108,4 +123,14 @@
     public BigInteger getOrder() {
         return order;
     }
+
+    /**
+     * Returns the name of the curve if this is a named curve. Returns
+     * {@code null} if this is not known to be a named curve.
+     *
+     * @hide
+     */
+    public String getCurveName() {
+        return curveName;
+    }
 }
diff --git a/luni/src/main/java/java/sql/Date.java b/luni/src/main/java/java/sql/Date.java
index 2434fbd..34ca5bd 100644
--- a/luni/src/main/java/java/sql/Date.java
+++ b/luni/src/main/java/java/sql/Date.java
@@ -81,7 +81,7 @@
     @Deprecated
     @Override
     public int getHours() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -94,7 +94,7 @@
     @Deprecated
     @Override
     public int getMinutes() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -107,7 +107,7 @@
     @Deprecated
     @Override
     public int getSeconds() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -121,7 +121,7 @@
     @Deprecated
     @Override
     public void setHours(int theHours) {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -135,7 +135,7 @@
     @Deprecated
     @Override
     public void setMinutes(int theMinutes) {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -149,7 +149,7 @@
     @Deprecated
     @Override
     public void setSeconds(int theSeconds) {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -212,7 +212,7 @@
      */
     public static Date valueOf(String dateString) {
         if (dateString == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("dateString == null");
         }
         int firstIndex = dateString.indexOf('-');
         int secondIndex = dateString.indexOf('-', firstIndex + 1);
diff --git a/luni/src/main/java/java/sql/Driver.java b/luni/src/main/java/java/sql/Driver.java
index 41ca863..fc35a30 100644
--- a/luni/src/main/java/java/sql/Driver.java
+++ b/luni/src/main/java/java/sql/Driver.java
@@ -20,17 +20,13 @@
 import java.util.Properties;
 
 /**
- * An interface to a JDBC driver.
- * <p>
- * The JDBC driver uses URLs to specify the location of specific data. URL
+ * An interface to a JDBC driver. Instances are returned by {@link DriverManager}.
+ *
+ * <p>The JDBC driver uses URLs to specify the location of specific data. URL
  * format typically takes the form " {@code xxxx:yyyy:SpecificData}", where "
  * {@code xxxx:yyyy}" is referred to as the <i>subprotocol</i> and is normally
  * the same for all of a particular driver. " {@code SpecificData}" is a string
  * which identifies the particular data source that the driver should use.
- * <p>
- * A driver needs to be registered with a {@link DriverManager}. It is
- * registered and instantiated by calling {@code Class.forName("DriverURL")}
- * with the URL string as argument.
  *
  * @see DriverManager
  */
diff --git a/luni/src/main/java/java/sql/DriverManager.java b/luni/src/main/java/java/sql/DriverManager.java
index c547585..550ccae 100644
--- a/luni/src/main/java/java/sql/DriverManager.java
+++ b/luni/src/main/java/java/sql/DriverManager.java
@@ -28,9 +28,13 @@
 import java.util.Properties;
 
 /**
- * Provides facilities for managing JDBC drivers.
- * <p>
- * The {@code DriverManager} class loads JDBC drivers during its initialization,
+ * Provides facilities for managing JDBC drivers. The <code>android.database</code> and
+ * <code>android.database.sqlite</code> packages offer a higher-performance alternative for new
+ * code.
+ *
+ * <p>Note that Android does not include any JDBC drivers by default; you must provide your own.
+ *
+ * <p>The {@code DriverManager} class loads JDBC drivers during its initialization,
  * from the list of drivers referenced by the system property {@code
  * "jdbc.drivers"}.
  */
diff --git a/luni/src/main/java/java/sql/Time.java b/luni/src/main/java/java/sql/Time.java
index bccce68..913379c 100644
--- a/luni/src/main/java/java/sql/Time.java
+++ b/luni/src/main/java/java/sql/Time.java
@@ -76,7 +76,7 @@
     @Deprecated
     @Override
     public int getDate() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -90,7 +90,7 @@
     @Deprecated
     @Override
     public int getDay() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -104,7 +104,7 @@
     @Deprecated
     @Override
     public int getMonth() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -118,7 +118,7 @@
     @Deprecated
     @Override
     public int getYear() {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -131,7 +131,7 @@
     @Deprecated
     @Override
     public void setDate(int i) {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -144,7 +144,7 @@
     @Deprecated
     @Override
     public void setMonth(int i) {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -157,7 +157,7 @@
     @Deprecated
     @Override
     public void setYear(int i) {
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("unimplemented");
     }
 
     /**
@@ -223,7 +223,7 @@
      */
     public static Time valueOf(String timeString) {
         if (timeString == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("timeString == null");
         }
         int firstIndex = timeString.indexOf(':');
         int secondIndex = timeString.indexOf(':', firstIndex + 1);
diff --git a/luni/src/main/java/java/sql/Timestamp.java b/luni/src/main/java/java/sql/Timestamp.java
index 463ea8b..2e6d3d9 100644
--- a/luni/src/main/java/java/sql/Timestamp.java
+++ b/luni/src/main/java/java/sql/Timestamp.java
@@ -20,6 +20,7 @@
 import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Locale;
 import java.util.regex.Pattern;
 
 /**
@@ -82,7 +83,7 @@
             throws IllegalArgumentException {
         super(theYear, theMonth, theDate, theHour, theMinute, theSecond);
         if (theNano < 0 || theNano > 999999999) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("ns out of range: " + theNano);
         }
         nanos = theNano;
     }
@@ -413,7 +414,7 @@
             throw badTimestampString(s);
         }
 
-        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
         ParsePosition pp = new ParsePosition(0);
 
         /*
diff --git a/luni/src/main/java/java/sql/package.html b/luni/src/main/java/java/sql/package.html
deleted file mode 100644
index 0799bb7..0000000
--- a/luni/src/main/java/java/sql/package.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-<body>
-<p>Provides a compatibility interface for accessing SQL-based databases.
-The <code>android.database</code> and <code>android.database.sqlite</code>
-packages offer a higher-performance alternative where source compatibility
-is not an issue.</p>
-
-<p>Note that you must provide your own JDBC driver.</p>
-</body>
-</html>
diff --git a/luni/src/main/java/java/text/AttributedString.java b/luni/src/main/java/java/text/AttributedString.java
index d679283..289ad6d 100644
--- a/luni/src/main/java/java/text/AttributedString.java
+++ b/luni/src/main/java/java/text/AttributedString.java
@@ -577,8 +577,8 @@
         if (attribute == null) {
             throw new NullPointerException("attribute == null");
         }
-        if (text.length() == 0) {
-            throw new IllegalArgumentException();
+        if (text.isEmpty()) {
+            throw new IllegalArgumentException("text is empty");
         }
 
         List<Range> ranges = attributeMap.get(attribute);
diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java
index 5bc7f0c..b14647c 100644
--- a/luni/src/main/java/java/text/BreakIterator.java
+++ b/luni/src/main/java/java/text/BreakIterator.java
@@ -385,6 +385,9 @@
      *            the new text string to be analyzed.
      */
     public void setText(String newText) {
+        if (newText == null) {
+            throw new NullPointerException("newText == null");
+        }
         wrapped.setText(newText);
     }
 
diff --git a/luni/src/main/java/java/text/ChoiceFormat.java b/luni/src/main/java/java/text/ChoiceFormat.java
index e0a1239..014b8c7 100644
--- a/luni/src/main/java/java/text/ChoiceFormat.java
+++ b/luni/src/main/java/java/text/ChoiceFormat.java
@@ -168,10 +168,10 @@
                     next = nextDouble(value.doubleValue());
                     break;
                 default:
-                    throw new IllegalArgumentException();
+                    throw new IllegalArgumentException("Bad character '" + ch + "' in template: " + template);
             }
             if (limitCount > 0 && next <= limits[limitCount - 1]) {
-                throw new IllegalArgumentException();
+                throw new IllegalArgumentException("Bad template: " + template);
             }
             buffer.setLength(0);
             position.setIndex(index);
@@ -426,7 +426,8 @@
      */
     public void setChoices(double[] limits, String[] formats) {
         if (limits.length != formats.length) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("limits.length != formats.length: " +
+                                               limits.length + " != " + formats.length);
         }
         choiceLimits = limits;
         choiceFormats = formats;
diff --git a/luni/src/main/java/java/text/Collator.java b/luni/src/main/java/java/text/Collator.java
index 2ddb516..58d7047 100644
--- a/luni/src/main/java/java/text/Collator.java
+++ b/luni/src/main/java/java/text/Collator.java
@@ -339,7 +339,7 @@
         case Collator.NO_DECOMPOSITION:
             return RuleBasedCollatorICU.VALUE_OFF;
         }
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Bad mode: " + mode);
     }
 
     private int decompositionMode_ICU_Java(int mode) {
@@ -366,7 +366,7 @@
         case Collator.IDENTICAL:
             return RuleBasedCollatorICU.VALUE_IDENTICAL;
         }
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Bad strength: " + value);
     }
 
     private int strength_ICU_Java(int value) {
diff --git a/luni/src/main/java/java/text/DateFormat.java b/luni/src/main/java/java/text/DateFormat.java
index b45e699..ac64eed 100644
--- a/luni/src/main/java/java/text/DateFormat.java
+++ b/luni/src/main/java/java/text/DateFormat.java
@@ -27,102 +27,39 @@
 import libcore.icu.LocaleData;
 
 /**
- * An abstract class for date/time formatting subclasses which formats and
- * parses dates or time in a language-independent manner. The date/time
- * formatting subclass, such as {@link SimpleDateFormat}, allows for formatting
- * (i.e., date -> text), parsing (text -> date), and normalization. The date is
- * represented as a {@code Date} object or as the milliseconds since January 1,
- * 1970, 00:00:00 GMT.
- * <p>
- * DateFormat provides many class methods for obtaining default date/time
- * formatters based on the default or a given locale and a number of formatting
- * styles. The formatting styles include FULL, LONG, MEDIUM, and SHORT. More
- * details and examples for using these styles are provided in the method
- * descriptions.
- * <p>
- * {@code DateFormat} helps you to format and parse dates for any locale. Your
- * code can be completely independent of the locale conventions for months, days
- * of the week, or even the calendar format: lunar vs. solar.
- * <p>
- * To format a date for the current Locale, use one of the static factory
- * methods:
- * <blockquote>
+ * Formats or parses dates and times.
  *
+ * <p>This class provides factories for obtaining instances configured for a specific locale.
+ * The most common subclass is {@link SimpleDateFormat}.
+ *
+ * <h4>Sample Code</h4>
+ * <p>This code:
  * <pre>
- * myString = DateFormat.getDateInstance().format(myDate);
- * </pre>
- *
- * </blockquote>
- * <p>
- * If you are formatting multiple dates, it is more efficient to get the format
- * and use it multiple times so that the system doesn't have to fetch the
- * information about the local language and country conventions multiple times.
- * <blockquote>
- *
- * <pre>
- * DateFormat df = DateFormat.getDateInstance();
- * for (int i = 0; i &lt; a.length; ++i) {
- *     output.println(df.format(myDate[i]) + &quot;; &quot;);
+ * DateFormat[] formats = new DateFormat[] {
+ *   DateFormat.getDateInstance(),
+ *   DateFormat.getDateTimeInstance(),
+ *   DateFormat.getTimeInstance(),
+ * };
+ * for (DateFormat df : formats) {
+ *   System.out.println(df.format(new Date(0)));
+ *   df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ *   System.out.println(df.format(new Date(0)));
  * }
  * </pre>
  *
- * </blockquote>
- * <p>
- * To format a number for a different locale, specify it in the call to
- * {@code getDateInstance}:
- * <blockquote>
- *
+ * <p>Produces this output when run on an {@code en_US} device in the America/Los_Angeles time zone:
  * <pre>
- * DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
+ * Dec 31, 1969
+ * Jan 1, 1970
+ * Dec 31, 1969 4:00:00 PM
+ * Jan 1, 1970 12:00:00 AM
+ * 4:00:00 PM
+ * 12:00:00 AM
  * </pre>
- *
- * </blockquote>
- * <p>
- * {@code DateFormat} can also be used to parse strings:
- * <blockquote>
- *
- * <pre>
- * myDate = df.parse(myString);
- * </pre>
- *
- * </blockquote>
- * <p>
- * Use {@code getDateInstance} to get the normal date format for a country.
- * Other static factory methods are available: Use {@code getTimeInstance} to
- * get the time format for a country. Use {@code getDateTimeInstance} to get the
- * date and time format. You can pass in different options to these factory
- * methods to control the length of the result; from SHORT to MEDIUM to LONG to
- * FULL. The exact result depends on the locale, but generally:
- * <ul>
- * <li>SHORT is completely numeric, such as 12.13.52 or 3:30pm
- * <li>MEDIUM is longer, such as Jan 12, 1952
- * <li>LONG is longer, such as January 12, 1952 or 3:30:32pm
- * <li>FULL is pretty completely specified, such as Tuesday, April 12, 1952 AD
- * or 3:30:42pm PST.
- * </ul>
- * <p>
- * If needed, the time zone can be set on the format. For even greater control
- * over the formatting or parsing, try casting the {@code DateFormat} you get
- * from the factory methods to a {@code SimpleDateFormat}. This will work for
- * the majority of countries; just remember to put it in a try block in case you
- * encounter an unusual one.
- * <p>
- * There are versions of the parse and format methods which use
- * {@code ParsePosition} and {@code FieldPosition} to allow you to
- * <ul>
- * <li>progressively parse through pieces of a string;
- * <li>align any particular field.
- * </ul>
- * <h4>Synchronization</h4>
- * <p>
- * Date formats are not synchronized. It is recommended to create separate
- * format instances for each thread. If multiple threads access a format
- * concurrently, it must be synchronized externally.
- *
- * @see NumberFormat
- * @see SimpleDateFormat
- * @see Calendar
- * @see TimeZone
+ * And will produce similarly appropriate localized human-readable output on any user's system.
+ * Notice how the same point in time when formatted can appear to be a different time when rendered
+ * for a different time zone. This is one reason why formatting should be left until the data will
+ * only be presented to a human. Machines should interchange "Unix time" integers.
  */
 public abstract class DateFormat extends Format {
 
@@ -349,16 +286,14 @@
      *            {@code Number} instance.
      */
     @Override
-    public final StringBuffer format(Object object, StringBuffer buffer,
-            FieldPosition field) {
+    public final StringBuffer format(Object object, StringBuffer buffer, FieldPosition field) {
         if (object instanceof Date) {
             return format((Date) object, buffer, field);
         }
         if (object instanceof Number) {
-            return format(new Date(((Number) object).longValue()), buffer,
-                    field);
+            return format(new Date(((Number) object).longValue()), buffer, field);
         }
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Bad class: " + object.getClass());
     }
 
     /**
@@ -369,8 +304,7 @@
      * @return the formatted string.
      */
     public final String format(Date date) {
-        return format(date, new StringBuffer(), new FieldPosition(0))
-                .toString();
+        return format(date, new StringBuffer(), new FieldPosition(0)).toString();
     }
 
     /**
@@ -391,8 +325,7 @@
      *            of the alignment field in the formatted text.
      * @return the string buffer.
      */
-    public abstract StringBuffer format(Date date, StringBuffer buffer,
-            FieldPosition field);
+    public abstract StringBuffer format(Date date, StringBuffer buffer, FieldPosition field);
 
     /**
      * Returns an array of locales for which custom {@code DateFormat} instances
@@ -874,9 +807,8 @@
          */
         public static Field ofCalendarField(int calendarField) {
             if (calendarField < 0 || calendarField >= Calendar.FIELD_COUNT) {
-                throw new IllegalArgumentException();
+                throw new IllegalArgumentException("Field out of range: " + calendarField);
             }
-
             return table.get(Integer.valueOf(calendarField));
         }
     }
@@ -884,14 +816,14 @@
     private static void checkDateStyle(int style) {
         if (!(style == SHORT || style == MEDIUM || style == LONG
                 || style == FULL || style == DEFAULT)) {
-            throw new IllegalArgumentException("Illegal date style " + style);
+            throw new IllegalArgumentException("Illegal date style: " + style);
         }
     }
 
     private static void checkTimeStyle(int style) {
         if (!(style == SHORT || style == MEDIUM || style == LONG
                 || style == FULL || style == DEFAULT)) {
-            throw new IllegalArgumentException("Illegal time style " + style);
+            throw new IllegalArgumentException("Illegal time style: " + style);
         }
     }
 }
diff --git a/luni/src/main/java/java/text/DateFormatSymbols.java b/luni/src/main/java/java/text/DateFormatSymbols.java
index e2a2345..e75b82c 100644
--- a/luni/src/main/java/java/text/DateFormatSymbols.java
+++ b/luni/src/main/java/java/text/DateFormatSymbols.java
@@ -23,9 +23,10 @@
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.TimeZone;
 import libcore.icu.ICU;
 import libcore.icu.LocaleData;
-import libcore.icu.TimeZones;
+import libcore.icu.TimeZoneNames;
 
 /**
  * Encapsulates localized date-time formatting data, such as the names of the
@@ -57,11 +58,8 @@
 
     String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays;
 
-    // These are used to implement ICU/Android extensions.
-    transient String[] longStandAloneMonths;
-    transient String[] shortStandAloneMonths;
-    transient String[] longStandAloneWeekdays;
-    transient String[] shortStandAloneWeekdays;
+    // This is used to implement parts of Unicode UTS #35 not historically supported.
+    transient LocaleData localeData;
 
     // Localized display names.
     String[][] zoneStrings;
@@ -82,7 +80,7 @@
      */
     synchronized String[][] internalZoneStrings() {
         if (zoneStrings == null) {
-            zoneStrings = TimeZones.getZoneStrings(locale);
+            zoneStrings = TimeZoneNames.getZoneStrings(locale);
         }
         return zoneStrings;
     }
@@ -106,19 +104,14 @@
     public DateFormatSymbols(Locale locale) {
         this.locale = locale;
         this.localPatternChars = SimpleDateFormat.PATTERN_CHARS;
-        LocaleData localeData = LocaleData.get(locale);
+
+        this.localeData = LocaleData.get(locale);
         this.ampms = localeData.amPm;
         this.eras = localeData.eras;
         this.months = localeData.longMonthNames;
         this.shortMonths = localeData.shortMonthNames;
         this.weekdays = localeData.longWeekdayNames;
         this.shortWeekdays = localeData.shortWeekdayNames;
-
-        // ICU/Android extensions.
-        this.longStandAloneMonths = localeData.longStandAloneMonthNames;
-        this.shortStandAloneMonths = localeData.shortStandAloneMonthNames;
-        this.longStandAloneWeekdays = localeData.longStandAloneWeekdayNames;
-        this.shortStandAloneWeekdays = localeData.shortStandAloneWeekdayNames;
     }
 
     /**
@@ -159,12 +152,7 @@
 
     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
         ois.defaultReadObject();
-
-        // The RI doesn't have these fields, so we'll have to fall back and do the best we can.
-        longStandAloneMonths = months;
-        shortStandAloneMonths = shortMonths;
-        longStandAloneWeekdays = weekdays;
-        shortStandAloneWeekdays = shortWeekdays;
+        this.localeData = LocaleData.get(locale);
     }
 
     private void writeObject(ObjectOutputStream oos) throws IOException {
@@ -331,7 +319,25 @@
      * </ul>
      */
     public String[][] getZoneStrings() {
-        return clone2dStringArray(internalZoneStrings());
+        String[][] result = clone2dStringArray(internalZoneStrings());
+        // If icu4c doesn't have a name, our array contains a null. TimeZone.getDisplayName
+        // knows how to format GMT offsets (and, unlike icu4c, has accurate data). http://b/8128460.
+        for (String[] zone : result) {
+            String id = zone[0];
+            if (zone[1] == null) {
+                zone[1] = TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.LONG, locale);
+            }
+            if (zone[2] == null) {
+                zone[2] = TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.SHORT, locale);
+            }
+            if (zone[3] == null) {
+                zone[3] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.LONG, locale);
+            }
+            if (zone[4] == null) {
+                zone[4] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.SHORT, locale);
+            }
+        }
+        return result;
     }
 
     private static String[][] clone2dStringArray(String[][] array) {
diff --git a/luni/src/main/java/java/text/NumberFormat.java b/luni/src/main/java/java/text/NumberFormat.java
index c285e3d..36fdd0f 100644
--- a/luni/src/main/java/java/text/NumberFormat.java
+++ b/luni/src/main/java/java/text/NumberFormat.java
@@ -301,7 +301,7 @@
             double dv = ((Number) object).doubleValue();
             return format(dv, buffer, field);
         }
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Bad class: " + object.getClass());
     }
 
     /**
diff --git a/luni/src/main/java/java/text/RuleBasedBreakIterator.java b/luni/src/main/java/java/text/RuleBasedBreakIterator.java
index 8490833..a16968e 100644
--- a/luni/src/main/java/java/text/RuleBasedBreakIterator.java
+++ b/luni/src/main/java/java/text/RuleBasedBreakIterator.java
@@ -40,14 +40,14 @@
     }
 
     @Override public int following(int offset) {
-        validateOffset(offset);
+        checkOffset(offset);
         return wrapped.following(offset);
     }
 
-    /*
-     * check the offset, throw exception if it is invalid
-     */
-    private void validateOffset(int offset) {
+    private void checkOffset(int offset) {
+        if (!wrapped.hasText()) {
+            throw new IllegalArgumentException("BreakIterator has no text");
+        }
         CharacterIterator it = wrapped.getText();
         if (offset < it.getBeginIndex() || offset > it.getEndIndex()) {
             String message = "Valid range is [" + it.getBeginIndex() + " " + it.getEndIndex() + "]";
@@ -76,18 +76,20 @@
     }
 
     @Override public void setText(CharacterIterator newText) {
-        // call a method to check if null pointer
+        if (newText == null) {
+            throw new NullPointerException("newText == null");
+        }
         newText.current();
         wrapped.setText(newText);
     }
 
     @Override public boolean isBoundary(int offset) {
-        validateOffset(offset);
+        checkOffset(offset);
         return wrapped.isBoundary(offset);
     }
 
     @Override public int preceding(int offset) {
-        validateOffset(offset);
+        checkOffset(offset);
         return wrapped.preceding(offset);
     }
 
diff --git a/luni/src/main/java/java/text/SimpleDateFormat.java b/luni/src/main/java/java/text/SimpleDateFormat.java
index 760d6a0..fb07428 100644
--- a/luni/src/main/java/java/text/SimpleDateFormat.java
+++ b/luni/src/main/java/java/text/SimpleDateFormat.java
@@ -30,15 +30,15 @@
 import java.util.SimpleTimeZone;
 import java.util.TimeZone;
 import libcore.icu.LocaleData;
-import libcore.icu.TimeZones;
+import libcore.icu.TimeZoneNames;
 
 /**
- * A concrete class for formatting and parsing dates in a locale-sensitive
- * manner. Formatting turns a {@link Date} into a {@link String}, and parsing turns a
- * {@code String} into a {@code Date}.
+ * Formats and parses dates in a locale-sensitive manner. Formatting turns a {@link Date} into
+ * a {@link String}, and parsing turns a {@code String} into a {@code Date}.
  *
  * <h4>Time Pattern Syntax</h4>
- * <p>You can supply a pattern describing what strings are produced/accepted, but almost all
+ * <p>You can supply a Unicode <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
+ * pattern describing what strings are produced/accepted, but almost all
  * callers should use {@link DateFormat#getDateInstance}, {@link DateFormat#getDateTimeInstance},
  * or {@link DateFormat#getTimeInstance} to get a ready-made instance suitable for the user's
  * locale.
@@ -57,22 +57,26 @@
  * of the ASCII letters is given in the table below. ASCII letters not appearing in the table are
  * reserved for future use, and it is an error to attempt to use them.
  *
+ * <p>The number of consecutive copies (the "count") of a pattern character further influences
+ * the format, as shown in the table. For fields of kind "number", the count is the minimum number
+ * of digits; shorter values are zero-padded to the given width and longer values overflow it.
+ *
  * <p><table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
  * <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor">
- *      <td><B>Symbol</B></td> <td><B>Meaning</B></td> <td><B>Presentation</B></td> <td><B>Example</B></td> </tr>
+ *      <td><B>Symbol</B></td> <td><B>Meaning</B></td> <td><B>Kind</B></td> <td><B>Example</B></td> </tr>
  * <tr> <td>{@code D}</td> <td>day in year</td>             <td>(Number)</td>      <td>189</td> </tr>
- * <tr> <td>{@code E}</td> <td>day of week</td>             <td>(Text)</td>        <td>Tuesday</td> </tr>
+ * <tr> <td>{@code E}</td> <td>day of week</td>             <td>(Text)</td>        <td>{@code E}/{@code EE}/{@code EEE}:Tue, {@code EEEE}:Tuesday, {@code EEEEE}:T</td> </tr>
  * <tr> <td>{@code F}</td> <td>day of week in month</td>    <td>(Number)</td>      <td>2 <i>(2nd Wed in July)</i></td> </tr>
  * <tr> <td>{@code G}</td> <td>era designator</td>          <td>(Text)</td>        <td>AD</td> </tr>
  * <tr> <td>{@code H}</td> <td>hour in day (0-23)</td>      <td>(Number)</td>      <td>0</td> </tr>
  * <tr> <td>{@code K}</td> <td>hour in am/pm (0-11)</td>    <td>(Number)</td>      <td>0</td> </tr>
- * <tr> <td>{@code L}</td> <td>stand-alone month</td>       <td>(Text/Number)</td> <td>July / 07</td> </tr>
- * <tr> <td>{@code M}</td> <td>month in year</td>           <td>(Text/Number)</td> <td>July / 07</td> </tr>
+ * <tr> <td>{@code L}</td> <td>stand-alone month</td>       <td>(Text)</td>        <td>{@code L}:1 {@code LL}:01 {@code LLL}:Jan {@code LLLL}:January {@code LLLLL}:J</td> </tr>
+ * <tr> <td>{@code M}</td> <td>month in year</td>           <td>(Text)</td>        <td>{@code M}:1 {@code MM}:01 {@code MMM}:Jan {@code MMMM}:January {@code MMMMM}:J</td> </tr>
  * <tr> <td>{@code S}</td> <td>fractional seconds</td>      <td>(Number)</td>      <td>978</td> </tr>
  * <tr> <td>{@code W}</td> <td>week in month</td>           <td>(Number)</td>      <td>2</td> </tr>
- * <tr> <td>{@code Z}</td> <td>time zone (RFC 822)</td>     <td>(Timezone)</td>    <td>-0800</td> </tr>
+ * <tr> <td>{@code Z}</td> <td>time zone (RFC 822)</td>     <td>(Time Zone)</td>   <td>{@code Z}/{@code ZZ}/{@code ZZZ}:-0800 {@code ZZZZ}:GMT-08:00 {@code ZZZZZ}:-08:00</td> </tr>
  * <tr> <td>{@code a}</td> <td>am/pm marker</td>            <td>(Text)</td>        <td>PM</td> </tr>
- * <tr> <td>{@code c}</td> <td>stand-alone day of week</td> <td>(Text/Number)</td> <td>Tuesday / 2</td> </tr>
+ * <tr> <td>{@code c}</td> <td>stand-alone day of week</td> <td>(Text)</td>        <td>{@code c}/{@code cc}/{@code ccc}:Tue, {@code cccc}:Tuesday, {@code ccccc}:T</td> </tr>
  * <tr> <td>{@code d}</td> <td>day in month</td>            <td>(Number)</td>      <td>10</td> </tr>
  * <tr> <td>{@code h}</td> <td>hour in am/pm (1-12)</td>    <td>(Number)</td>      <td>12</td> </tr>
  * <tr> <td>{@code k}</td> <td>hour in day (1-24)</td>      <td>(Number)</td>      <td>24</td> </tr>
@@ -85,27 +89,7 @@
  * <tr> <td>{@code ''}</td> <td>single quote</td>           <td>(Literal)</td>     <td>{@code 'o''clock'}:o'clock</td> </tr>
  * </table>
  *
- * <p>The number of consecutive copies (the "count") of a pattern character further influences
- * the format.
- * <ul>
- * <li><b>Text</b> if the count is 4 or more, use the full form; otherwise use a short or
- * abbreviated form if one exists. So {@code zzzz} might give {@code Pacific Standard Time}
- * whereas {@code z} might give {@code PST}. Note that the count does <i>not</i> specify the
- * exact width of the field.
- *
- * <li><b>Number</b> the count is the minimum number of digits. Shorter values are
- * zero-padded to this width, longer values overflow this width.
- *
- * <p>Years are handled specially: {@code yy} truncates to the last 2 digits, but any
- * other number of consecutive {@code y}s does not truncate. So where {@code yyyy} or
- * {@code y} might give {@code 2010}, {@code yy} would give {@code 10}.
- *
- * <p>Fractional seconds are also handled specially: they're zero-padded on the
- * <i>right</i>.
- *
- * <li><b>Text/Number</b>: if the count is 3 or more, use text; otherwise use a number.
- * So {@code MM} might give {@code 07} while {@code MMM} gives {@code July}.
- * </ul>
+ * <p>Fractional seconds are handled specially: they're zero-padded on the <i>right</i>.
  *
  * <p>The two pattern characters {@code L} and {@code c} are ICU-compatible extensions, not
  * available in the RI or in Android before Android 2.3 "Gingerbread" (API level 9). These
@@ -115,7 +99,7 @@
  * form (because the usual case is to format a complete date). The relationship between {@code E}
  * and {@code c} is equivalent, but for weekday names.
  *
- * <p>When numeric fields are adjacent directly, with no intervening delimiter
+ * <p>When two numeric fields are directly adjacent with no intervening delimiter
  * characters, they constitute a run of adjacent numeric fields. Such runs are
  * parsed specially. For example, the format "HHmmss" parses the input text
  * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
@@ -138,11 +122,11 @@
  *   DateFormat.getTimeInstance(),
  * };
  * for (DateFormat df : formats) {
- *   System.err.println(df.format(new Date(0)));
+ *   System.out.println(df.format(new Date(0)));
  * }
  * </pre>
  *
- * <p>Produces this output when run on an {@code en_US} device in the PDT time zone:
+ * <p>Produces this output when run on an {@code en_US} device in the America/Los_Angeles time zone:
  * <pre>
  * Dec 31, 1969
  * Dec 31, 1969 4:00:00 PM
@@ -161,13 +145,13 @@
  * };
  * for (String format : formats) {
  *   SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
- *   System.err.format("%30s %s\n", format, sdf.format(new Date(0)));
+ *   System.out.format("%30s %s\n", format, sdf.format(new Date(0)));
  *   sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
- *   System.err.format("%30s %s\n", format, sdf.format(new Date(0)));
+ *   System.out.format("%30s %s\n", format, sdf.format(new Date(0)));
  * }
  * </pre>
  *
- * <p>Which produces this output when run in the PDT time zone:
+ * <p>Which produces this output when run in the America/Los_Angeles time zone:
  * <pre>
  *                     yyyy-MM-dd 1969-12-31
  *                     yyyy-MM-dd 1970-01-01
@@ -191,7 +175,10 @@
  * the original {@code Date}. For this
  * reason it is almost always necessary and desirable to include the timezone in the output.
  * It may also be desirable to set the formatter's time zone to UTC (to ease comparison, or to
- * make logs more readable, for example).
+ * make logs more readable, for example). It is often best to avoid formatting completely when
+ * writing dates/times in machine-readable form. Simply sending the "Unix time" as a {@code long}
+ * or as the string corresponding to the long is cheaper and unambiguous, and can be formatted any
+ * way the recipient deems appropriate.
  *
  * <h4>Synchronization</h4>
  * {@code SimpleDateFormat} is not thread-safe. Users should create a separate instance for
@@ -480,8 +467,7 @@
         if (object instanceof Number) {
             return formatToCharacterIteratorImpl(new Date(((Number) object).longValue()));
         }
-        throw new IllegalArgumentException();
-
+        throw new IllegalArgumentException("Bad class: " + object.getClass());
     }
 
     private AttributedCharacterIterator formatToCharacterIteratorImpl(Date date) {
@@ -530,8 +516,7 @@
      *            if the object cannot be formatted by this Format.
      */
     private StringBuffer formatImpl(Date date, StringBuffer buffer,
-            FieldPosition field, List<FieldPosition> fields) {
-
+                                    FieldPosition field, List<FieldPosition> fields) {
         boolean quote = false;
         int next, last = -1, count = 0;
         calendar.setTime(date);
@@ -611,24 +596,24 @@
                     appendNumber(buffer, count, year);
                 }
                 break;
-            case STAND_ALONE_MONTH_FIELD: // L
+            case STAND_ALONE_MONTH_FIELD: // 'L'
                 dateFormatField = Field.MONTH;
-                appendMonth(buffer, count, formatData.longStandAloneMonths, formatData.shortStandAloneMonths);
+                appendMonth(buffer, count, true);
                 break;
-            case MONTH_FIELD: // M
+            case MONTH_FIELD: // 'M'
                 dateFormatField = Field.MONTH;
-                appendMonth(buffer, count, formatData.months, formatData.shortMonths);
+                appendMonth(buffer, count, false);
                 break;
             case DATE_FIELD:
                 dateFormatField = Field.DAY_OF_MONTH;
                 field = Calendar.DATE;
                 break;
-            case HOUR_OF_DAY1_FIELD: // k
+            case HOUR_OF_DAY1_FIELD: // 'k'
                 dateFormatField = Field.HOUR_OF_DAY1;
                 int hour = calendar.get(Calendar.HOUR_OF_DAY);
                 appendNumber(buffer, count, hour == 0 ? 24 : hour);
                 break;
-            case HOUR_OF_DAY0_FIELD: // H
+            case HOUR_OF_DAY0_FIELD: // 'H'
                 dateFormatField = Field.HOUR_OF_DAY0;
                 field = Calendar.HOUR_OF_DAY;
                 break;
@@ -647,11 +632,11 @@
                 break;
             case STAND_ALONE_DAY_OF_WEEK_FIELD:
                 dateFormatField = Field.DAY_OF_WEEK;
-                appendDayOfWeek(buffer, count, formatData.longStandAloneWeekdays, formatData.shortStandAloneWeekdays);
+                appendDayOfWeek(buffer, count, true);
                 break;
             case DAY_OF_WEEK_FIELD:
                 dateFormatField = Field.DAY_OF_WEEK;
-                appendDayOfWeek(buffer, count, formatData.weekdays, formatData.shortWeekdays);
+                appendDayOfWeek(buffer, count, false);
                 break;
             case DAY_OF_YEAR_FIELD:
                 dateFormatField = Field.DAY_OF_YEAR;
@@ -673,22 +658,22 @@
                 dateFormatField = Field.AM_PM;
                 buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
                 break;
-            case HOUR1_FIELD: // h
+            case HOUR1_FIELD: // 'h'
                 dateFormatField = Field.HOUR1;
                 hour = calendar.get(Calendar.HOUR);
                 appendNumber(buffer, count, hour == 0 ? 12 : hour);
                 break;
-            case HOUR0_FIELD: // K
+            case HOUR0_FIELD: // 'K'
                 dateFormatField = Field.HOUR0;
                 field = Calendar.HOUR;
                 break;
-            case TIMEZONE_FIELD: // z
+            case TIMEZONE_FIELD: // 'z'
                 dateFormatField = Field.TIME_ZONE;
                 appendTimeZone(buffer, count, true);
                 break;
-            case RFC_822_TIMEZONE_FIELD: // Z
+            case RFC_822_TIMEZONE_FIELD: // 'Z'
                 dateFormatField = Field.TIME_ZONE;
-                appendNumericTimeZone(buffer, false);
+                appendNumericTimeZone(buffer, count, false);
                 break;
         }
         if (field != -1) {
@@ -711,22 +696,38 @@
         }
     }
 
-    private void appendDayOfWeek(StringBuffer buffer, int count, String[] longs, String[] shorts) {
-        boolean isLong = (count > 3);
-        String[] days = isLong ? longs : shorts;
-        buffer.append(days[calendar.get(Calendar.DAY_OF_WEEK)]);
+    // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
+    private void appendDayOfWeek(StringBuffer buffer, int count, boolean standAlone) {
+      String[] days;
+      LocaleData ld = formatData.localeData;
+      if (count == 4) {
+        days = standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays;
+      } else if (count == 5) {
+        days = standAlone ? ld.tinyStandAloneWeekdayNames : formatData.localeData.tinyWeekdayNames;
+      } else {
+        days = standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays;
+      }
+      buffer.append(days[calendar.get(Calendar.DAY_OF_WEEK)]);
     }
 
-    private void appendMonth(StringBuffer buffer, int count, String[] longs, String[] shorts) {
-        int month = calendar.get(Calendar.MONTH);
-        if (count <= 2) {
-            appendNumber(buffer, count, month + 1);
-            return;
-        }
+    // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
+    private void appendMonth(StringBuffer buffer, int count, boolean standAlone) {
+      int month = calendar.get(Calendar.MONTH);
+      if (count <= 2) {
+        appendNumber(buffer, count, month + 1);
+        return;
+      }
 
-        boolean isLong = (count > 3);
-        String[] months = isLong ? longs : shorts;
-        buffer.append(months[month]);
+      String[] months;
+      LocaleData ld = formatData.localeData;
+      if (count == 4) {
+        months = standAlone ? ld.longStandAloneMonthNames : formatData.months;
+      } else if (count == 5) {
+        months = standAlone ? ld.tinyStandAloneMonthNames : ld.tinyMonthNames;
+      } else {
+        months = standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths;
+      }
+      buffer.append(months[month]);
     }
 
     /**
@@ -749,32 +750,31 @@
             }
             // We can't call TimeZone.getDisplayName() because it would not use
             // the custom DateFormatSymbols of this SimpleDateFormat.
-            String custom = TimeZones.getDisplayName(formatData.zoneStrings, tz.getID(), daylight, style);
+            String custom = TimeZoneNames.getDisplayName(formatData.zoneStrings, tz.getID(), daylight, style);
             if (custom != null) {
                 buffer.append(custom);
                 return;
             }
         }
         // We didn't find what we were looking for, so default to a numeric time zone.
-        appendNumericTimeZone(buffer, generalTimeZone);
+        appendNumericTimeZone(buffer, count, generalTimeZone);
     }
 
-    /**
-     * @param generalTimeZone "GMT-08:00" rather than "-0800".
-     */
-    private void appendNumericTimeZone(StringBuffer buffer, boolean generalTimeZone) {
+    // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
+    // @param generalTimeZone "GMT-08:00" rather than "-0800".
+    private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) {
         int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
         char sign = '+';
         if (offset < 0) {
             sign = '-';
             offset = -offset;
         }
-        if (generalTimeZone) {
+        if (generalTimeZone || count == 4) {
             buffer.append("GMT");
         }
         buffer.append(sign);
         appendNumber(buffer, 2, offset / 3600000);
-        if (generalTimeZone) {
+        if (generalTimeZone || count >= 4) {
             buffer.append(':');
         }
         appendNumber(buffer, 2, (offset % 3600000) / 60000);
@@ -880,16 +880,14 @@
                     return position.getIndex();
                 }
                 break;
-            case STAND_ALONE_MONTH_FIELD: // L
-                return parseMonth(string, offset, count, absolute,
-                        formatData.longStandAloneMonths, formatData.shortStandAloneMonths);
-            case MONTH_FIELD: // M
-                return parseMonth(string, offset, count, absolute,
-                        formatData.months, formatData.shortMonths);
+            case STAND_ALONE_MONTH_FIELD: // 'L'
+                return parseMonth(string, offset, count, absolute, true);
+            case MONTH_FIELD: // 'M'
+                return parseMonth(string, offset, count, absolute, false);
             case DATE_FIELD:
                 field = Calendar.DATE;
                 break;
-            case HOUR_OF_DAY1_FIELD: // k
+            case HOUR_OF_DAY1_FIELD: // 'k'
                 ParsePosition position = new ParsePosition(offset);
                 Number result = parseNumber(absolute, string, position);
                 if (result == null) {
@@ -901,7 +899,7 @@
                 }
                 calendar.set(Calendar.HOUR_OF_DAY, hour);
                 return position.getIndex();
-            case HOUR_OF_DAY0_FIELD: // H
+            case HOUR_OF_DAY0_FIELD: // 'H'
                 field = Calendar.HOUR_OF_DAY;
                 break;
             case MINUTE_FIELD:
@@ -914,9 +912,9 @@
                 field = Calendar.MILLISECOND;
                 break;
             case STAND_ALONE_DAY_OF_WEEK_FIELD:
-                return parseDayOfWeek(string, offset, formatData.longStandAloneWeekdays, formatData.shortStandAloneWeekdays);
+                return parseDayOfWeek(string, offset, true);
             case DAY_OF_WEEK_FIELD:
-                return parseDayOfWeek(string, offset, formatData.weekdays, formatData.shortWeekdays);
+                return parseDayOfWeek(string, offset, false);
             case DAY_OF_YEAR_FIELD:
                 field = Calendar.DAY_OF_YEAR;
                 break;
@@ -931,7 +929,7 @@
                 break;
             case AM_PM_FIELD:
                 return parseText(string, offset, formatData.ampms, Calendar.AM_PM);
-            case HOUR1_FIELD: // h
+            case HOUR1_FIELD: // 'h'
                 position = new ParsePosition(offset);
                 result = parseNumber(absolute, string, position);
                 if (result == null) {
@@ -943,12 +941,12 @@
                 }
                 calendar.set(Calendar.HOUR, hour);
                 return position.getIndex();
-            case HOUR0_FIELD: // K
+            case HOUR0_FIELD: // 'K'
                 field = Calendar.HOUR;
                 break;
-            case TIMEZONE_FIELD: // z
+            case TIMEZONE_FIELD: // 'z'
                 return parseTimeZone(string, offset);
-            case RFC_822_TIMEZONE_FIELD: // Z
+            case RFC_822_TIMEZONE_FIELD: // 'Z'
                 return parseTimeZone(string, offset);
         }
         if (field != -1) {
@@ -957,23 +955,33 @@
         return offset;
     }
 
-    private int parseDayOfWeek(String string, int offset, String[] longs, String[] shorts) {
-        int index = parseText(string, offset, longs, Calendar.DAY_OF_WEEK);
-        if (index < 0) {
-            index = parseText(string, offset, shorts, Calendar.DAY_OF_WEEK);
-        }
-        return index;
+    private int parseDayOfWeek(String string, int offset, boolean standAlone) {
+      LocaleData ld = formatData.localeData;
+      int index = parseText(string, offset,
+                            standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays,
+                            Calendar.DAY_OF_WEEK);
+      if (index < 0) {
+        index = parseText(string, offset,
+                          standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays,
+                          Calendar.DAY_OF_WEEK);
+      }
+      return index;
     }
 
-    private int parseMonth(String string, int offset, int count, int absolute, String[] longs, String[] shorts) {
-        if (count <= 2) {
-            return parseNumber(absolute, string, offset, Calendar.MONTH, -1);
-        }
-        int index = parseText(string, offset, longs, Calendar.MONTH);
-        if (index < 0) {
-            index = parseText(string, offset, shorts, Calendar.MONTH);
-        }
-        return index;
+    private int parseMonth(String string, int offset, int count, int absolute, boolean standAlone) {
+      if (count <= 2) {
+        return parseNumber(absolute, string, offset, Calendar.MONTH, -1);
+      }
+      LocaleData ld = formatData.localeData;
+      int index = parseText(string, offset,
+                            standAlone ? ld.longStandAloneMonthNames : formatData.months,
+                            Calendar.MONTH);
+      if (index < 0) {
+        index = parseText(string, offset,
+                          standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths,
+                          Calendar.MONTH);
+      }
+      return index;
     }
 
     /**
@@ -1152,8 +1160,7 @@
             offset += 3;
         }
         char sign;
-        if (offset < string.length()
-                && ((sign = string.charAt(offset)) == '+' || sign == '-')) {
+        if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) {
             ParsePosition position = new ParsePosition(offset + 1);
             Number result = numberFormat.parse(string, position);
             if (result == null) {
@@ -1183,16 +1190,21 @@
             calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
             return offset;
         }
-        String[][] zones = formatData.internalZoneStrings();
-        for (String[] element : zones) {
-            for (int j = TimeZones.LONG_NAME; j < TimeZones.NAME_COUNT; j++) {
-                if (string.regionMatches(true, offset, element[j], 0, element[j].length())) {
-                    TimeZone zone = TimeZone.getTimeZone(element[TimeZones.OLSON_NAME]);
+        for (String[] row : formatData.internalZoneStrings()) {
+            for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) {
+                if (row[i] == null) {
+                    // If icu4c doesn't have a name, our array contains a null. Normally we'd
+                    // work out the correct GMT offset, but we already handled parsing GMT offsets
+                    // above, so we can just ignore these cases. http://b/8128460.
+                    continue;
+                }
+                if (string.regionMatches(true, offset, row[i], 0, row[i].length())) {
+                    TimeZone zone = TimeZone.getTimeZone(row[TimeZoneNames.OLSON_NAME]);
                     if (zone == null) {
                         return -offset - 1;
                     }
                     int raw = zone.getRawOffset();
-                    if (j == TimeZones.LONG_NAME_DST || j == TimeZones.SHORT_NAME_DST) {
+                    if (i == TimeZoneNames.LONG_NAME_DST || i == TimeZoneNames.SHORT_NAME_DST) {
                         // Not all time zones use a one-hour difference, so we need to query
                         // the TimeZone. (Australia/Lord_Howe is the usual example of this.)
                         int dstSavings = zone.getDSTSavings();
@@ -1207,7 +1219,7 @@
                         raw += dstSavings;
                     }
                     calendar.setTimeZone(new SimpleTimeZone(raw, ""));
-                    return offset + element[j].length();
+                    return offset + row[i].length();
                 }
             }
         }
diff --git a/luni/src/main/java/java/util/AbstractCollection.java b/luni/src/main/java/java/util/AbstractCollection.java
index 7cbdcc0..c9b4f8f 100644
--- a/luni/src/main/java/java/util/AbstractCollection.java
+++ b/luni/src/main/java/java/util/AbstractCollection.java
@@ -336,29 +336,20 @@
     public abstract int size();
 
     public Object[] toArray() {
-        int size = size(), index = 0;
-        Iterator<?> it = iterator();
-        Object[] array = new Object[size];
-        while (index < size) {
-            array[index++] = it.next();
-        }
-        return array;
+        return toArrayList().toArray();
+    }
+
+    public <T> T[] toArray(T[] contents) {
+        return toArrayList().toArray(contents);
     }
 
     @SuppressWarnings("unchecked")
-    public <T> T[] toArray(T[] contents) {
-        int size = size(), index = 0;
-        if (size > contents.length) {
-            Class<?> ct = contents.getClass().getComponentType();
-            contents = (T[]) Array.newInstance(ct, size);
-        }
+    private ArrayList<Object> toArrayList() {
+        ArrayList<Object> result = new ArrayList<Object>(size());
         for (E entry : this) {
-            contents[index++] = (T) entry;
+            result.add(entry);
         }
-        if (index < contents.length) {
-            contents[index] = null;
-        }
-        return contents;
+        return result;
     }
 
     /**
diff --git a/luni/src/main/java/java/util/AbstractMap.java b/luni/src/main/java/java/util/AbstractMap.java
index ccfaefe..33eabb3 100644
--- a/luni/src/main/java/java/util/AbstractMap.java
+++ b/luni/src/main/java/java/util/AbstractMap.java
@@ -33,10 +33,10 @@
  * @since 1.2
  */
 public abstract class AbstractMap<K, V> implements Map<K, V> {
-
-    // Lazily initialized key set.
+    // Lazily-initialized key set (for implementing {@link #keySet}).
     Set<K> keySet;
 
+    // Lazily-initialized values collection (for implementing {@link #values}).
     Collection<V> valuesCollection;
 
     /**
diff --git a/luni/src/main/java/java/util/ArrayDeque.java b/luni/src/main/java/java/util/ArrayDeque.java
index 5ee3f81..c4fef89 100644
--- a/luni/src/main/java/java/util/ArrayDeque.java
+++ b/luni/src/main/java/java/util/ArrayDeque.java
@@ -115,7 +115,7 @@
      * when head and tail have wrapped around to become equal.
      */
     private void doubleCapacity() {
-        assert head == tail;
+        // assert head == tail;
         int p = head;
         int n = elements.length;
         int r = n - p; // number of elements to the right of p
@@ -484,11 +484,11 @@
     }
 
     private void checkInvariants() {
-        assert elements[tail] == null;
-        assert head == tail ? elements[head] == null :
-            (elements[head] != null &&
-             elements[(tail - 1) & (elements.length - 1)] != null);
-        assert elements[(head - 1) & (elements.length - 1)] == null;
+        // assert elements[tail] == null;
+        // assert head == tail ? elements[head] == null :
+        //     (elements[head] != null &&
+        //      elements[(tail - 1) & (elements.length - 1)] != null);
+        // assert elements[(head - 1) & (elements.length - 1)] == null;
     }
 
     /**
@@ -502,7 +502,7 @@
      * @return true if elements moved backwards
      */
     private boolean delete(int i) {
-        checkInvariants();
+        //checkInvariants();
         final Object[] elements = this.elements;
         final int mask = elements.length - 1;
         final int h = head;
diff --git a/luni/src/main/java/java/util/ArrayList.java b/luni/src/main/java/java/util/ArrayList.java
index dc7b198..8a3218d 100644
--- a/luni/src/main/java/java/util/ArrayList.java
+++ b/luni/src/main/java/java/util/ArrayList.java
@@ -70,7 +70,7 @@
      */
     public ArrayList(int capacity) {
         if (capacity < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
         array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
     }
@@ -90,6 +90,10 @@
      *            the collection of elements to add.
      */
     public ArrayList(Collection<? extends E> collection) {
+        if (collection == null) {
+            throw new NullPointerException("collection == null");
+        }
+
         Object[] a = collection.toArray();
         if (a.getClass() != Object[].class) {
             Object[] newArray = new Object[a.length];
diff --git a/luni/src/main/java/java/util/Arrays.java b/luni/src/main/java/java/util/Arrays.java
index 4a149b7..c1b2727 100644
--- a/luni/src/main/java/java/util/Arrays.java
+++ b/luni/src/main/java/java/util/Arrays.java
@@ -2400,7 +2400,7 @@
                         }
                     } else {
                         // element is an Object[], so we assert that
-                        assert elem instanceof Object[];
+                        // assert elem instanceof Object[];
                         if (deepToStringImplContains(origArrays, elem)) {
                             sb.append("[...]");
                         } else {
diff --git a/luni/src/main/java/java/util/Calendar.java b/luni/src/main/java/java/util/Calendar.java
index 81d01fb..4ed2ad19 100644
--- a/luni/src/main/java/java/util/Calendar.java
+++ b/luni/src/main/java/java/util/Calendar.java
@@ -712,12 +712,7 @@
     }
 
     /**
-     * Constructs a {@code Calendar} instance using the specified {@code TimeZone} and {@code Locale}.
-     *
-     * @param timezone
-     *            the timezone.
-     * @param locale
-     *            the locale.
+     * Constructs a {@code Calendar} instance using the given {@code TimeZone} and {@code Locale}.
      */
     protected Calendar(TimeZone timezone, Locale locale) {
         this(timezone);
@@ -728,7 +723,7 @@
 
 
     /**
-     * Adds the specified amount to a {@code Calendar} field.
+     * Adds the given amount to a {@code Calendar} field.
      *
      * @param field
      *            the {@code Calendar} field to modify.
@@ -741,8 +736,8 @@
     public abstract void add(int field, int value);
 
     /**
-     * Returns whether the {@code Date} specified by this {@code Calendar} instance is after the {@code Date}
-     * specified by the parameter. The comparison is not dependent on the time
+     * Returns whether the {@code Date} represented by this {@code Calendar} instance is after the {@code Date}
+     * represented by the parameter. The comparison is not dependent on the time
      * zones of the {@code Calendar}.
      *
      * @param calendar
@@ -760,8 +755,8 @@
     }
 
     /**
-     * Returns whether the {@code Date} specified by this {@code Calendar} instance is before the
-     * {@code Date} specified by the parameter. The comparison is not dependent on the
+     * Returns whether the {@code Date} represented by this {@code Calendar} instance is before the
+     * {@code Date} represented by the parameter. The comparison is not dependent on the
      * time zones of the {@code Calendar}.
      *
      * @param calendar
@@ -779,8 +774,9 @@
     }
 
     /**
-     * Clears all of the fields of this {@code Calendar}. All fields are initialized to
-     * zero.
+     * Clears the values of all the time fields, marking them all unset and assigning
+     * them all a value of zero. The actual field values will be determined the next
+     * time the fields are accessed.
      */
     public final void clear() {
         for (int i = 0; i < FIELD_COUNT; i++) {
@@ -791,10 +787,9 @@
     }
 
     /**
-     * Clears the specified field to zero and sets the isSet flag to {@code false}.
-     *
-     * @param field
-     *            the field to clear.
+     * Clears the value in the given time field, marking it unset and assigning
+     * it a value of zero. The actual field value will be determined the next
+     * time the field is accessed.
      */
     public final void clear(int field) {
         fields[field] = 0;
@@ -803,11 +798,7 @@
     }
 
     /**
-     * Returns a new {@code Calendar} with the same properties.
-     *
-     * @return a shallow copy of this {@code Calendar}.
-     *
-     * @see java.lang.Cloneable
+     * Returns a shallow copy of this {@code Calendar} with the same properties.
      */
     @Override
     public Object clone() {
@@ -856,13 +847,11 @@
     protected abstract void computeTime();
 
     /**
-     * Compares the specified object to this {@code Calendar} and returns whether they are
+     * Compares the given object to this {@code Calendar} and returns whether they are
      * equal. The object must be an instance of {@code Calendar} and have the same
      * properties.
      *
-     * @param object
-     *            the object to compare with this object.
-     * @return {@code true} if the specified object is equal to this {@code Calendar}, {@code false}
+     * @return {@code true} if the given object is equal to this {@code Calendar}, {@code false}
      *         otherwise.
      */
     @Override
@@ -882,13 +871,9 @@
     }
 
     /**
-     * Gets the value of the specified field after computing the field values by
+     * Returns the value of the given field after computing the field values by
      * calling {@code complete()} first.
      *
-     * @param field
-     *            the field to get.
-     * @return the value of the specified field.
-     *
      * @throws IllegalArgumentException
      *                if the fields are not set, the time is not set, and the
      *                time cannot be computed from the current field values.
@@ -902,7 +887,7 @@
     }
 
     /**
-     * Returns the maximum value of the specified field for the current date.
+     * Returns the maximum value of the given field for the current date.
      * For example, the maximum number of days in the current month.
      */
     public int getActualMaximum(int field) {
@@ -924,11 +909,7 @@
     }
 
     /**
-     * Gets the minimum value of the specified field for the current date.
-     *
-     * @param field
-     *            the field.
-     * @return the minimum value of the specified field.
+     * Returns the minimum value of the given field for the current date.
      */
     public int getActualMinimum(int field) {
         int value, next;
@@ -958,31 +939,22 @@
     }
 
     /**
-     * Gets the first day of the week for this {@code Calendar}.
-     *
-     * @return the first day of the week.
+     * Returns the first day of the week for this {@code Calendar}.
      */
     public int getFirstDayOfWeek() {
         return firstDayOfWeek;
     }
 
     /**
-     * Gets the greatest minimum value of the specified field. This is the
+     * Returns the greatest minimum value of the given field. This is the
      * biggest value that {@code getActualMinimum} can return for any possible
      * time.
-     *
-     * @param field
-     *            the field.
-     * @return the greatest minimum value of the specified field.
      */
     public abstract int getGreatestMinimum(int field);
 
     /**
      * Constructs a new instance of the {@code Calendar} subclass appropriate for the
-     * default {@code Locale}.
-     *
-     * @return a {@code Calendar} subclass instance set to the current date and time in
-     *         the default {@code Timezone}.
+     * default {@code Locale} and default {@code TimeZone}, set to the current date and time.
      */
     public static synchronized Calendar getInstance() {
         return new GregorianCalendar();
@@ -990,11 +962,7 @@
 
     /**
      * Constructs a new instance of the {@code Calendar} subclass appropriate for the
-     * specified {@code Locale}.
-     *
-     * @param locale
-     *            the locale to use.
-     * @return a {@code Calendar} subclass instance set to the current date and time.
+     * given {@code Locale} and default {@code TimeZone}, set to the current date and time.
      */
     public static synchronized Calendar getInstance(Locale locale) {
         return new GregorianCalendar(locale);
@@ -1002,12 +970,7 @@
 
     /**
      * Constructs a new instance of the {@code Calendar} subclass appropriate for the
-     * default {@code Locale}, using the specified {@code TimeZone}.
-     *
-     * @param timezone
-     *            the {@code TimeZone} to use.
-     * @return a {@code Calendar} subclass instance set to the current date and time in
-     *         the specified timezone.
+     * default {@code Locale} and given {@code TimeZone}, set to the current date and time.
      */
     public static synchronized Calendar getInstance(TimeZone timezone) {
         return new GregorianCalendar(timezone);
@@ -1015,63 +978,40 @@
 
     /**
      * Constructs a new instance of the {@code Calendar} subclass appropriate for the
-     * specified {@code Locale}.
-     *
-     * @param timezone
-     *            the {@code TimeZone} to use.
-     * @param locale
-     *            the {@code Locale} to use.
-     * @return a {@code Calendar} subclass instance set to the current date and time in
-     *         the specified timezone.
+     * given {@code Locale} and given {@code TimeZone}, set to the current date and time.
      */
     public static synchronized Calendar getInstance(TimeZone timezone, Locale locale) {
         return new GregorianCalendar(timezone, locale);
     }
 
     /**
-     * Gets the smallest maximum value of the specified field. This is the
+     * Returns the smallest maximum value of the given field. This is the
      * smallest value that {@code getActualMaximum()} can return for any
      * possible time.
-     *
-     * @param field
-     *            the field number.
-     * @return the smallest maximum value of the specified field.
      */
     public abstract int getLeastMaximum(int field);
 
     /**
-     * Gets the greatest maximum value of the specified field. This returns the
-     * biggest value that {@code get} can return for the specified field.
-     *
-     * @param field
-     *            the field.
-     * @return the greatest maximum value of the specified field.
+     * Returns the greatest maximum value of the given field. This returns the
+     * biggest value that {@code get} can return for the given field.
      */
     public abstract int getMaximum(int field);
 
     /**
-     * Gets the minimal days in the first week of the year.
-     *
-     * @return the minimal days in the first week of the year.
+     * Returns the minimal days in the first week of the year.
      */
     public int getMinimalDaysInFirstWeek() {
         return minimalDaysInFirstWeek;
     }
 
     /**
-     * Gets the smallest minimum value of the specified field. this returns the
-     * smallest value thet {@code get} can return for the specified field.
-     *
-     * @param field
-     *            the field number.
-     * @return the smallest minimum value of the specified field.
+     * Returns the smallest minimum value of the given field. this returns the
+     * smallest value that {@code get} can return for the given field.
      */
     public abstract int getMinimum(int field);
 
     /**
-     * Gets the time of this {@code Calendar} as a {@code Date} object.
-     *
-     * @return a new {@code Date} initialized to the time of this {@code Calendar}.
+     * Returns the time of this {@code Calendar} as a {@code Date} object.
      *
      * @throws IllegalArgumentException
      *                if the time is not set and the time cannot be computed
@@ -1082,9 +1022,8 @@
     }
 
     /**
-     * Computes the time from the fields if required and returns the time.
-     *
-     * @return the time of this {@code Calendar}.
+     * Returns the time represented by this {@code Calendar}, recomputing the time from its
+     * fields if necessary.
      *
      * @throws IllegalArgumentException
      *                if the time is not set and the time cannot be computed
@@ -1099,22 +1038,12 @@
     }
 
     /**
-     * Gets the timezone of this {@code Calendar}.
-     *
-     * @return the {@code TimeZone} used by this {@code Calendar}.
+     * Returns the time zone used by this {@code Calendar}.
      */
     public TimeZone getTimeZone() {
         return zone;
     }
 
-    /**
-     * Returns an integer hash code for the receiver. Objects which are equal
-     * return the same value for this method.
-     *
-     * @return the receiver's hash.
-     *
-     * @see #equals
-     */
     @Override
     public int hashCode() {
         return (isLenient() ? 1237 : 1231) + getFirstDayOfWeek()
@@ -1122,28 +1051,22 @@
     }
 
     /**
-     * Gets the value of the specified field without recomputing.
-     *
-     * @param field
-     *            the field.
-     * @return the value of the specified field.
+     * Returns the value of the given field without recomputing.
      */
     protected final int internalGet(int field) {
         return fields[field];
     }
 
     /**
-     * Returns if this {@code Calendar} accepts field values which are outside the valid
+     * Tests whether this {@code Calendar} accepts field values which are outside the valid
      * range for the field.
-     *
-     * @return {@code true} if this {@code Calendar} is lenient, {@code false} otherwise.
      */
     public boolean isLenient() {
         return lenient;
     }
 
     /**
-     * Returns whether the specified field is set. Note that the interpretation of "is set" is
+     * Tests whether the given field is set. Note that the interpretation of "is set" is
      * somewhat technical. In particular, it does <i>not</i> mean that the field's value is up
      * to date. If you want to know whether a field contains an up-to-date value, you must also
      * check {@code areFieldsSet}, making this method somewhat useless unless you're a subclass,
@@ -1153,25 +1076,16 @@
      * of the {@code clear} methods. Thus "set" does not mean "valid". You probably want to call
      * {@code get} -- which will update fields as necessary -- rather than try to make use of
      * this method.
-     *
-     * @param field
-     *            a {@code Calendar} field number.
-     * @return {@code true} if the specified field is set, {@code false} otherwise.
      */
     public final boolean isSet(int field) {
         return isSet[field];
     }
 
     /**
-     * Adds the specified amount to the specified field and wraps the value of
+     * Adds the given amount to the given field and wraps the value of
      * the field when it goes beyond the maximum or minimum value for the
      * current date. Other fields will be adjusted as required to maintain a
      * consistent date.
-     *
-     * @param field
-     *            the field to roll.
-     * @param value
-     *            the amount to add.
      */
     public void roll(int field, int value) {
         boolean increment = value >= 0;
@@ -1182,25 +1096,15 @@
     }
 
     /**
-     * Increment or decrement the specified field and wrap the value of the
+     * Increment or decrement the given field and wrap the value of the
      * field when it goes beyond the maximum or minimum value for the current
      * date. Other fields will be adjusted as required to maintain a consistent
      * date.
-     *
-     * @param field
-     *            the number indicating the field to roll.
-     * @param increment
-     *            {@code true} to increment the field, {@code false} to decrement.
      */
     public abstract void roll(int field, boolean increment);
 
     /**
-     * Sets a field to the specified value.
-     *
-     * @param field
-     *            the code indicating the {@code Calendar} field to modify.
-     * @param value
-     *            the value.
+     * Sets the given field to the given value.
      */
     public void set(int field, int value) {
         fields[field] = value;
@@ -1218,15 +1122,9 @@
     }
 
     /**
-     * Sets the year, month and day of the month fields. Other fields are not
-     * changed.
-     *
-     * @param year
-     *            the year.
-     * @param month
-     *            the month.
-     * @param day
-     *            the day of the month.
+     * Sets the year, month, and day of the month fields.
+     * Other fields are not changed; call {@link #clear} first if this is not desired.
+     * The month value is 0-based, so it may be clearer to use a constant like {@code JANUARY}.
      */
     public final void set(int year, int month, int day) {
         set(YEAR, year);
@@ -1235,66 +1133,37 @@
     }
 
     /**
-     * Sets the year, month, day of the month, hour of day and minute fields.
-     * Other fields are not changed.
-     *
-     * @param year
-     *            the year.
-     * @param month
-     *            the month.
-     * @param day
-     *            the day of the month.
-     * @param hourOfDay
-     *            the hour of day.
-     * @param minute
-     *            the minute.
+     * Sets the year, month, day of the month, hour of day, and minute fields.
+     * Other fields are not changed; call {@link #clear} first if this is not desired.
+     * The month value is 0-based, so it may be clearer to use a constant like {@code JANUARY}.
      */
-    public final void set(int year, int month, int day, int hourOfDay,
-            int minute) {
+    public final void set(int year, int month, int day, int hourOfDay, int minute) {
         set(year, month, day);
         set(HOUR_OF_DAY, hourOfDay);
         set(MINUTE, minute);
     }
 
     /**
-     * Sets the year, month, day of the month, hour of day, minute and second
-     * fields. Other fields are not changed.
-     *
-     * @param year
-     *            the year.
-     * @param month
-     *            the month.
-     * @param day
-     *            the day of the month.
-     * @param hourOfDay
-     *            the hour of day.
-     * @param minute
-     *            the minute.
-     * @param second
-     *            the second.
+     * Sets the year, month, day of the month, hour of day, minute, and second fields.
+     * Other fields are not changed; call {@link #clear} first if this is not desired.
+     * The month value is 0-based, so it may be clearer to use a constant like {@code JANUARY}.
      */
-    public final void set(int year, int month, int day, int hourOfDay,
-            int minute, int second) {
+    public final void set(int year, int month, int day, int hourOfDay, int minute, int second) {
         set(year, month, day, hourOfDay, minute);
         set(SECOND, second);
     }
 
     /**
      * Sets the first day of the week for this {@code Calendar}.
-     *
-     * @param value
-     *            a {@code Calendar} day of the week.
+     * The value should be a day of the week such as {@code MONDAY}.
      */
     public void setFirstDayOfWeek(int value) {
         firstDayOfWeek = value;
     }
 
     /**
-     * Sets this {@code Calendar} to accept field values which are outside the valid
+     * Sets whether this {@code Calendar} accepts field values which are outside the valid
      * range for the field.
-     *
-     * @param value
-     *            a boolean value.
      */
     public void setLenient(boolean value) {
         lenient = value;
@@ -1302,9 +1171,6 @@
 
     /**
      * Sets the minimal days in the first week of the year.
-     *
-     * @param value
-     *            the minimal days in the first week of the year.
      */
     public void setMinimalDaysInFirstWeek(int value) {
         minimalDaysInFirstWeek = value;
@@ -1312,19 +1178,14 @@
 
     /**
      * Sets the time of this {@code Calendar}.
-     *
-     * @param date
-     *            a {@code Date} object.
      */
     public final void setTime(Date date) {
         setTimeInMillis(date.getTime());
     }
 
     /**
-     * Sets the time of this {@code Calendar}.
-     *
-     * @param milliseconds
-     *            the time as the number of milliseconds since Jan. 1, 1970.
+     * Sets the time of this {@code Calendar} to the given Unix time. See {@link Date} for more
+     * about what this means.
      */
     public void setTimeInMillis(long milliseconds) {
         if (!isTimeSet || !areFieldsSet || time != milliseconds) {
@@ -1337,9 +1198,6 @@
 
     /**
      * Sets the {@code TimeZone} used by this Calendar.
-     *
-     * @param timezone
-     *            a {@code TimeZone}.
      */
     public void setTimeZone(TimeZone timezone) {
         zone = timezone;
@@ -1347,7 +1205,7 @@
     }
 
     /**
-     * Returns the string representation of this {@code Calendar}.
+     * Returns a string representation of this {@code Calendar}, showing which fields are set.
      */
     @Override
     public String toString() {
@@ -1373,11 +1231,9 @@
     }
 
     /**
-     * Compares the times of the two {@code Calendar}, which represent the milliseconds
-     * from the January 1, 1970 00:00:00.000 GMT (Gregorian).
+     * Compares the time represented by this {@code Calendar} to that represented by the given
+     * {@code Calendar}.
      *
-     * @param anotherCalendar
-     *            another calendar that this one is compared with.
      * @return 0 if the times of the two {@code Calendar}s are equal, -1 if the time of
      *         this {@code Calendar} is before the other one, 1 if the time of this
      *         {@code Calendar} is after the other one.
diff --git a/luni/src/main/java/java/util/Collections.java b/luni/src/main/java/java/util/Collections.java
index d49ca85..491642b 100644
--- a/luni/src/main/java/java/util/Collections.java
+++ b/luni/src/main/java/java/util/Collections.java
@@ -63,7 +63,7 @@
 
         CopiesList(int length, E object) {
             if (length < 0) {
-                throw new IllegalArgumentException();
+                throw new IllegalArgumentException("length < 0: " + length);
             }
             n = length;
             element = object;
@@ -2648,7 +2648,7 @@
         if (map.isEmpty()) {
             return new SetFromMap<E>(map);
         }
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("map not empty");
     }
 
     /**
@@ -2663,8 +2663,8 @@
     private static class SetFromMap<E> extends AbstractSet<E> implements Serializable {
         private static final long serialVersionUID = 2454657854757543876L;
 
-        // must named as it, to pass serialization compatibility test.
-        private Map<E, Boolean> m;
+        // Must be named as is, to pass serialization compatibility test.
+        private final Map<E, Boolean> m;
 
         private transient Set<E> backingSet;
 
@@ -2741,7 +2741,7 @@
     private static class AsLIFOQueue<E> extends AbstractQueue<E> implements Serializable {
         private static final long serialVersionUID = 1802017725587941708L;
 
-        // must named as it, to pass serialization compatibility test.
+        // Must be named as is, to pass serialization compatibility test.
         private final Deque<E> q;
 
         AsLIFOQueue(final Deque<E> deque) {
@@ -2829,9 +2829,9 @@
 
         private static final long serialVersionUID = 1578914078182001775L;
 
-        Collection<E> c;
+        final Collection<E> c;
 
-        Class<E> type;
+        final Class<E> type;
 
         public CheckedCollection(Collection<E> c, Class<E> type) {
             if (c == null) {
@@ -2915,9 +2915,9 @@
      */
     private static class CheckedListIterator<E> implements ListIterator<E> {
 
-        private ListIterator<E> i;
+        private final ListIterator<E> i;
 
-        private Class<E> type;
+        private final Class<E> type;
 
         /**
          * Constructs a dynamically typesafe view of the specified ListIterator.
@@ -2975,7 +2975,7 @@
 
         private static final long serialVersionUID = 65247728283967356L;
 
-        List<E> l;
+        final List<E> l;
 
         public CheckedList(List<E> l, Class<E> type) {
             super(l, type);
@@ -3076,9 +3076,9 @@
 
         private static final long serialVersionUID = 5742860141034234728L;
 
-        Map<K, V> m;
-        Class<K> keyType;
-        Class<V> valueType;
+        final Map<K, V> m;
+        final Class<K> keyType;
+        final Class<V> valueType;
 
         private CheckedMap(Map<K, V> m, Class<K> keyType, Class<V> valueType) {
             if (m == null) {
@@ -3173,8 +3173,8 @@
          * A dynamically typesafe view of a Map.Entry.
          */
         private static class CheckedEntry<K, V> implements Map.Entry<K, V> {
-            Map.Entry<K, V> e;
-            Class<V> valueType;
+            final Map.Entry<K, V> e;
+            final Class<V> valueType;
 
             public CheckedEntry(Map.Entry<K, V> e, Class<V> valueType) {
                 if (e == null) {
@@ -3209,8 +3209,8 @@
          * A dynamically typesafe view of an entry set.
          */
         private static class CheckedEntrySet<K, V> implements Set<Map.Entry<K, V>> {
-            Set<Map.Entry<K, V>> s;
-            Class<V> valueType;
+            final Set<Map.Entry<K, V>> s;
+            final Class<V> valueType;
 
             public CheckedEntrySet(Set<Map.Entry<K, V>> s, Class<V> valueType) {
                 this.s = s;
@@ -3329,7 +3329,7 @@
      */
     private static class CheckedSortedSet<E> extends CheckedSet<E> implements SortedSet<E> {
         private static final long serialVersionUID = 1599911165492914959L;
-        private SortedSet<E> ss;
+        private final SortedSet<E> ss;
 
         public CheckedSortedSet(SortedSet<E> s, Class<E> type) {
             super(s, type);
@@ -3368,7 +3368,7 @@
     private static class CheckedSortedMap<K, V> extends CheckedMap<K, V>
             implements SortedMap<K, V> {
         private static final long serialVersionUID = 1599671320688067438L;
-        SortedMap<K, V> sm;
+        final SortedMap<K, V> sm;
 
         CheckedSortedMap(SortedMap<K, V> m, Class<K> keyType, Class<V> valueType) {
             super(m, keyType, valueType);
@@ -3399,4 +3399,59 @@
             return sm.lastKey();
         }
     }
+
+    /**
+     * Computes a hash code and applies a supplemental hash function to defend
+     * against poor quality hash functions. This is critical because HashMap
+     * uses power-of-two length hash tables, that otherwise encounter collisions
+     * for hash codes that do not differ in lower or upper bits.
+     * Routine taken from java.util.concurrent.ConcurrentHashMap.hash(int).
+     * @hide
+     */
+    public static int secondaryHash(Object key) {
+        return secondaryHash(key.hashCode());
+    }
+
+    /**
+     * Computes an identity hash code and applies a supplemental hash function to defend
+     * against poor quality hash functions. This is critical because identity hash codes
+     * are currently implemented as object addresses, which will have been aligned by the
+     * underlying memory allocator causing all hash codes to have the same bottom bits.
+     * @hide
+     */
+    public static int secondaryIdentityHash(Object key) {
+        return secondaryHash(System.identityHashCode(key));
+    }
+
+    private static int secondaryHash(int h) {
+        // Spread bits to regularize both segment and index locations,
+        // using variant of single-word Wang/Jenkins hash.
+        h += (h <<  15) ^ 0xffffcd7d;
+        h ^= (h >>> 10);
+        h += (h <<   3);
+        h ^= (h >>>  6);
+        h += (h <<   2) + (h << 14);
+        return h ^ (h >>> 16);
+    }
+
+    /**
+     * Returns the smallest power of two >= its argument, with several caveats:
+     * If the argument is negative but not Integer.MIN_VALUE, the method returns
+     * zero. If the argument is > 2^30 or equal to Integer.MIN_VALUE, the method
+     * returns Integer.MIN_VALUE. If the argument is zero, the method returns
+     * zero.
+     * @hide
+     */
+    public static int roundUpToPowerOfTwo(int i) {
+        i--; // If input is a power of two, shift its high-order bit right.
+
+        // "Smear" the high-order bit all the way to the right.
+        i |= i >>>  1;
+        i |= i >>>  2;
+        i |= i >>>  4;
+        i |= i >>>  8;
+        i |= i >>> 16;
+
+        return i + 1;
+    }
 }
diff --git a/luni/src/main/java/java/util/Date.java b/luni/src/main/java/java/util/Date.java
index 0eab8dc..da9f296 100644
--- a/luni/src/main/java/java/util/Date.java
+++ b/luni/src/main/java/java/util/Date.java
@@ -27,20 +27,27 @@
 import libcore.icu.LocaleData;
 
 /**
- * {@code Date} represents a specific moment in time, to the millisecond.
+ * A specific moment in time, with millisecond precision. Values typically come
+ * from {@link System#currentTimeMillis}, and are always UTC, regardless of the
+ * system's time zone. This is often called "Unix time" or "epoch time".
  *
- * @see System#currentTimeMillis
- * @see Calendar
- * @see GregorianCalendar
- * @see SimpleTimeZone
- * @see TimeZone
+ * <p>Instances of this class are suitable for comparison, but little else.
+ * Use {@link java.text.DateFormat} to format a {@code Date} for display to a human.
+ * Use {@link Calendar} to break down a {@code Date} if you need to extract fields such
+ * as the current month or day of week, or to construct a {@code Date} from a broken-down
+ * time. That is: this class' deprecated display-related functionality is now provided
+ * by {@code DateFormat}, and this class' deprecated computational functionality is
+ * now provided by {@code Calendar}. Both of these other classes (and their subclasses)
+ * allow you to interpret a {@code Date} in a given time zone.
+ *
+ * <p>Note that, surprisingly, instances of this class are mutable.
  */
 public class Date implements Serializable, Cloneable, Comparable<Date> {
 
     private static final long serialVersionUID = 7523967970034938905L;
 
     // Used by parse()
-    private static int creationYear = new Date().getYear();
+    private static final int CREATION_YEAR = new Date().getYear();
 
     private transient long milliseconds;
 
@@ -356,6 +363,10 @@
         return -1;
     }
 
+    private static IllegalArgumentException parseError(String string) {
+        throw new IllegalArgumentException("Parse error: " + string);
+    }
+
     /**
      * Returns the millisecond value of the date and time parsed from the
      * specified {@code String}. Many date/time formats are recognized, including IETF
@@ -406,7 +417,7 @@
             } else if ('0' <= next && next <= '9') {
                 nextState = NUMBERS;
             } else if (!Character.isSpace(next) && ",+-:/".indexOf(next) == -1) {
-                throw new IllegalArgumentException();
+                throw parseError(string);
             }
 
             if (state == NUMBERS && nextState != NUMBERS) {
@@ -426,7 +437,7 @@
                         zoneOffset = sign == '-' ? -digit : digit;
                         sign = 0;
                     } else {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 } else if (digit >= 70) {
                     if (year == -1
@@ -434,7 +445,7 @@
                                     || next == '/' || next == '\r')) {
                         year = digit;
                     } else {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 } else if (next == ':') {
                     if (hour == -1) {
@@ -442,7 +453,7 @@
                     } else if (minute == -1) {
                         minute = digit;
                     } else {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 } else if (next == '/') {
                     if (month == -1) {
@@ -450,7 +461,7 @@
                     } else if (date == -1) {
                         date = digit;
                     } else {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 } else if (Character.isSpace(next) || next == ','
                         || next == '-' || next == '\r') {
@@ -463,30 +474,30 @@
                     } else if (year == -1) {
                         year = digit;
                     } else {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 } else if (year == -1 && month != -1 && date != -1) {
                     year = digit;
                 } else {
-                    throw new IllegalArgumentException();
+                    throw parseError(string);
                 }
             } else if (state == LETTERS && nextState != LETTERS) {
                 String text = buffer.toString().toUpperCase(Locale.US);
                 buffer.setLength(0);
                 if (text.length() == 1) {
-                    throw new IllegalArgumentException();
+                    throw parseError(string);
                 }
                 if (text.equals("AM")) {
                     if (hour == 12) {
                         hour = 0;
                     } else if (hour < 1 || hour > 12) {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 } else if (text.equals("PM")) {
                     if (hour == 12) {
                         hour = 0;
                     } else if (hour < 1 || hour > 12) {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                     hour += 12;
                 } else {
@@ -503,7 +514,7 @@
                         zone = true;
                         zoneOffset = value;
                     } else {
-                        throw new IllegalArgumentException();
+                        throw parseError(string);
                     }
                 }
             }
@@ -531,7 +542,7 @@
             if (second == -1) {
                 second = 0;
             }
-            if (year < (creationYear - 80)) {
+            if (year < (CREATION_YEAR - 80)) {
                 year += 2000;
             } else if (year < 100) {
                 year += 1900;
@@ -549,7 +560,7 @@
             return new Date(year - 1900, month, date, hour, minute, second)
                     .getTime();
         }
-        throw new IllegalArgumentException();
+        throw parseError(string);
     }
 
     /**
diff --git a/luni/src/main/java/java/util/EnumMap.java b/luni/src/main/java/java/util/EnumMap.java
index a721ee3..827fb51 100644
--- a/luni/src/main/java/java/util/EnumMap.java
+++ b/luni/src/main/java/java/util/EnumMap.java
@@ -436,8 +436,8 @@
         if (map instanceof EnumMap) {
             initialization((EnumMap<K, V>) map);
         } else {
-            if (map.size() == 0) {
-                throw new IllegalArgumentException();
+            if (map.isEmpty()) {
+                throw new IllegalArgumentException("map is empty");
             }
             Iterator<K> iter = map.keySet().iterator();
             K enumKey = iter.next();
diff --git a/luni/src/main/java/java/util/EnumSet.java b/luni/src/main/java/java/util/EnumSet.java
index cc8969e..20be6f7 100644
--- a/luni/src/main/java/java/util/EnumSet.java
+++ b/luni/src/main/java/java/util/EnumSet.java
@@ -104,7 +104,7 @@
             return copyOf((EnumSet<E>) c);
         }
         if (c.isEmpty()) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("empty collection");
         }
         Iterator<E> iterator = c.iterator();
         E element = iterator.next();
@@ -284,7 +284,7 @@
      */
     public static <E extends Enum<E>> EnumSet<E> range(E start, E end) {
         if (start.compareTo(end) > 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("start is behind end");
         }
         EnumSet<E> set = EnumSet.noneOf(start.getDeclaringClass());
         set.setRange(start, end);
diff --git a/luni/src/main/java/java/util/EventObject.java b/luni/src/main/java/java/util/EventObject.java
index 058e7e8..0fc92bb 100644
--- a/luni/src/main/java/java/util/EventObject.java
+++ b/luni/src/main/java/java/util/EventObject.java
@@ -41,7 +41,7 @@
      */
     public EventObject(Object source) {
         if (source == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("source == null");
         }
         this.source = source;
     }
diff --git a/luni/src/main/java/java/util/Formatter.java b/luni/src/main/java/java/util/Formatter.java
index caf5c49..d6b772a 100644
--- a/luni/src/main/java/java/util/Formatter.java
+++ b/luni/src/main/java/java/util/Formatter.java
@@ -297,9 +297,9 @@
  * </tr>
  * </table>
  * <p>
- * It's also possible to format dates and times with {@code Formatter}, though you should seriously
- * consider using {@link java.text.SimpleDateFormat} via the factory methods in
- * {@link java.text.DateFormat} instead.
+ * It's also possible to format dates and times with {@code Formatter}, though you should
+ * use {@link java.text.SimpleDateFormat} (probably via the factory methods in
+ * {@link java.text.DateFormat}) instead.
  * The facilities offered by {@code Formatter} are low-level and place the burden of localization
  * on the developer. Using {@link java.text.DateFormat#getDateInstance},
  * {@link java.text.DateFormat#getTimeInstance}, and
@@ -310,11 +310,8 @@
  * which you can get with {@code "%tF"} (2010-01-22), {@code "%tF %tR"} (2010-01-22 13:39),
  * {@code "%tF %tT"} (2010-01-22 13:39:15), or {@code "%tF %tT%z"} (2010-01-22 13:39:15-0800).
  * <p>
- * As with the other conversions, date/time conversion has an uppercase format. Replacing
- * {@code %t} with {@code %T} will uppercase the field according to the rules of the formatter's
- * locale.
- * <p>
- * This table shows the date/time conversions:
+ * This table shows the date/time conversions, but you should use {@link java.text.SimpleDateFormat}
+ * instead:
  * <table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
  * <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor">
  * <TD COLSPAN=4><B>Date/time conversions</B>
@@ -350,7 +347,7 @@
  * </tr>
  * <tr>
  * <td width="5%">{@code tc}</td>
- * <td width="25%">Locale-preferred date and time representation. (See {@link java.text.DateFormat} for more variations.)</td>
+ * <td width="25%">C library <i>asctime(3)</i>-like output. Do not use.</td>
  * <td width="30%">{@code format("%tc", cal);}</td>
  * <td width="30%">{@code Tue Apr 01 16:19:17 CEST 2008}</td>
  * </tr>
@@ -511,6 +508,10 @@
  * <td width="30%">{@code CEST}</td>
  * </tr>
  * </table>
+ * <p>
+ * As with the other conversions, date/time conversion has an uppercase format. Replacing
+ * {@code %t} with {@code %T} will uppercase the field according to the rules of the formatter's
+ * locale.
  * <p><i>Number localization</i>. Some conversions use localized decimal digits rather than the
  * usual ASCII digits. So formatting {@code 123} with {@code %d} will give 123 in English locales
  * but &#x0661;&#x0662;&#x0663; in appropriate Arabic locales, for example. This number localization
diff --git a/luni/src/main/java/java/util/HashMap.java b/luni/src/main/java/java/util/HashMap.java
index 1b2438b..80fbd0c 100644
--- a/luni/src/main/java/java/util/HashMap.java
+++ b/luni/src/main/java/java/util/HashMap.java
@@ -153,7 +153,7 @@
         } else if (capacity > MAXIMUM_CAPACITY) {
             capacity = MAXIMUM_CAPACITY;
         } else {
-            capacity = roundUpToPowerOfTwo(capacity);
+            capacity = Collections.roundUpToPowerOfTwo(capacity);
         }
         makeTable(capacity);
     }
@@ -202,6 +202,9 @@
      * readObject). Also used by LinkedHashMap.
      */
     final void constructorPutAll(Map<? extends K, ? extends V> map) {
+        if (table == EMPTY_TABLE) {
+            doubleCapacity(); // Don't do unchecked puts to a shared table.
+        }
         for (Entry<? extends K, ? extends V> e : map.entrySet()) {
             constructorPut(e.getKey(), e.getValue());
         }
@@ -294,7 +297,8 @@
             return e == null ? null : e.value;
         }
 
-        // Doug Lea's supplemental secondaryHash function (inlined)
+        // Doug Lea's supplemental secondaryHash function (inlined).
+        // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
         int hash = key.hashCode();
         hash ^= (hash >>> 20) ^ (hash >>> 12);
         hash ^= (hash >>> 7) ^ (hash >>> 4);
@@ -323,7 +327,8 @@
             return entryForNullKey != null;
         }
 
-        // Doug Lea's supplemental secondaryHash function (inlined)
+        // Doug Lea's supplemental secondaryHash function (inlined).
+        // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
         int hash = key.hashCode();
         hash ^= (hash >>> 20) ^ (hash >>> 12);
         hash ^= (hash >>> 7) ^ (hash >>> 4);
@@ -339,6 +344,15 @@
         return false;
     }
 
+    // Doug Lea's supplemental secondaryHash function (non-inlined).
+    // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
+    static int secondaryHash(Object key) {
+        int hash = key.hashCode();
+        hash ^= (hash >>> 20) ^ (hash >>> 12);
+        hash ^= (hash >>> 7) ^ (hash >>> 4);
+        return hash;
+    }
+
     /**
      * Returns whether this map contains the specified value.
      *
@@ -387,7 +401,7 @@
             return putValueForNullKey(value);
         }
 
-        int hash = secondaryHash(key.hashCode());
+        int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
@@ -450,7 +464,7 @@
             return;
         }
 
-        int hash = secondaryHash(key.hashCode());
+        int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         HashMapEntry<K, V> first = tab[index];
@@ -519,7 +533,7 @@
      *  <p>This method is called only by putAll.
      */
     private void ensureCapacity(int numMappings) {
-        int newCapacity = roundUpToPowerOfTwo(capacityForInitSize(numMappings));
+        int newCapacity = Collections.roundUpToPowerOfTwo(capacityForInitSize(numMappings));
         HashMapEntry<K, V>[] oldTable = table;
         int oldCapacity = oldTable.length;
         if (newCapacity <= oldCapacity) {
@@ -618,7 +632,7 @@
         if (key == null) {
             return removeNullKey();
         }
-        int hash = secondaryHash(key.hashCode());
+        int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashMapEntry<K, V> e = tab[index], prev = null;
@@ -838,7 +852,7 @@
             return e != null && Objects.equal(value, e.value);
         }
 
-        int hash = secondaryHash(key.hashCode());
+        int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
@@ -866,7 +880,7 @@
             return true;
         }
 
-        int hash = secondaryHash(key.hashCode());
+        int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashMapEntry<K, V> e = tab[index], prev = null;
@@ -962,38 +976,6 @@
         }
     }
 
-    /**
-     * Applies a supplemental hash function to a given hashCode, which defends
-     * against poor quality hash functions. This is critical because HashMap
-     * uses power-of-two length hash tables, that otherwise encounter collisions
-     * for hashCodes that do not differ in lower or upper bits.
-     */
-    private static int secondaryHash(int h) {
-        // Doug Lea's supplemental hash function
-        h ^= (h >>> 20) ^ (h >>> 12);
-        return h ^ (h >>> 7) ^ (h >>> 4);
-    }
-
-    /**
-     * Returns the smallest power of two >= its argument, with several caveats:
-     * If the argument is negative but not Integer.MIN_VALUE, the method returns
-     * zero. If the argument is > 2^30 or equal to Integer.MIN_VALUE, the method
-     * returns Integer.MIN_VALUE. If the argument is zero, the method returns
-     * zero.
-     */
-    private static int roundUpToPowerOfTwo(int i) {
-        i--; // If input is a power of two, shift its high-order bit right
-
-        // "Smear" the high-order bit all the way to the right
-        i |= i >>>  1;
-        i |= i >>>  2;
-        i |= i >>>  4;
-        i |= i >>>  8;
-        i |= i >>> 16;
-
-        return i + 1;
-    }
-
     private static final long serialVersionUID = 362498820763181265L;
 
     private static final ObjectStreamField[] serialPersistentFields = {
@@ -1026,7 +1008,7 @@
         } else if (capacity > MAXIMUM_CAPACITY) {
             capacity = MAXIMUM_CAPACITY;
         } else {
-            capacity = roundUpToPowerOfTwo(capacity);
+            capacity = Collections.roundUpToPowerOfTwo(capacity);
         }
         makeTable(capacity);
 
diff --git a/luni/src/main/java/java/util/Hashtable.java b/luni/src/main/java/java/util/Hashtable.java
index a4e24bc..bac5689 100644
--- a/luni/src/main/java/java/util/Hashtable.java
+++ b/luni/src/main/java/java/util/Hashtable.java
@@ -132,7 +132,7 @@
         } else if (capacity > MAXIMUM_CAPACITY) {
             capacity = MAXIMUM_CAPACITY;
         } else {
-            capacity = roundUpToPowerOfTwo(capacity);
+            capacity = Collections.roundUpToPowerOfTwo(capacity);
         }
         makeTable(capacity);
     }
@@ -178,6 +178,9 @@
      * readObject).
      */
     private void constructorPutAll(Map<? extends K, ? extends V> map) {
+        if (table == EMPTY_TABLE) {
+            doubleCapacity(); // Don't do unchecked puts to a shared table.
+        }
         for (Entry<? extends K, ? extends V> e : map.entrySet()) {
             constructorPut(e.getKey(), e.getValue());
         }
@@ -259,11 +262,7 @@
      * @see #put
      */
     public synchronized V get(Object key) {
-        // Doug Lea's supplemental secondaryHash function (inlined)
-        int hash = key.hashCode();
-        hash ^= (hash >>> 20) ^ (hash >>> 12);
-        hash ^= (hash >>> 7) ^ (hash >>> 4);
-
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         for (HashtableEntry<K, V> e = tab[hash & (tab.length - 1)];
                 e != null; e = e.next) {
@@ -287,11 +286,7 @@
      * @see java.lang.Object#equals
      */
     public synchronized boolean containsKey(Object key) {
-        // Doug Lea's supplemental secondaryHash function (inlined)
-        int hash = key.hashCode();
-        hash ^= (hash >>> 20) ^ (hash >>> 12);
-        hash ^= (hash >>> 7) ^ (hash >>> 4);
-
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         for (HashtableEntry<K, V> e = tab[hash & (tab.length - 1)];
                 e != null; e = e.next) {
@@ -366,7 +361,7 @@
         } else if (value == null) {
             throw new NullPointerException("value == null");
         }
-        int hash = secondaryHash(key.hashCode());
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         HashtableEntry<K, V> first = tab[index];
@@ -402,7 +397,7 @@
         } else if (value == null) {
             throw new NullPointerException("value == null");
         }
-        int hash = secondaryHash(key.hashCode());
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         HashtableEntry<K, V> first = tab[index];
@@ -442,7 +437,7 @@
      *  <p>This method is called only by putAll.
      */
     private void ensureCapacity(int numMappings) {
-        int newCapacity = roundUpToPowerOfTwo(capacityForInitSize(numMappings));
+        int newCapacity = Collections.roundUpToPowerOfTwo(capacityForInitSize(numMappings));
         HashtableEntry<K, V>[] oldTable = table;
         int oldCapacity = oldTable.length;
         if (newCapacity <= oldCapacity) {
@@ -555,7 +550,7 @@
      * @see #put
      */
     public synchronized V remove(Object key) {
-        int hash = secondaryHash(key.hashCode());
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashtableEntry<K, V> e = tab[index], prev = null;
@@ -799,7 +794,7 @@
      * Returns true if this map contains the specified mapping.
      */
     private synchronized boolean containsMapping(Object key, Object value) {
-        int hash = secondaryHash(key.hashCode());
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashtableEntry<K, V> e = tab[index]; e != null; e = e.next) {
@@ -815,7 +810,7 @@
      * exists; otherwise, returns does nothing and returns false.
      */
     private synchronized boolean removeMapping(Object key, Object value) {
-        int hash = secondaryHash(key.hashCode());
+        int hash = Collections.secondaryHash(key);
         HashtableEntry<K, V>[] tab = table;
         int index = hash & (tab.length - 1);
         for (HashtableEntry<K, V> e = tab[index], prev = null;
@@ -1062,38 +1057,6 @@
         }
     }
 
-    /**
-     * Applies a supplemental hash function to a given hashCode, which defends
-     * against poor quality hash functions. This is critical because Hashtable
-     * uses power-of-two length hash tables, that otherwise encounter collisions
-     * for hashCodes that do not differ in lower or upper bits.
-     */
-    private static int secondaryHash(int h) {
-        // Doug Lea's supplemental hash function
-        h ^= (h >>> 20) ^ (h >>> 12);
-        return h ^ (h >>> 7) ^ (h >>> 4);
-    }
-
-    /**
-     * Returns the smallest power of two >= its argument, with several caveats:
-     * If the argument is negative but not Integer.MIN_VALUE, the method returns
-     * zero. If the argument is > 2^30 or equal to Integer.MIN_VALUE, the method
-     * returns Integer.MIN_VALUE. If the argument is zero, the method returns
-     * zero.
-     */
-    private static int roundUpToPowerOfTwo(int i) {
-        i--; // If input is a power of two, shift its high-order bit right
-
-        // "Smear" the high-order bit all the way to the right
-        i |= i >>>  1;
-        i |= i >>>  2;
-        i |= i >>>  4;
-        i |= i >>>  8;
-        i |= i >>> 16;
-
-        return i + 1;
-    }
-
     private static final long serialVersionUID = 1421746759512286392L;
 
     private static final ObjectStreamField[] serialPersistentFields = {
@@ -1129,7 +1092,7 @@
         } else if (capacity > MAXIMUM_CAPACITY) {
             capacity = MAXIMUM_CAPACITY;
         } else {
-            capacity = roundUpToPowerOfTwo(capacity);
+            capacity = Collections.roundUpToPowerOfTwo(capacity);
         }
         makeTable(capacity);
 
diff --git a/luni/src/main/java/java/util/IdentityHashMap.java b/luni/src/main/java/java/util/IdentityHashMap.java
index e693f7d..eda2d7b 100644
--- a/luni/src/main/java/java/util/IdentityHashMap.java
+++ b/luni/src/main/java/java/util/IdentityHashMap.java
@@ -261,13 +261,12 @@
      *            this map.
      */
     public IdentityHashMap(int maxSize) {
-        if (maxSize >= 0) {
-            this.size = 0;
-            threshold = getThreshold(maxSize);
-            elementData = newElementArray(computeElementArraySize());
-        } else {
-            throw new IllegalArgumentException();
+        if (maxSize < 0) {
+            throw new IllegalArgumentException("maxSize < 0: " + maxSize);
         }
+        size = 0;
+        threshold = getThreshold(maxSize);
+        elementData = newElementArray(computeElementArraySize());
     }
 
     private int getThreshold(int maxSize) {
@@ -442,7 +441,7 @@
     }
 
     private int getModuloHash(Object key, int length) {
-        return ((System.identityHashCode(key) & 0x7FFFFFFF) % (length / 2)) * 2;
+        return ((Collections.secondaryIdentityHash(key) & 0x7FFFFFFF) % (length / 2)) * 2;
     }
 
     /**
diff --git a/luni/src/main/java/java/util/LinkedHashMap.java b/luni/src/main/java/java/util/LinkedHashMap.java
index d8fc02d..e61b0f9 100644
--- a/luni/src/main/java/java/util/LinkedHashMap.java
+++ b/luni/src/main/java/java/util/LinkedHashMap.java
@@ -247,11 +247,8 @@
             return e.value;
         }
 
-        // Doug Lea's supplemental secondaryHash function (inlined)
-        int hash = key.hashCode();
-        hash ^= (hash >>> 20) ^ (hash >>> 12);
-        hash ^= (hash >>> 7) ^ (hash >>> 4);
-
+        // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
+        int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                 e != null; e = e.next) {
diff --git a/luni/src/main/java/java/util/ListResourceBundle.java b/luni/src/main/java/java/util/ListResourceBundle.java
index fc6ab97..7809b9a 100644
--- a/luni/src/main/java/java/util/ListResourceBundle.java
+++ b/luni/src/main/java/java/util/ListResourceBundle.java
@@ -119,7 +119,7 @@
             table = new HashMap<String, Object>(contents.length / 3 * 4 + 3);
             for (Object[] content : contents) {
                 if (content[0] == null || content[1] == null) {
-                    throw new NullPointerException();
+                    throw new NullPointerException("null entry");
                 }
                 table.put((String) content[0], content[1]);
             }
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index 0fbe2f5..d513bd7 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -66,11 +66,12 @@
  * <p>Here are the versions of ICU (and the corresponding CLDR and Unicode versions) used in
  * various Android releases:
  * <table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
- * <tr><td>cupcake/donut/eclair</td> <td>ICU 3.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-5">CLDR 1.5</a></td> <td><a href="http://www.unicode.org/versions/Unicode5.0.0/">Unicode 5.0</a></td></tr>
- * <tr><td>froyo</td>                <td>ICU 4.2</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-7">CLDR 1.7</a></td> <td><a href="http://www.unicode.org/versions/Unicode5.1.0/">Unicode 5.1</a></td></tr>
- * <tr><td>gingerbread/honeycomb</td><td>ICU 4.4</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td> <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr>
- * <tr><td>ice cream sandwich</td>   <td>ICU 4.6</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
- * <tr><td>jelly bean</td>           <td>ICU 4.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
+ * <tr><td>cupcake/donut/eclair</td> <td>ICU 3.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-5">CLDR 1.5</a></td>  <td><a href="http://www.unicode.org/versions/Unicode5.0.0/">Unicode 5.0</a></td></tr>
+ * <tr><td>froyo</td>                <td>ICU 4.2</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-7">CLDR 1.7</a></td>  <td><a href="http://www.unicode.org/versions/Unicode5.1.0/">Unicode 5.1</a></td></tr>
+ * <tr><td>gingerbread/honeycomb</td><td>ICU 4.4</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td>  <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr>
+ * <tr><td>ice cream sandwich</td>   <td>ICU 4.6</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td>  <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
+ * <tr><td>jelly bean</td>           <td>ICU 4.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td>  <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
+ * <tr><td>later</td>                <td>ICU 49</td>  <td><a href="http://cldr.unicode.org/index/downloads/cldr-21">CLDR 21.0</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.1.0/">Unicode 6.1</a></td></tr>
  * </table>
  *
  * <a name="default_locale"><h3>Be wary of the default locale</h3></a>
@@ -270,7 +271,9 @@
      */
     public Locale(String language, String country, String variant) {
         if (language == null || country == null || variant == null) {
-            throw new NullPointerException();
+            throw new NullPointerException("language=" + language +
+                                           ",country=" + country +
+                                           ",variant=" + variant);
         }
         if (language.isEmpty() && country.isEmpty()) {
             languageCode = "";
@@ -397,19 +400,17 @@
             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.
+        // http://b/8049507 --- frameworks/base should use fil_PH instead of tl_PH.
+        // Until then, we're stuck covering their tracks, making it look like they're
+        // using "fil" when they're not.
+        String localeString = toString();
         if (languageCode.equals("tl")) {
-            return "Filipino";
+            localeString = toNewString("fil", countryCode, variantCode);
         }
 
-        String result = ICU.getDisplayLanguageNative(toString(), locale.toString());
+        String result = ICU.getDisplayLanguageNative(localeString, 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());
+            result = ICU.getDisplayLanguageNative(localeString, Locale.getDefault().toString());
         }
         return result;
     }
@@ -563,7 +564,7 @@
      */
     public synchronized static void setDefault(Locale locale) {
         if (locale == null) {
-            throw new NullPointerException();
+            throw new NullPointerException("locale == null");
         }
         defaultLocale = locale;
     }
@@ -582,10 +583,13 @@
     @Override
     public final String toString() {
         String result = cachedToStringResult;
-        return (result == null) ? (cachedToStringResult = toNewString()) : result;
+        if (result == null) {
+            result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode);
+        }
+        return result;
     }
 
-    private String toNewString() {
+    private static String toNewString(String languageCode, String countryCode, String variantCode) {
         // The string form of a locale that only has a variant is the empty string.
         if (languageCode.length() == 0 && countryCode.length() == 0) {
             return "";
diff --git a/luni/src/main/java/java/util/PriorityQueue.java b/luni/src/main/java/java/util/PriorityQueue.java
index e09eb05..cc9bfe6 100644
--- a/luni/src/main/java/java/util/PriorityQueue.java
+++ b/luni/src/main/java/java/util/PriorityQueue.java
@@ -82,7 +82,7 @@
      */
     public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
         if (initialCapacity < 1) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("initialCapacity < 1: " + initialCapacity);
         }
         elements = newElementArray(initialCapacity);
         this.comparator = comparator;
diff --git a/luni/src/main/java/java/util/Random.java b/luni/src/main/java/java/util/Random.java
index b0a92ff..4a67244 100644
--- a/luni/src/main/java/java/util/Random.java
+++ b/luni/src/main/java/java/util/Random.java
@@ -171,18 +171,18 @@
      * in the half-open range [0, n).
      */
     public int nextInt(int n) {
-        if (n > 0) {
-            if ((n & -n) == n) {
-                return (int) ((n * (long) next(31)) >> 31);
-            }
-            int bits, val;
-            do {
-                bits = next(31);
-                val = bits % n;
-            } while (bits - val + (n - 1) < 0);
-            return val;
+        if (n <= 0) {
+            throw new IllegalArgumentException("n <= 0: " + n);
         }
-        throw new IllegalArgumentException();
+        if ((n & -n) == n) {
+            return (int) ((n * (long) next(31)) >> 31);
+        }
+        int bits, val;
+        do {
+            bits = next(31);
+            val = bits % n;
+        } while (bits - val + (n - 1) < 0);
+        return val;
     }
 
     /**
diff --git a/luni/src/main/java/java/util/SimpleTimeZone.java b/luni/src/main/java/java/util/SimpleTimeZone.java
index 93dc88e..0675480 100644
--- a/luni/src/main/java/java/util/SimpleTimeZone.java
+++ b/luni/src/main/java/java/util/SimpleTimeZone.java
@@ -546,11 +546,10 @@
      *            the daylight savings offset in milliseconds.
      */
     public void setDSTSavings(int milliseconds) {
-        if (milliseconds > 0) {
-            dstSavings = milliseconds;
-        } else {
-            throw new IllegalArgumentException();
+        if (milliseconds <= 0) {
+            throw new IllegalArgumentException("milliseconds <= 0: " + milliseconds);
         }
+        dstSavings = milliseconds;
     }
 
     private void checkRange(int month, int dayOfWeek, int time) {
diff --git a/luni/src/main/java/java/util/TimeZone.java b/luni/src/main/java/java/util/TimeZone.java
index 85011bc..848ebbb 100644
--- a/luni/src/main/java/java/util/TimeZone.java
+++ b/luni/src/main/java/java/util/TimeZone.java
@@ -21,7 +21,7 @@
 import java.io.Serializable;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import libcore.icu.TimeZones;
+import libcore.icu.TimeZoneNames;
 import libcore.util.ZoneInfoDB;
 
 /**
@@ -55,14 +55,11 @@
  *
  * <p>Note the type returned by the factory methods {@link #getDefault} and {@link #getTimeZone} is
  * implementation dependent. This may introduce serialization incompatibility issues between
- * different implementations. Android returns instances of {@link SimpleTimeZone} so that
- * the bytes serialized by Android can be deserialized successfully on other
- * implementations, but the reverse compatibility cannot be guaranteed.
+ * different implementations, or different versions of Android.
  *
  * @see Calendar
  * @see GregorianCalendar
  * @see SimpleDateFormat
- * @see SimpleTimeZone
  */
 public abstract class TimeZone implements Serializable, Cloneable {
     private static final long serialVersionUID = 3581463369166924961L;
@@ -171,21 +168,28 @@
      */
     public String getDisplayName(boolean daylightTime, int style, Locale locale) {
         if (style != SHORT && style != LONG) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad style: " + style);
         }
 
-        boolean useDaylight = daylightTime && useDaylightTime();
-
-        String[][] zoneStrings = TimeZones.getZoneStrings(locale);
-        String result = TimeZones.getDisplayName(zoneStrings, getID(), daylightTime, style);
+        String[][] zoneStrings = TimeZoneNames.getZoneStrings(locale);
+        String result = TimeZoneNames.getDisplayName(zoneStrings, getID(), daylightTime, style);
         if (result != null) {
             return result;
         }
 
-        // TODO: do we ever get here?
+        // If we get here, it's because icu4c has nothing for us. Most commonly, this is in the
+        // case of short names. For Pacific/Fiji, for example, icu4c has nothing better to offer
+        // than "GMT+12:00". Why do we re-do this work ourselves? Because we have up-to-date
+        // time zone transition data, which icu4c _doesn't_ use --- it uses its own baked-in copy,
+        // which only gets updated when we update icu4c. http://b/7955614 and http://b/8026776.
+
+        // TODO: should we generate these once, in TimeZoneNames.getDisplayName? Revisit when we
+        // upgrade to icu4c 50 and rewrite the underlying native code. See also the
+        // "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in
+        // DateFormatSymbols.getZoneStrings.
 
         int offset = getRawOffset();
-        if (useDaylight && this instanceof SimpleTimeZone) {
+        if (daylightTime) {
             offset += getDSTSavings();
         }
         offset /= 60000;
diff --git a/luni/src/main/java/java/util/Timer.java b/luni/src/main/java/java/util/Timer.java
index afc745c..25ac432 100644
--- a/luni/src/main/java/java/util/Timer.java
+++ b/luni/src/main/java/java/util/Timer.java
@@ -433,7 +433,7 @@
      */
     public void schedule(TimerTask task, Date when) {
         if (when.getTime() < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("when < 0: " + when.getTime());
         }
         long delay = when.getTime() - System.currentTimeMillis();
         scheduleImpl(task, delay < 0 ? 0 : delay, -1, false);
@@ -454,7 +454,7 @@
      */
     public void schedule(TimerTask task, long delay) {
         if (delay < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("delay < 0: " + delay);
         }
         scheduleImpl(task, delay, -1, false);
     }
diff --git a/luni/src/main/java/java/util/TreeMap.java b/luni/src/main/java/java/util/TreeMap.java
index 9e19933..db37b9d 100644
--- a/luni/src/main/java/java/util/TreeMap.java
+++ b/luni/src/main/java/java/util/TreeMap.java
@@ -441,7 +441,7 @@
             if (parent.left == node) {
                 parent.left = replacement;
             } else {
-                assert (parent.right == node);
+                // assert (parent.right == node);
                 parent.right = replacement;
             }
         } else {
@@ -474,7 +474,7 @@
                 if (rightDelta == -1 || (rightDelta == 0 && !insert)) {
                     rotateLeft(node); // AVL right right
                 } else {
-                    assert (rightDelta == 1);
+                    // assert (rightDelta == 1);
                     rotateRight(right); // AVL right left
                     rotateLeft(node);
                 }
@@ -492,7 +492,7 @@
                 if (leftDelta == 1 || (leftDelta == 0 && !insert)) {
                     rotateRight(node); // AVL left left
                 } else {
-                    assert (leftDelta == -1);
+                    // assert (leftDelta == -1);
                     rotateLeft(left); // AVL left right
                     rotateRight(node);
                 }
@@ -507,7 +507,7 @@
                 }
 
             } else {
-                assert (delta == -1 || delta == 1);
+                // assert (delta == -1 || delta == 1);
                 node.height = Math.max(leftHeight, rightHeight) + 1;
                 if (!insert) {
                     break; // the height hasn't changed, so rebalancing is done!
diff --git a/luni/src/main/java/java/util/UUID.java b/luni/src/main/java/java/util/UUID.java
index 8ac0a63..3594d87 100644
--- a/luni/src/main/java/java/util/UUID.java
+++ b/luni/src/main/java/java/util/UUID.java
@@ -335,13 +335,13 @@
             return this.mostSigBits < uuid.mostSigBits ? -1 : 1;
         }
 
-        assert this.mostSigBits == uuid.mostSigBits;
+        // assert this.mostSigBits == uuid.mostSigBits;
 
         if (this.leastSigBits != uuid.leastSigBits) {
             return this.leastSigBits < uuid.leastSigBits ? -1 : 1;
         }
 
-        assert this.leastSigBits == uuid.leastSigBits;
+        // assert this.leastSigBits == uuid.leastSigBits;
 
         return 0;
     }
diff --git a/luni/src/main/java/java/util/Vector.java b/luni/src/main/java/java/util/Vector.java
index c236be0..65e135a 100644
--- a/luni/src/main/java/java/util/Vector.java
+++ b/luni/src/main/java/java/util/Vector.java
@@ -92,7 +92,7 @@
      */
     public Vector(int capacity, int capacityIncrement) {
         if (capacity < 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
         elementData = newElementArray(capacity);
         elementCount = 0;
@@ -445,7 +445,7 @@
     private void grow(int newCapacity) {
         E[] newData = newElementArray(newCapacity);
         // Assumes elementCount is <= newCapacity
-        assert elementCount <= newCapacity;
+        // assert elementCount <= newCapacity;
         System.arraycopy(elementData, 0, newData, 0, elementCount);
         elementData = newData;
     }
diff --git a/luni/src/main/java/java/util/WeakHashMap.java b/luni/src/main/java/java/util/WeakHashMap.java
index 6417679..2db8695 100644
--- a/luni/src/main/java/java/util/WeakHashMap.java
+++ b/luni/src/main/java/java/util/WeakHashMap.java
@@ -55,7 +55,7 @@
 
     private static final class Entry<K, V> extends WeakReference<K> implements
             Map.Entry<K, V> {
-        int hash;
+        final int hash;
 
         boolean isNull;
 
@@ -70,7 +70,7 @@
         Entry(K key, V object, ReferenceQueue<K> queue) {
             super(key, queue);
             isNull = key == null;
-            hash = isNull ? 0 : key.hashCode();
+            hash = isNull ? 0 : Collections.secondaryHash(key);
             value = object;
         }
 
@@ -198,15 +198,14 @@
      *                if the capacity is less than zero.
      */
     public WeakHashMap(int capacity) {
-        if (capacity >= 0) {
-            elementCount = 0;
-            elementData = newEntryArray(capacity == 0 ? 1 : capacity);
-            loadFactor = 7500; // Default load factor of 0.75
-            computeMaxSize();
-            referenceQueue = new ReferenceQueue<K>();
-        } else {
-            throw new IllegalArgumentException();
+        if (capacity < 0) {
+            throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
+        elementCount = 0;
+        elementData = newEntryArray(capacity == 0 ? 1 : capacity);
+        loadFactor = 7500; // Default load factor of 0.75
+        computeMaxSize();
+        referenceQueue = new ReferenceQueue<K>();
     }
 
     /**
@@ -222,15 +221,17 @@
      *             or equal to zero.
      */
     public WeakHashMap(int capacity, float loadFactor) {
-        if (capacity >= 0 && loadFactor > 0) {
-            elementCount = 0;
-            elementData = newEntryArray(capacity == 0 ? 1 : capacity);
-            this.loadFactor = (int) (loadFactor * 10000);
-            computeMaxSize();
-            referenceQueue = new ReferenceQueue<K>();
-        } else {
-            throw new IllegalArgumentException();
+        if (capacity < 0) {
+            throw new IllegalArgumentException("capacity < 0: " + capacity);
         }
+        if (loadFactor <= 0) {
+            throw new IllegalArgumentException("loadFactor <= 0: " + loadFactor);
+        }
+        elementCount = 0;
+        elementData = newEntryArray(capacity == 0 ? 1 : capacity);
+        this.loadFactor = (int) (loadFactor * 10000);
+        computeMaxSize();
+        referenceQueue = new ReferenceQueue<K>();
     }
 
     /**
@@ -383,26 +384,6 @@
                         }
                     });
                 }
-
-                @Override
-                public Object[] toArray() {
-                    Collection<K> coll = new ArrayList<K>(size());
-
-                    for (Iterator<K> iter = iterator(); iter.hasNext();) {
-                        coll.add(iter.next());
-                    }
-                    return coll.toArray();
-                }
-
-                @Override
-                public <T> T[] toArray(T[] contents) {
-                    Collection<K> coll = new ArrayList<K>(size());
-
-                    for (Iterator<K> iter = iterator(); iter.hasNext();) {
-                        coll.add(iter.next());
-                    }
-                    return coll.toArray(contents);
-                }
             };
         }
         return keySet;
@@ -472,7 +453,7 @@
     public V get(Object key) {
         poll();
         if (key != null) {
-            int index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
+            int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
             Entry<K, V> entry = elementData[index];
             while (entry != null) {
                 if (key.equals(entry.get())) {
@@ -495,7 +476,7 @@
     Entry<K, V> getEntry(Object key) {
         poll();
         if (key != null) {
-            int index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
+            int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
             Entry<K, V> entry = elementData[index];
             while (entry != null) {
                 if (key.equals(entry.get())) {
@@ -609,7 +590,7 @@
         int index = 0;
         Entry<K, V> entry;
         if (key != null) {
-            index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
+            index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
             entry = elementData[index];
             while (entry != null && !key.equals(entry.get())) {
                 entry = entry.next;
@@ -624,7 +605,7 @@
             modCount++;
             if (++elementCount > threshold) {
                 rehash();
-                index = key == null ? 0 : (key.hashCode() & 0x7FFFFFFF)
+                index = key == null ? 0 : (Collections.secondaryHash(key) & 0x7FFFFFFF)
                         % elementData.length;
             }
             entry = new Entry<K, V>(key, value, referenceQueue);
@@ -687,7 +668,7 @@
         int index = 0;
         Entry<K, V> entry, last = null;
         if (key != null) {
-            index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
+            index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
             entry = elementData[index];
             while (entry != null && !key.equals(entry.get())) {
                 last = entry;
diff --git a/luni/src/main/java/java/util/jar/InitManifest.java b/luni/src/main/java/java/util/jar/InitManifest.java
index c82a1e44..ade4790 100644
--- a/luni/src/main/java/java/util/jar/InitManifest.java
+++ b/luni/src/main/java/java/util/jar/InitManifest.java
@@ -37,15 +37,8 @@
     private final UnsafeByteSequence valueBuffer = new UnsafeByteSequence(80);
     private int consecutiveLineBreaks = 0;
 
-    InitManifest(byte[] buf, Attributes main, Attributes.Name ver) throws IOException {
+    InitManifest(byte[] buf, Attributes main) throws IOException {
         this.buf = buf;
-
-        // check a version attribute
-        if (!readHeader() || (ver != null && !name.equals(ver))) {
-            throw new IOException("Missing version attribute: " + ver);
-        }
-
-        main.put(name, value);
         while (readHeader()) {
             main.put(name, value);
         }
diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java
index e129e82..0b270bc 100644
--- a/luni/src/main/java/java/util/jar/JarFile.java
+++ b/luni/src/main/java/java/util/jar/JarFile.java
@@ -337,7 +337,8 @@
                 if (verifier != null
                         && (endsWithIgnoreCase(entryName, ".SF")
                                 || endsWithIgnoreCase(entryName, ".DSA")
-                                || endsWithIgnoreCase(entryName, ".RSA"))) {
+                                || endsWithIgnoreCase(entryName, ".RSA")
+                                || endsWithIgnoreCase(entryName, ".EC"))) {
                     signed = true;
                     InputStream is = super.getInputStream(entry);
                     verifier.addMetaEntry(entryName, Streams.readFully(is));
diff --git a/luni/src/main/java/java/util/jar/JarVerifier.java b/luni/src/main/java/java/util/jar/JarVerifier.java
index ec0e088..187b229 100644
--- a/luni/src/main/java/java/util/jar/JarVerifier.java
+++ b/luni/src/main/java/java/util/jar/JarVerifier.java
@@ -51,6 +51,16 @@
  * </ul>
  */
 class JarVerifier {
+    /**
+     * List of accepted digest algorithms. This list is in order from most
+     * preferred to least preferred.
+     */
+    private static final String[] DIGEST_ALGORITHMS = new String[] {
+        "SHA-512",
+        "SHA-384",
+        "SHA-256",
+        "SHA1",
+    };
 
     private final String jarName;
 
@@ -190,22 +200,17 @@
         }
         Certificate[] certificatesArray = certs.toArray(new Certificate[certs.size()]);
 
-        String algorithms = attributes.getValue("Digest-Algorithms");
-        if (algorithms == null) {
-            algorithms = "SHA SHA1";
-        }
-        StringTokenizer tokens = new StringTokenizer(algorithms);
-        while (tokens.hasMoreTokens()) {
-            String algorithm = tokens.nextToken();
-            String hash = attributes.getValue(algorithm + "-Digest");
+        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+            final String algorithm = DIGEST_ALGORITHMS[i];
+            final String hash = attributes.getValue(algorithm + "-Digest");
             if (hash == null) {
                 continue;
             }
             byte[] hashBytes = hash.getBytes(Charsets.ISO_8859_1);
 
             try {
-                return new VerifierEntry(name, MessageDigest
-                        .getInstance(algorithm), hashBytes, certificatesArray);
+                return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
+                        certificatesArray);
             } catch (NoSuchAlgorithmException e) {
                 // ignored
             }
@@ -254,7 +259,7 @@
         Iterator<String> it = metaEntries.keySet().iterator();
         while (it.hasNext()) {
             String key = it.next();
-            if (key.endsWith(".DSA") || key.endsWith(".RSA")) {
+            if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
                 verifyCertificate(key);
                 // Check for recursive class load
                 if (metaEntries == null) {
@@ -309,12 +314,17 @@
         Attributes attributes = new Attributes();
         HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
         try {
-            InitManifest im = new InitManifest(sfBytes, attributes, Attributes.Name.SIGNATURE_VERSION);
+            InitManifest im = new InitManifest(sfBytes, attributes);
             im.initEntries(entries, null);
         } catch (IOException e) {
             return;
         }
 
+        // Do we actually have any signatures to look at?
+        if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
+            return;
+        }
+
         boolean createdBySigntool = false;
         String createdBy = attributes.getValue("Created-By");
         if (createdBy != null) {
@@ -378,13 +388,8 @@
 
     private boolean verify(Attributes attributes, String entry, byte[] data,
             int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
-        String algorithms = attributes.getValue("Digest-Algorithms");
-        if (algorithms == null) {
-            algorithms = "SHA SHA1";
-        }
-        StringTokenizer tokens = new StringTokenizer(algorithms);
-        while (tokens.hasMoreTokens()) {
-            String algorithm = tokens.nextToken();
+        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+            String algorithm = DIGEST_ALGORITHMS[i];
             String hash = attributes.getValue(algorithm + entry);
             if (hash == null) {
                 continue;
diff --git a/luni/src/main/java/java/util/jar/Manifest.java b/luni/src/main/java/java/util/jar/Manifest.java
index d46aea3..723aa99 100644
--- a/luni/src/main/java/java/util/jar/Manifest.java
+++ b/luni/src/main/java/java/util/jar/Manifest.java
@@ -174,11 +174,10 @@
     }
 
     /**
-     * Writes out the attribute information of the receiver to the specified
-     * {@code OutputStream}.
+     * Writes this {@code Manifest}'s name/attributes pairs to the given {@code OutputStream}.
+     * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
+     * calling this method, or no attributes will be written.
      *
-     * @param os
-     *            The {@code OutputStream} to write to.
      * @throws IOException
      *             If an error occurs writing the {@code Manifest}.
      */
@@ -214,9 +213,7 @@
             buf[buf.length - 1] = '\n';
         }
 
-        // Attributes.Name.MANIFEST_VERSION is not used for
-        // the second parameter for RI compatibility
-        InitManifest im = new InitManifest(buf, mainAttributes, null);
+        InitManifest im = new InitManifest(buf, mainAttributes);
         mainEnd = im.getPos();
         im.initEntries(entries, chunks);
     }
@@ -308,13 +305,18 @@
         CharsetEncoder encoder = Charsets.UTF_8.newEncoder();
         ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
 
-        String version = manifest.mainAttributes.getValue(Attributes.Name.MANIFEST_VERSION);
+        Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
+        String version = manifest.mainAttributes.getValue(versionName);
+        if (version == null) {
+            versionName = Attributes.Name.SIGNATURE_VERSION;
+            version = manifest.mainAttributes.getValue(versionName);
+        }
         if (version != null) {
-            writeEntry(out, Attributes.Name.MANIFEST_VERSION, version, encoder, buffer);
+            writeEntry(out, versionName, version, encoder, buffer);
             Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
             while (entries.hasNext()) {
                 Attributes.Name name = (Attributes.Name) entries.next();
-                if (!name.equals(Attributes.Name.MANIFEST_VERSION)) {
+                if (!name.equals(versionName)) {
                     writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
                 }
             }
@@ -324,11 +326,11 @@
         while (i.hasNext()) {
             String key = i.next();
             writeEntry(out, NAME_ATTRIBUTE, key, encoder, buffer);
-            Attributes attrib = manifest.entries.get(key);
-            Iterator<?> entries = attrib.keySet().iterator();
+            Attributes attributes = manifest.entries.get(key);
+            Iterator<?> entries = attributes.keySet().iterator();
             while (entries.hasNext()) {
                 Attributes.Name name = (Attributes.Name) entries.next();
-                writeEntry(out, name, attrib.getValue(name), encoder, buffer);
+                writeEntry(out, name, attributes.getValue(name), encoder, buffer);
             }
             out.write(LINE_SEPARATOR);
         }
diff --git a/luni/src/main/java/java/util/logging/Handler.java b/luni/src/main/java/java/util/logging/Handler.java
index 13dbdd5..ae7288d 100644
--- a/luni/src/main/java/java/util/logging/Handler.java
+++ b/luni/src/main/java/java/util/logging/Handler.java
@@ -281,14 +281,11 @@
      * Sets the character encoding used by this handler, {@code null} indicates
      * a default encoding.
      *
-     * @param encoding
-     *            the character encoding to set.
-     * @throws UnsupportedEncodingException
-     *             if the specified encoding is not supported by the runtime.
+     * @throws UnsupportedEncodingException if {@code charsetName} is not supported.
      */
-    public void setEncoding(String encoding) throws UnsupportedEncodingException {
+    public void setEncoding(String charsetName) throws UnsupportedEncodingException {
         LogManager.getLogManager().checkAccess();
-        internalSetEncoding(encoding);
+        internalSetEncoding(charsetName);
     }
 
     /**
diff --git a/luni/src/main/java/java/util/logging/StreamHandler.java b/luni/src/main/java/java/util/logging/StreamHandler.java
index 60b4321..4785f13 100644
--- a/luni/src/main/java/java/util/logging/StreamHandler.java
+++ b/luni/src/main/java/java/util/logging/StreamHandler.java
@@ -180,16 +180,13 @@
      * Sets the character encoding used by this handler. A {@code null} value
      * indicates that the default encoding should be used.
      *
-     * @param encoding
-     *            the character encoding to set.
-     * @throws UnsupportedEncodingException
-     *             if the specified encoding is not supported by the runtime.
+     * @throws UnsupportedEncodingException if {@code charsetName} is not supported.
      */
     @Override
-    public void setEncoding(String encoding) throws UnsupportedEncodingException {
-        // flush first before set new encoding
+    public void setEncoding(String charsetName) throws UnsupportedEncodingException {
+        // Flush any existing data first.
         this.flush();
-        super.setEncoding(encoding);
+        super.setEncoding(charsetName);
         // renew writer only if the writer exists
         if (this.writer != null) {
             if (getEncoding() == null) {
diff --git a/luni/src/main/java/java/util/regex/Matcher.java b/luni/src/main/java/java/util/regex/Matcher.java
index cfd4432..320e14c 100644
--- a/luni/src/main/java/java/util/regex/Matcher.java
+++ b/luni/src/main/java/java/util/regex/Matcher.java
@@ -28,9 +28,10 @@
     private Pattern pattern;
 
     /**
-     * Holds the handle for the native version of the pattern.
+     * The address of the native peer.
+     * Uses of this must be manually synchronized to avoid native crashes.
      */
-    private int address;
+    private long address;
 
     /**
      * Holds the input text.
@@ -194,7 +195,7 @@
      */
     private Matcher reset(CharSequence input, int start, int end) {
         if (input == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("input == null");
         }
 
         if (start < 0 || end < 0 || start > input.length() || end > input.length() || start > end) {
@@ -224,16 +225,18 @@
      */
     public Matcher usePattern(Pattern pattern) {
         if (pattern == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("pattern == null");
         }
 
         this.pattern = pattern;
 
-        if (address != 0) {
-            closeImpl(address);
-            address = 0;
+        synchronized (this) {
+            if (address != 0) {
+                closeImpl(address);
+                address = 0; // In case openImpl throws.
+            }
+            address = openImpl(pattern.address);
         }
-        address = openImpl(pattern.address);
 
         if (input != null) {
             resetForInput();
@@ -245,9 +248,11 @@
     }
 
     private void resetForInput() {
-        setInputImpl(address, input, regionStart, regionEnd);
-        useAnchoringBoundsImpl(address, anchoringBounds);
-        useTransparentBoundsImpl(address, transparentBounds);
+        synchronized (this) {
+            setInputImpl(address, input, regionStart, regionEnd);
+            useAnchoringBoundsImpl(address, anchoringBounds);
+            useTransparentBoundsImpl(address, transparentBounds);
+        }
     }
 
     /**
@@ -381,7 +386,9 @@
             throw new IndexOutOfBoundsException("start=" + start + "; length=" + input.length());
         }
 
-        matchFound = findImpl(address, input, start, matchOffsets);
+        synchronized (this) {
+            matchFound = findImpl(address, input, start, matchOffsets);
+        }
         return matchFound;
     }
 
@@ -394,7 +401,9 @@
      * @return true if (and only if) a match has been found.
      */
     public boolean find() {
-        matchFound = findNextImpl(address, input, matchOffsets);
+        synchronized (this) {
+            matchFound = findNextImpl(address, input, matchOffsets);
+        }
         return matchFound;
     }
 
@@ -406,7 +415,9 @@
      * @return true if (and only if) the {@code Pattern} matches.
      */
     public boolean lookingAt() {
-        matchFound = lookingAtImpl(address, input, matchOffsets);
+        synchronized (this) {
+            matchFound = lookingAtImpl(address, input, matchOffsets);
+        }
         return matchFound;
     }
 
@@ -418,7 +429,9 @@
      *         region.
      */
     public boolean matches() {
-        matchFound = matchesImpl(address, input, matchOffsets);
+        synchronized (this) {
+            matchFound = matchesImpl(address, input, matchOffsets);
+        }
         return matchFound;
     }
 
@@ -494,7 +507,9 @@
      * @return the number of groups.
      */
     public int groupCount() {
-        return groupCountImpl(address);
+        synchronized (this) {
+            return groupCountImpl(address);
+        }
     }
 
     /**
@@ -534,8 +549,10 @@
      * @return the {@code Matcher} itself.
      */
     public Matcher useAnchoringBounds(boolean value) {
-        anchoringBounds = value;
-        useAnchoringBoundsImpl(address, value);
+        synchronized (this) {
+            anchoringBounds = value;
+            useAnchoringBoundsImpl(address, value);
+        }
         return this;
     }
 
@@ -562,8 +579,10 @@
      * @return the {@code Matcher} itself.
      */
     public Matcher useTransparentBounds(boolean value) {
-        transparentBounds = value;
-        useTransparentBoundsImpl(address, value);
+        synchronized (this) {
+            transparentBounds = value;
+            useTransparentBoundsImpl(address, value);
+        }
         return this;
     }
 
@@ -593,63 +612,60 @@
     }
 
     /**
-     * Returns this matcher's region start, that is, the first character that is
+     * Returns this matcher's region start, that is, the index of the first character that is
      * considered for a match.
-     *
-     * @return the start of the region.
      */
     public int regionStart() {
         return regionStart;
     }
 
     /**
-     * Returns this matcher's region end, that is, the first character that is
+     * Returns this matcher's region end, that is, the index of the first character that is
      * not considered for a match.
-     *
-     * @return the end of the region.
      */
     public int regionEnd() {
         return regionEnd;
     }
 
     /**
-     * Indicates whether more input might change a successful match into an
+     * Returns true if and only if more input might change a successful match into an
      * unsuccessful one.
-     *
-     * @return true if (and only if) more input might change a successful match
-     *         into an unsuccessful one.
      */
     public boolean requireEnd() {
-        return requireEndImpl(address);
+        synchronized (this) {
+            return requireEndImpl(address);
+        }
     }
 
     /**
-     * Indicates whether the last match hit the end of the input.
-     *
-     * @return true if (and only if) the last match hit the end of the input.
+     * Returns true if and only if the last match hit the end of the input.
      */
     public boolean hitEnd() {
-        return hitEndImpl(address);
+        synchronized (this) {
+            return hitEndImpl(address);
+        }
     }
 
     @Override protected void finalize() throws Throwable {
         try {
-            closeImpl(address);
+            synchronized (this) {
+                closeImpl(address);
+            }
         } finally {
             super.finalize();
         }
     }
 
-    private static native void closeImpl(int addr);
-    private static native boolean findImpl(int addr, String s, int startIndex, int[] offsets);
-    private static native boolean findNextImpl(int addr, String s, int[] offsets);
-    private static native int groupCountImpl(int addr);
-    private static native boolean hitEndImpl(int addr);
-    private static native boolean lookingAtImpl(int addr, String s, int[] offsets);
-    private static native boolean matchesImpl(int addr, String s, int[] offsets);
-    private static native int openImpl(int patternAddr);
-    private static native boolean requireEndImpl(int addr);
-    private static native void setInputImpl(int addr, String s, int start, int end);
-    private static native void useAnchoringBoundsImpl(int addr, boolean value);
-    private static native void useTransparentBoundsImpl(int addr, boolean value);
+    private static native void closeImpl(long addr);
+    private static native boolean findImpl(long addr, String s, int startIndex, int[] offsets);
+    private static native boolean findNextImpl(long addr, String s, int[] offsets);
+    private static native int groupCountImpl(long addr);
+    private static native boolean hitEndImpl(long addr);
+    private static native boolean lookingAtImpl(long addr, String s, int[] offsets);
+    private static native boolean matchesImpl(long addr, String s, int[] offsets);
+    private static native long openImpl(long patternAddr);
+    private static native boolean requireEndImpl(long addr);
+    private static native void setInputImpl(long addr, String s, int start, int end);
+    private static native void useAnchoringBoundsImpl(long addr, boolean value);
+    private static native void useTransparentBoundsImpl(long addr, boolean value);
 }
diff --git a/luni/src/main/java/java/util/regex/Pattern.java b/luni/src/main/java/java/util/regex/Pattern.java
index cbd5965..44b749e 100644
--- a/luni/src/main/java/java/util/regex/Pattern.java
+++ b/luni/src/main/java/java/util/regex/Pattern.java
@@ -285,7 +285,7 @@
     private final String pattern;
     private final int flags;
 
-    transient int address;
+    transient long address;
 
     /**
      * Returns a {@link Matcher} for this pattern applied to the given {@code input}.
@@ -452,6 +452,6 @@
         compile();
     }
 
-    private static native void closeImpl(int addr);
-    private static native int compileImpl(String regex, int flags);
+    private static native void closeImpl(long addr);
+    private static native long compileImpl(String regex, int flags);
 }
diff --git a/luni/src/main/java/java/util/regex/Splitter.java b/luni/src/main/java/java/util/regex/Splitter.java
index d30bada..a4cb52c 100644
--- a/luni/src/main/java/java/util/regex/Splitter.java
+++ b/luni/src/main/java/java/util/regex/Splitter.java
@@ -18,6 +18,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import libcore.util.EmptyArray;
 
 /**
  * Used to make {@code String.split} fast (and to help {@code Pattern.split} too).
@@ -63,16 +64,41 @@
             return new String[] { "" };
         }
 
-        // Collect text preceding each occurrence of the separator, while there's enough space.
-        ArrayList<String> list = new ArrayList<String>();
-        int maxSize = limit <= 0 ? Integer.MAX_VALUE : limit;
+        // Count separators
+        int separatorCount = 0;
         int begin = 0;
         int end;
-        while ((end = input.indexOf(ch, begin)) != -1 && list.size() + 1 < maxSize) {
-            list.add(input.substring(begin, end));
+        while (separatorCount + 1 != limit && (end = input.indexOf(ch, begin)) != -1) {
+            ++separatorCount;
             begin = end + 1;
         }
-        return finishSplit(list, input, begin, maxSize, limit);
+        int lastPartEnd = input.length();
+        if (limit == 0 && begin == lastPartEnd) {
+            // Last part is empty for limit == 0, remove all trailing empty matches.
+            if (separatorCount == lastPartEnd) {
+                // Input contains only separators.
+                return EmptyArray.STRING;
+            }
+            // Find the beginning of trailing separators.
+            do {
+                --begin;
+            } while (input.charAt(begin - 1) == ch);
+            // Reduce separatorCount and fix lastPartEnd.
+            separatorCount -= input.length() - begin;
+            lastPartEnd = begin;
+        }
+
+        // Collect the result parts.
+        String[] result = new String[separatorCount + 1];
+        begin = 0;
+        for (int i = 0; i != separatorCount; ++i) {
+            end = input.indexOf(ch, begin);
+            result[i] = input.substring(begin, end);
+            begin = end + 1;
+        }
+        // Add last part.
+        result[separatorCount] = input.substring(begin, lastPartEnd);
+        return result;
     }
 
     public static String[] split(Pattern pattern, String re, String input, int limit) {
@@ -89,25 +115,23 @@
 
         // Collect text preceding each occurrence of the separator, while there's enough space.
         ArrayList<String> list = new ArrayList<String>();
-        int maxSize = limit <= 0 ? Integer.MAX_VALUE : limit;
         Matcher matcher = new Matcher(pattern, input);
         int begin = 0;
-        while (matcher.find() && list.size() + 1 < maxSize) {
+        while (list.size() + 1 != limit && matcher.find()) {
             list.add(input.substring(begin, matcher.start()));
             begin = matcher.end();
         }
-        return finishSplit(list, input, begin, maxSize, limit);
+        return finishSplit(list, input, begin, limit);
     }
 
-    private static String[] finishSplit(List<String> list, String input, int begin, int maxSize, int limit) {
+    private static String[] finishSplit(List<String> list, String input, int begin, int limit) {
         // Add trailing text.
         if (begin < input.length()) {
             list.add(input.substring(begin));
-        } else if (limit != 0) { // No point adding the empty string if limit == 0, just to remove it below.
+        } else if (limit != 0) {
             list.add("");
-        }
-        // Remove all trailing empty matches in the limit == 0 case.
-        if (limit == 0) {
+        } else {
+            // Remove all trailing empty matches in the limit == 0 case.
             int i = list.size() - 1;
             while (i >= 0 && list.get(i).isEmpty()) {
                 list.remove(i);
diff --git a/luni/src/main/java/java/util/zip/Deflater.java b/luni/src/main/java/java/util/zip/Deflater.java
index 044b976..d96ab2a 100644
--- a/luni/src/main/java/java/util/zip/Deflater.java
+++ b/luni/src/main/java/java/util/zip/Deflater.java
@@ -50,21 +50,37 @@
  * {@link #setInput setInput} repeatedly, but you're much better off using
  * {@link DeflaterOutputStream} to handle all this for you. {@link DeflaterOutputStream} also helps
  * minimize memory requirements&nbsp;&mdash; the sample code above is very expensive.
+ *
+ * <a name="compression_level"><h3>Compression levels</h3></a>
+ * <p>A compression level must be {@link #DEFAULT_COMPRESSION} to compromise between speed and
+ * compression (currently equivalent to level 6), or between 0 ({@link #NO_COMPRESSION}, where
+ * the input is simply copied) and 9 ({@link #BEST_COMPRESSION}). Level 1 ({@link #BEST_SPEED})
+ * performs some compression, but with minimal speed overhead.
  */
 public class Deflater {
 
     /**
-     * Upper bound for the compression level range.
+     * This <a href="#compression_level">compression level</a> gives the best compression,
+     * but takes the most time.
      */
     public static final int BEST_COMPRESSION = 9;
 
     /**
-     * Lower bound for compression level range.
+     * This <a href="#compression_level">compression level</a> gives minimal compression,
+     * but takes the least time (of any level that actually performs compression;
+     * see {@link #NO_COMPRESSION}).
      */
     public static final int BEST_SPEED = 1;
 
     /**
-     * The default compression level.
+     * This <a href="#compression_level">compression level</a> does no compression.
+     * It is even faster than {@link #BEST_SPEED}.
+     */
+    public static final int NO_COMPRESSION = 0;
+
+    /**
+     * The default <a href="#compression_level">compression level</a>.
+     * This is a trade-off between speed and compression, currently equivalent to level 6.
      */
     public static final int DEFAULT_COMPRESSION = -1;
 
@@ -89,11 +105,6 @@
     public static final int HUFFMAN_ONLY = 2;
 
     /**
-     * A compression level.
-     */
-    public static final int NO_COMPRESSION = 0;
-
-    /**
      * Use buffering for best compression.
      *
      * @hide
@@ -150,8 +161,9 @@
     private final CloseGuard guard = CloseGuard.get();
 
     /**
-     * Constructs a new {@code Deflater} instance using the default compression
-     * level. The strategy can be specified with {@link #setStrategy}. A
+     * Constructs a new {@code Deflater} instance using the
+     * default <a href="#compression_level">compression level</a>.
+     * The compression strategy can be specified with {@link #setStrategy}. A
      * header is added to the output by default; use {@link
      * #Deflater(int, boolean)} if you need to omit the header.
      */
@@ -160,32 +172,26 @@
     }
 
     /**
-     * Constructs a new {@code Deflater} instance using compression
-     * level {@code level}. The strategy can be specified with {@link #setStrategy}.
+     * Constructs a new {@code Deflater} instance with the
+     * given <a href="#compression_level">compression level</a>.
+     * The compression strategy can be specified with {@link #setStrategy}.
      * A header is added to the output by default; use
      * {@link #Deflater(int, boolean)} if you need to omit the header.
-     *
-     * @param level
-     *            the compression level in the range between 0 and 9.
      */
     public Deflater(int level) {
         this(level, false);
     }
 
     /**
-     * Constructs a new {@code Deflater} instance with a specific compression
-     * level. If {@code noHeader} is true, no ZLIB header is added to the
-     * output. In a ZIP archive every entry (compressed file) comes with such a
+     * Constructs a new {@code Deflater} instance with the
+     * given <a href="#compression_level">compression level</a>.
+     * If {@code noHeader} is true, no ZLIB header is added to the
+     * output. In a zip file, every entry (compressed file) comes with such a
      * header. The strategy can be specified using {@link #setStrategy}.
-     *
-     * @param level
-     *            the compression level in the range between 0 and 9.
-     * @param noHeader
-     *            {@code true} indicates that no ZLIB header should be written.
      */
     public Deflater(int level, boolean noHeader) {
         if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad level: " + level);
         }
         compressLevel = level;
         streamHandle = createStream(compressLevel, strategy, noHeader);
@@ -414,8 +420,8 @@
     private native void setInputImpl(byte[] buf, int offset, int byteCount, long handle);
 
     /**
-     * Sets the compression level to be used when compressing data. The
-     * compression level must be a value between 0 and 9. This value must be set
+     * Sets the given <a href="#compression_level">compression level</a>
+     * to be used when compressing data. This value must be set
      * prior to calling {@link #setInput setInput}.
      * @exception IllegalArgumentException
      *                If the compression level is invalid.
diff --git a/luni/src/main/java/java/util/zip/DeflaterInputStream.java b/luni/src/main/java/java/util/zip/DeflaterInputStream.java
index 805ce17..d854fec 100644
--- a/luni/src/main/java/java/util/zip/DeflaterInputStream.java
+++ b/luni/src/main/java/java/util/zip/DeflaterInputStream.java
@@ -78,7 +78,7 @@
             throw new NullPointerException("deflater == null");
         }
         if (bufferSize <= 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("bufferSize <= 0: " + bufferSize);
         }
         this.def = deflater;
         this.buf = new byte[bufferSize];
diff --git a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
index d336e72..448f61c 100644
--- a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
+++ b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
@@ -86,11 +86,11 @@
      * @param def
      *            is the specific {@code Deflater} that will be used to compress
      *            data.
-     * @param bsize
+     * @param bufferSize
      *            is the size to be used for the internal buffer.
      */
-    public DeflaterOutputStream(OutputStream os, Deflater def, int bsize) {
-        this(os, def, bsize, false);
+    public DeflaterOutputStream(OutputStream os, Deflater def, int bufferSize) {
+        this(os, def, bufferSize, false);
     }
 
     /**
@@ -113,19 +113,19 @@
      * @hide
      * @since 1.7
      */
-    public DeflaterOutputStream(OutputStream os, Deflater def, int bsize, boolean syncFlush) {
+    public DeflaterOutputStream(OutputStream os, Deflater def, int bufferSize, boolean syncFlush) {
         super(os);
         if (os == null) {
             throw new NullPointerException("os == null");
         } else if (def == null) {
             throw new NullPointerException("def == null");
         }
-        if (bsize <= 0) {
-            throw new IllegalArgumentException();
+        if (bufferSize <= 0) {
+            throw new IllegalArgumentException("bufferSize <= 0: " + bufferSize);
         }
         this.def = def;
         this.syncFlush = syncFlush;
-        buf = new byte[bsize];
+        buf = new byte[bufferSize];
     }
 
     /**
diff --git a/luni/src/main/java/java/util/zip/InflaterInputStream.java b/luni/src/main/java/java/util/zip/InflaterInputStream.java
index 6081037..371c80a 100644
--- a/luni/src/main/java/java/util/zip/InflaterInputStream.java
+++ b/luni/src/main/java/java/util/zip/InflaterInputStream.java
@@ -98,24 +98,24 @@
      *            the {@code InputStream} to read data from.
      * @param inflater
      *            the specific {@code Inflater} for decompressing data.
-     * @param bsize
+     * @param bufferSize
      *            the size to be used for the internal buffer.
      */
-    public InflaterInputStream(InputStream is, Inflater inflater, int bsize) {
+    public InflaterInputStream(InputStream is, Inflater inflater, int bufferSize) {
         super(is);
         if (is == null) {
             throw new NullPointerException("is == null");
         } else if (inflater == null) {
             throw new NullPointerException("inflater == null");
         }
-        if (bsize <= 0) {
-            throw new IllegalArgumentException();
+        if (bufferSize <= 0) {
+            throw new IllegalArgumentException("bufferSize <= 0: " + bufferSize);
         }
         this.inf = inflater;
         if (is instanceof ZipFile.RAFStream) {
-            nativeEndBufSize = bsize;
+            nativeEndBufSize = bufferSize;
         } else {
-            buf = new byte[bsize];
+            buf = new byte[bufferSize];
         }
     }
 
@@ -189,13 +189,8 @@
     protected void fill() throws IOException {
         checkClosed();
         if (nativeEndBufSize > 0) {
-            ZipFile.RAFStream is = (ZipFile.RAFStream)in;
-            synchronized (is.mSharedRaf) {
-                long len = is.mLength - is.mOffset;
-                if (len > nativeEndBufSize) len = nativeEndBufSize;
-                int cnt = inf.setFileInput(is.mSharedRaf.getFD(), is.mOffset, nativeEndBufSize);
-                is.skip(cnt);
-            }
+            ZipFile.RAFStream is = (ZipFile.RAFStream) in;
+            len = is.fill(inf, nativeEndBufSize);
         } else {
             if ((len = in.read(buf)) > 0) {
                 inf.setInput(buf, 0, len);
diff --git a/luni/src/main/java/java/util/zip/InflaterOutputStream.java b/luni/src/main/java/java/util/zip/InflaterOutputStream.java
index 9a699a8..ca8a9508 100644
--- a/luni/src/main/java/java/util/zip/InflaterOutputStream.java
+++ b/luni/src/main/java/java/util/zip/InflaterOutputStream.java
@@ -76,7 +76,7 @@
             throw new NullPointerException("inf == null");
         }
         if (bufferSize <= 0) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("bufferSize <= 0: " + bufferSize);
         }
         this.inf = inf;
         this.buf = new byte[bufferSize];
diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java
index 19680df..e9168b5 100644
--- a/luni/src/main/java/java/util/zip/ZipEntry.java
+++ b/luni/src/main/java/java/util/zip/ZipEntry.java
@@ -30,27 +30,29 @@
 import libcore.io.HeapBufferIterator;
 
 /**
- * An instance of {@code ZipEntry} represents an entry within a <i>ZIP-archive</i>.
- * An entry has attributes such as name (= path) or the size of its data. While
- * an entry identifies data stored in an archive, it does not hold the data
- * itself. For example when reading a <i>ZIP-file</i> you will first retrieve
- * all its entries in a collection and then read the data for a specific entry
- * through an input stream.
- *
- * @see ZipFile
- * @see ZipOutputStream
+ * An entry within a zip file.
+ * An entry has attributes such as its name (which is actually a path) and the uncompressed size
+ * of the corresponding data. An entry does not contain the data itself, but can be used as a key
+ * with {@link ZipFile#getInputStream}. The class documentation for {@link ZipInputStream} and
+ * {@link ZipOutputStream} shows how {@code ZipEntry} is used in conjunction with those two classes.
  */
 public class ZipEntry implements ZipConstants, Cloneable {
-    String name, comment;
+    String name;
+    String comment;
 
-    long compressedSize = -1, crc = -1, size = -1;
+    long crc = -1; // Needs to be a long to distinguish -1 ("not set") from the 0xffffffff CRC32.
 
-    int compressionMethod = -1, time = -1, modDate = -1;
+    long compressedSize = -1;
+    long size = -1;
+
+    int compressionMethod = -1;
+    int time = -1;
+    int modDate = -1;
 
     byte[] extra;
 
     int nameLength = -1;
-    long mLocalHeaderRelOffset = -1;
+    long localHeaderRelOffset = -1;
 
     /**
      * Zip entry state: Deflated.
@@ -63,10 +65,9 @@
     public static final int STORED = 0;
 
     /**
-     * Constructs a new {@code ZipEntry} with the specified name.
+     * Constructs a new {@code ZipEntry} with the specified name. The name is actually a path,
+     * and may contain {@code /} characters.
      *
-     * @param name
-     *            the name of the ZIP entry.
      * @throws IllegalArgumentException
      *             if the name length is outside the range (> 0xFFFF).
      */
@@ -81,11 +82,8 @@
     }
 
     /**
-     * Gets the comment for this {@code ZipEntry}.
-     *
-     * @return the comment for this {@code ZipEntry}, or {@code null} if there
-     *         is no comment. If we're reading an archive with
-     *         {@code ZipInputStream} the comment is not available.
+     * Returns the comment for this {@code ZipEntry}, or {@code null} if there is no comment.
+     * If we're reading a zip file using {@code ZipInputStream}, the comment is not available.
      */
     public String getComment() {
         return comment;
@@ -179,16 +177,19 @@
 
     /**
      * Sets the comment for this {@code ZipEntry}.
-     *
-     * @param comment
-     *            the comment for this entry.
+     * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes.
      */
     public void setComment(String comment) {
-        if (comment == null || comment.length() <= 0xFFFF) {
-            this.comment = comment;
-        } else {
-            throw new IllegalArgumentException();
+        if (comment == null) {
+            this.comment = null;
+            return;
         }
+
+        byte[] commentBytes = comment.getBytes(Charsets.UTF_8);
+        if (commentBytes.length > 0xffff) {
+            throw new IllegalArgumentException("Comment too long: " + commentBytes.length);
+        }
+        this.comment = comment;
     }
 
     /**
@@ -220,25 +221,22 @@
     /**
      * Sets the extra information for this {@code ZipEntry}.
      *
-     * @param data
-     *            a byte array containing the extra information.
-     * @throws IllegalArgumentException
-     *             when the length of data is greater than 0xFFFF bytes.
+     * @throws IllegalArgumentException if the data length >= 64 KiB.
      */
     public void setExtra(byte[] data) {
-        if (data == null || data.length <= 0xFFFF) {
-            extra = data;
-        } else {
-            throw new IllegalArgumentException();
+        if (data != null && data.length > 0xffff) {
+            throw new IllegalArgumentException("Extra data too long: " + data.length);
         }
+        extra = data;
     }
 
     /**
-     * Sets the compression method for this {@code ZipEntry}.
-     *
-     * @param value
-     *            the compression method, either {@code DEFLATED} or {@code
-     *            STORED}.
+     * Sets the compression method for this entry to either {@code DEFLATED} or {@code STORED}.
+     * The default is {@code DEFLATED}, which will cause the size, compressed size, and CRC to be
+     * set automatically, and the entry's data to be compressed. If you switch to {@code STORED}
+     * note that you'll have to set the size (or compressed size; they must be the same, but it's
+     * okay to only set one) and CRC yourself because they must appear <i>before</i> the user data
+     * in the resulting zip file. See {@link #setSize} and {@link #setCrc}.
      * @throws IllegalArgumentException
      *             when value is not {@code DEFLATED} or {@code STORED}.
      */
@@ -317,7 +315,7 @@
         modDate = ze.modDate;
         extra = ze.extra;
         nameLength = ze.nameLength;
-        mLocalHeaderRelOffset = ze.mLocalHeaderRelOffset;
+        localHeaderRelOffset = ze.localHeaderRelOffset;
     }
 
     /**
@@ -378,11 +376,11 @@
 
         nameLength = it.readShort() & 0xffff;
         int extraLength = it.readShort() & 0xffff;
-        int commentLength = it.readShort() & 0xffff;
+        int commentByteCount = it.readShort() & 0xffff;
 
         // This is a 32-bit value in the file, but a 64-bit field in this object.
         it.seek(42);
-        mLocalHeaderRelOffset = ((long) it.readInt()) & 0xffffffffL;
+        localHeaderRelOffset = ((long) it.readInt()) & 0xffffffffL;
 
         byte[] nameBytes = new byte[nameLength];
         Streams.readFully(in, nameBytes, 0, nameBytes.length);
@@ -393,9 +391,9 @@
 
         // The RI has always assumed UTF-8. (If GPBF_UTF8_FLAG isn't set, the encoding is
         // actually IBM-437.)
-        if (commentLength > 0) {
-            byte[] commentBytes = new byte[commentLength];
-            Streams.readFully(in, commentBytes, 0, commentLength);
+        if (commentByteCount > 0) {
+            byte[] commentBytes = new byte[commentByteCount];
+            Streams.readFully(in, commentBytes, 0, commentByteCount);
             comment = new String(commentBytes, 0, commentBytes.length, Charsets.UTF_8);
         }
 
diff --git a/luni/src/main/java/java/util/zip/ZipError.java b/luni/src/main/java/java/util/zip/ZipError.java
index 5718575..d4694d5 100644
--- a/luni/src/main/java/java/util/zip/ZipError.java
+++ b/luni/src/main/java/java/util/zip/ZipError.java
@@ -18,7 +18,7 @@
 package java.util.zip;
 
 /**
- * Thrown when an unrecoverable ZIP error has occurred.
+ * Thrown when an unrecoverable zip error has occurred.
  * @since 1.6
  */
 public class ZipError extends InternalError {
diff --git a/luni/src/main/java/java/util/zip/ZipException.java b/luni/src/main/java/java/util/zip/ZipException.java
index b77943e..27203fc 100644
--- a/luni/src/main/java/java/util/zip/ZipException.java
+++ b/luni/src/main/java/java/util/zip/ZipException.java
@@ -21,7 +21,7 @@
 
 /**
  * This runtime exception is thrown by {@code ZipFile} and {@code
- * ZipInputStream} when the file or stream is not a valid ZIP file.
+ * ZipInputStream} when the file or stream is not a valid zip file.
  *
  * @see ZipFile
  * @see ZipInputStream
diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java
index a79984c..519459b 100644
--- a/luni/src/main/java/java/util/zip/ZipFile.java
+++ b/luni/src/main/java/java/util/zip/ZipFile.java
@@ -34,18 +34,16 @@
 import libcore.io.Streams;
 
 /**
- * This class provides random read access to a <i>ZIP-archive</i> file.
- * <p>
- * While {@code ZipInputStream} provides stream based read access to a
- * <i>ZIP-archive</i>, this class implements more efficient (file based) access
- * and makes use of the <i>central directory</i> within a <i>ZIP-archive</i>.
- * <p>
- * Use {@code ZipOutputStream} if you want to create an archive.
- * <p>
- * A temporary ZIP file can be marked for automatic deletion upon closing it.
+ * This class provides random read access to a zip file. You pay more to read
+ * the zip file's central directory up front (from the constructor), but if you're using
+ * {@link #getEntry} to look up multiple files by name, you get the benefit of this index.
  *
- * @see ZipEntry
- * @see ZipOutputStream
+ * <p>If you only want to iterate through all the files (using {@link #entries}, you should
+ * consider {@link ZipInputStream}, which provides stream-like read access to a zip file and
+ * has a lower up-front cost because you don't pay to build an in-memory index.
+ *
+ * <p>If you want to create a zip file, use {@link ZipOutputStream}. There is no API for updating
+ * an existing zip file.
  */
 public class ZipFile implements ZipConstants {
     /**
@@ -86,81 +84,71 @@
     static final int GPBF_UNSUPPORTED_MASK = GPBF_ENCRYPTED_FLAG;
 
     /**
-     * Open ZIP file for read.
+     * Open zip file for reading.
      */
     public static final int OPEN_READ = 1;
 
     /**
-     * Delete ZIP file when closed.
+     * Delete zip file when closed.
      */
     public static final int OPEN_DELETE = 4;
 
-    private final String fileName;
+    private final String filename;
 
     private File fileToDeleteOnClose;
 
-    private RandomAccessFile mRaf;
+    private RandomAccessFile raf;
 
-    private final LinkedHashMap<String, ZipEntry> mEntries = new LinkedHashMap<String, ZipEntry>();
+    private final LinkedHashMap<String, ZipEntry> entries = new LinkedHashMap<String, ZipEntry>();
 
     private final CloseGuard guard = CloseGuard.get();
 
     /**
-     * Constructs a new {@code ZipFile} with the specified file.
-     *
-     * @param file
-     *            the file to read from.
-     * @throws ZipException
-     *             if a ZIP error occurs.
-     * @throws IOException
-     *             if an {@code IOException} occurs.
+     * Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
+     * @throws ZipException if a zip error occurs.
+     * @throws IOException if an {@code IOException} occurs.
      */
     public ZipFile(File file) throws ZipException, IOException {
         this(file, OPEN_READ);
     }
 
     /**
-     * Opens a file as <i>ZIP-archive</i>. "mode" must be {@code OPEN_READ} or
-     * {@code OPEN_DELETE} . The latter sets the "delete on exit" flag through a
-     * file.
+     * Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
+     * @throws IOException if an IOException occurs.
+     */
+    public ZipFile(String name) throws IOException {
+        this(new File(name), OPEN_READ);
+    }
+
+    /**
+     * Constructs a new {@code ZipFile} allowing access to the given file.
+     * The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}.
      *
-     * @param file
-     *            the ZIP file to read.
-     * @param mode
-     *            the mode of the file open operation.
-     * @throws IOException
-     *             if an {@code IOException} occurs.
+     * <p>If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the
+     * time that the {@code ZipFile} is closed (the contents will remain accessible until
+     * this {@code ZipFile} is closed); it also calls {@code File.deleteOnExit}.
+     *
+     * @throws IOException if an {@code IOException} occurs.
      */
     public ZipFile(File file, int mode) throws IOException {
-        fileName = file.getPath();
+        filename = file.getPath();
         if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE)) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad mode: " + mode);
         }
 
         if ((mode & OPEN_DELETE) != 0) {
-            fileToDeleteOnClose = file; // file.deleteOnExit();
+            fileToDeleteOnClose = file;
+            fileToDeleteOnClose.deleteOnExit();
         } else {
             fileToDeleteOnClose = null;
         }
 
-        mRaf = new RandomAccessFile(fileName, "r");
+        raf = new RandomAccessFile(filename, "r");
 
         readCentralDir();
         guard.open("close");
     }
 
-    /**
-     * Opens a ZIP archived file.
-     *
-     * @param name
-     *            the name of the ZIP file.
-     * @throws IOException
-     *             if an IOException occurs.
-     */
-    public ZipFile(String name) throws IOException {
-        this(new File(name), OPEN_READ);
-    }
-
     @Override protected void finalize() throws IOException {
         try {
             if (guard != null) {
@@ -176,19 +164,20 @@
     }
 
     /**
-     * Closes this ZIP file. This method is idempotent.
+     * Closes this zip file. This method is idempotent. This method may cause I/O if the
+     * zip file needs to be deleted.
      *
      * @throws IOException
      *             if an IOException occurs.
      */
     public void close() throws IOException {
         guard.close();
-        RandomAccessFile raf = mRaf;
 
-        if (raf != null) { // Only close initialized instances
-            synchronized(raf) {
-                mRaf = null;
-                raf.close();
+        RandomAccessFile localRaf = raf;
+        if (localRaf != null) { // Only close initialized instances
+            synchronized (localRaf) {
+                raf = null;
+                localRaf.close();
             }
             if (fileToDeleteOnClose != null) {
                 fileToDeleteOnClose.delete();
@@ -198,21 +187,24 @@
     }
 
     private void checkNotClosed() {
-        if (mRaf == null) {
+        if (raf == null) {
             throw new IllegalStateException("Zip file closed");
         }
     }
 
     /**
      * Returns an enumeration of the entries. The entries are listed in the
-     * order in which they appear in the ZIP archive.
+     * order in which they appear in the zip file.
      *
-     * @return the enumeration of the entries.
-     * @throws IllegalStateException if this ZIP file has been closed.
+     * <p>If you only need to iterate over the entries in a zip file, and don't
+     * need random-access entry lookup by name, you should probably use {@link ZipInputStream}
+     * instead, to avoid paying to construct the in-memory index.
+     *
+     * @throws IllegalStateException if this zip file has been closed.
      */
     public Enumeration<? extends ZipEntry> entries() {
         checkNotClosed();
-        final Iterator<ZipEntry> iterator = mEntries.values().iterator();
+        final Iterator<ZipEntry> iterator = entries.values().iterator();
 
         return new Enumeration<ZipEntry>() {
             public boolean hasMoreElements() {
@@ -228,13 +220,9 @@
     }
 
     /**
-     * Gets the ZIP entry with the specified name from this {@code ZipFile}.
+     * Returns the zip entry with the given name, or null if there is no such entry.
      *
-     * @param entryName
-     *            the name of the entry in the ZIP file.
-     * @return a {@code ZipEntry} or {@code null} if the entry name does not
-     *         exist in the ZIP file.
-     * @throws IllegalStateException if this ZIP file has been closed.
+     * @throws IllegalStateException if this zip file has been closed.
      */
     public ZipEntry getEntry(String entryName) {
         checkNotClosed();
@@ -242,9 +230,9 @@
             throw new NullPointerException("entryName == null");
         }
 
-        ZipEntry ze = mEntries.get(entryName);
+        ZipEntry ze = entries.get(entryName);
         if (ze == null) {
-            ze = mEntries.get(entryName + "/");
+            ze = entries.get(entryName + "/");
         }
         return ze;
     }
@@ -257,7 +245,7 @@
      * @return an input stream of the data contained in the {@code ZipEntry}.
      * @throws IOException
      *             if an {@code IOException} occurs.
-     * @throws IllegalStateException if this ZIP file has been closed.
+     * @throws IllegalStateException if this zip file has been closed.
      */
     public InputStream getInputStream(ZipEntry entry) throws IOException {
         // Make sure this ZipEntry is in this Zip file.  We run it through the name lookup.
@@ -267,13 +255,13 @@
         }
 
         // Create an InputStream at the right part of the file.
-        RandomAccessFile raf = mRaf;
-        synchronized (raf) {
+        RandomAccessFile localRaf = raf;
+        synchronized (localRaf) {
             // We don't know the entry data's start position. All we have is the
             // position of the entry's local header. At position 6 we find the
             // General Purpose Bit Flag.
             // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
-            RAFStream rafStream= new RAFStream(raf, entry.mLocalHeaderRelOffset + 6);
+            RAFStream rafStream= new RAFStream(localRaf, entry.localHeaderRelOffset + 6);
             DataInputStream is = new DataInputStream(rafStream);
             int gpbf = Short.reverseBytes(is.readShort()) & 0xffff;
             if ((gpbf & ZipFile.GPBF_UNSUPPORTED_MASK) != 0) {
@@ -292,11 +280,11 @@
 
             // The compressed or stored file data follows immediately after.
             if (entry.compressionMethod == ZipEntry.DEFLATED) {
-                rafStream.mLength = rafStream.mOffset + entry.compressedSize;
+                rafStream.length = rafStream.offset + entry.compressedSize;
                 int bufSize = Math.max(1024, (int) Math.min(entry.getSize(), 65535L));
                 return new ZipInflaterInputStream(rafStream, new Inflater(true), bufSize, entry);
             } else {
-                rafStream.mLength = rafStream.mOffset + entry.size;
+                rafStream.length = rafStream.offset + entry.size;
                 return rafStream;
             }
         }
@@ -308,18 +296,18 @@
      * @return the file name of this {@code ZipFile}.
      */
     public String getName() {
-        return fileName;
+        return filename;
     }
 
     /**
      * Returns the number of {@code ZipEntries} in this {@code ZipFile}.
      *
      * @return the number of entries in this file.
-     * @throws IllegalStateException if this ZIP file has been closed.
+     * @throws IllegalStateException if this zip file has been closed.
      */
     public int size() {
         checkNotClosed();
-        return mEntries.size();
+        return entries.size();
     }
 
     /**
@@ -336,17 +324,13 @@
      * it though, so we're in good company if this fails.
      */
     private void readCentralDir() throws IOException {
-        /*
-         * Scan back, looking for the End Of Central Directory field.  If
-         * the archive doesn't have a comment, we'll hit it on the first
-         * try.
-         *
-         * No need to synchronize mRaf here -- we only do this when we
-         * first open the Zip file.
-         */
-        long scanOffset = mRaf.length() - ENDHDR;
+        // Scan back, looking for the End Of Central Directory field. If the zip file doesn't
+        // have an overall comment (unrelated to any per-entry comments), we'll hit the EOCD
+        // on the first try.
+        // No need to synchronize raf here -- we only do this when we first open the zip file.
+        long scanOffset = raf.length() - ENDHDR;
         if (scanOffset < 0) {
-            throw new ZipException("File too short to be a zip file: " + mRaf.length());
+            throw new ZipException("File too short to be a zip file: " + raf.length());
         }
 
         long stopOffset = scanOffset - 65536;
@@ -356,21 +340,21 @@
 
         final int ENDHEADERMAGIC = 0x06054b50;
         while (true) {
-            mRaf.seek(scanOffset);
-            if (Integer.reverseBytes(mRaf.readInt()) == ENDHEADERMAGIC) {
+            raf.seek(scanOffset);
+            if (Integer.reverseBytes(raf.readInt()) == ENDHEADERMAGIC) {
                 break;
             }
 
             scanOffset--;
             if (scanOffset < stopOffset) {
-                throw new ZipException("EOCD not found; not a Zip archive?");
+                throw new ZipException("EOCD not found; not a zip file?");
             }
         }
 
         // Read the End Of Central Directory. We could use ENDHDR instead of the magic number 18,
         // but we don't actually need all the header.
         byte[] eocd = new byte[18];
-        mRaf.readFully(eocd);
+        raf.readFully(eocd);
 
         // Pull out the information we need.
         BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN);
@@ -379,20 +363,23 @@
         int numEntries = it.readShort() & 0xffff;
         int totalNumEntries = it.readShort() & 0xffff;
         it.skip(4); // Ignore centralDirSize.
-        int centralDirOffset = it.readInt();
+        long centralDirOffset = ((long) it.readInt()) & 0xffffffffL;
 
         if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
             throw new ZipException("spanned archives not supported");
         }
 
         // Seek to the first CDE and read all entries.
-        RAFStream rafs = new RAFStream(mRaf, centralDirOffset);
-        BufferedInputStream bin = new BufferedInputStream(rafs, 4096);
+        // We have to do this now (from the constructor) rather than lazily because the
+        // public API doesn't allow us to throw IOException except from the constructor
+        // or from getInputStream.
+        RAFStream rafStream = new RAFStream(raf, centralDirOffset);
+        BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096);
         byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry.
         for (int i = 0; i < numEntries; ++i) {
-            ZipEntry newEntry = new ZipEntry(hdrBuf, bin);
+            ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream);
             String entryName = newEntry.getName();
-            if (mEntries.put(entryName, newEntry) != null) {
+            if (entries.put(entryName, newEntry) != null) {
                 throw new ZipException("Duplicate entry name: " + entryName);
             }
         }
@@ -407,19 +394,18 @@
      * <p>We could support mark/reset, but we don't currently need them.
      */
     static class RAFStream extends InputStream {
+        private final RandomAccessFile sharedRaf;
+        private long length;
+        private long offset;
 
-        RandomAccessFile mSharedRaf;
-        long mOffset;
-        long mLength;
-
-        public RAFStream(RandomAccessFile raf, long pos) throws IOException {
-            mSharedRaf = raf;
-            mOffset = pos;
-            mLength = raf.length();
+        public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+            sharedRaf = raf;
+            offset = initialOffset;
+            length = raf.length();
         }
 
         @Override public int available() throws IOException {
-            return (mOffset < mLength ? 1 : 0);
+            return (offset < length ? 1 : 0);
         }
 
         @Override public int read() throws IOException {
@@ -427,14 +413,14 @@
         }
 
         @Override public int read(byte[] b, int off, int len) throws IOException {
-            synchronized (mSharedRaf) {
-                mSharedRaf.seek(mOffset);
-                if (len > mLength - mOffset) {
-                    len = (int) (mLength - mOffset);
+            synchronized (sharedRaf) {
+                sharedRaf.seek(offset);
+                if (len > length - offset) {
+                    len = (int) (length - offset);
                 }
-                int count = mSharedRaf.read(b, off, len);
+                int count = sharedRaf.read(b, off, len);
                 if (count > 0) {
-                    mOffset += count;
+                    offset += count;
                     return count;
                 } else {
                     return -1;
@@ -442,28 +428,36 @@
             }
         }
 
-        @Override
-        public long skip(long byteCount) throws IOException {
-            if (byteCount > mLength - mOffset) {
-                byteCount = mLength - mOffset;
+        @Override public long skip(long byteCount) throws IOException {
+            if (byteCount > length - offset) {
+                byteCount = length - offset;
             }
-            mOffset += byteCount;
+            offset += byteCount;
             return byteCount;
         }
+
+        public int fill(Inflater inflater, int nativeEndBufSize) throws IOException {
+            synchronized (sharedRaf) {
+                int len = Math.min((int) (length - offset), nativeEndBufSize);
+                int cnt = inflater.setFileInput(sharedRaf.getFD(), offset, nativeEndBufSize);
+                // setFileInput read from the file, so we need to get the OS and RAFStream back
+                // in sync...
+                skip(cnt);
+                return len;
+            }
+        }
     }
 
     static class ZipInflaterInputStream extends InflaterInputStream {
-
-        ZipEntry entry;
-        long bytesRead = 0;
+        private final ZipEntry entry;
+        private long bytesRead = 0;
 
         public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
             super(is, inf, bsize);
             this.entry = entry;
         }
 
-        @Override
-        public int read(byte[] buffer, int off, int nbytes) throws IOException {
+        @Override public int read(byte[] buffer, int off, int nbytes) throws IOException {
             int i = super.read(buffer, off, nbytes);
             if (i != -1) {
                 bytesRead += i;
@@ -471,8 +465,7 @@
             return i;
         }
 
-        @Override
-        public int available() throws IOException {
+        @Override public int available() throws IOException {
             if (closed) {
                 // Our superclass will throw an exception, but there's a jtreg test that
                 // explicitly checks that the InputStream returned from ZipFile.getInputStream
diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java
index 019e878..d46e2dd 100644
--- a/luni/src/main/java/java/util/zip/ZipInputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipInputStream.java
@@ -29,24 +29,25 @@
 import libcore.io.Streams;
 
 /**
- * This class provides an implementation of {@code FilterInputStream} that
- * decompresses data from an {@code InputStream} containing a ZIP archive.
+ * Used to read (decompress) the data from zip files.
  *
- * <p>A ZIP archive is a collection of (possibly) compressed files.
- * When reading from a {@code ZipInputStream}, you retrieve the
- * entry's metadata with {@code getNextEntry} before you can read the userdata.
+ * <p>A zip file (or "archive") is a collection of (possibly) compressed files.
+ * When reading from a {@code ZipInputStream}, you call {@link #getNextEntry}
+ * which returns a {@link ZipEntry} of metadata corresponding to the userdata that follows.
+ * When you appear to have hit the end of this stream (which is really just the end of the current
+ * entry's userdata), call {@code getNextEntry} again. When it returns null,
+ * there are no more entries in the input file.
  *
- * <p>Although {@code InflaterInputStream} can only read compressed ZIP archive
+ * <p>Although {@code InflaterInputStream} can only read compressed zip
  * entries, this class can read non-compressed entries as well.
  *
- * <p>Use {@code ZipFile} if you can access the archive as a file directly,
- * especially if you want random access to entries, rather than needing to
- * iterate over all entries.
+ * <p>Use {@link ZipFile} if you need random access to entries by name, but use this class
+ * if you just want to iterate over all entries.
  *
  * <h3>Example</h3>
  * <p>Using {@code ZipInputStream} is a little more complicated than {@link GZIPInputStream}
- * because ZIP archives are containers that can contain multiple files. This code pulls all the
- * files out of a ZIP archive, similar to the {@code unzip(1)} utility.
+ * because zip files are containers that can contain multiple files. This code pulls all the
+ * files out of a zip file, similar to the {@code unzip(1)} utility.
  * <pre>
  * InputStream is = ...
  * ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
@@ -67,9 +68,6 @@
  *     zis.close();
  * }
  * </pre>
- *
- * @see ZipEntry
- * @see ZipFile
  */
 public class ZipInputStream extends InflaterInputStream implements ZipConstants {
     private static final int ZIPLocalHeaderVersionNeeded = 20;
@@ -93,10 +91,7 @@
     private char[] charBuf = new char[256];
 
     /**
-     * Constructs a new {@code ZipInputStream} from the specified input stream.
-     *
-     * @param stream
-     *            the input stream to representing a ZIP archive.
+     * Constructs a new {@code ZipInputStream} to read zip entries from the given input stream.
      */
     public ZipInputStream(InputStream stream) {
         super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true));
@@ -120,7 +115,7 @@
     }
 
     /**
-     * Closes the current ZIP entry and positions to read the next entry.
+     * Closes the current zip entry and prepares to read the next entry.
      *
      * @throws IOException
      *             if an {@code IOException} occurs.
@@ -213,13 +208,10 @@
     }
 
     /**
-     * Reads the next entry from this {@code ZipInputStream} or {@code null} if
+     * Returns the next entry from this {@code ZipInputStream} or {@code null} if
      * no more entries are present.
      *
-     * @return the next {@code ZipEntry} contained in the input stream.
-     * @throws IOException
-     *             if an {@code IOException} occurs.
-     * @see ZipEntry
+     * @throws IOException if an {@code IOException} occurs.
      */
     public ZipEntry getNextEntry() throws IOException {
         closeEntry();
diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java
index 47db8f6..59849d3 100644
--- a/luni/src/main/java/java/util/zip/ZipOutputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java
@@ -23,25 +23,23 @@
 import java.nio.charset.Charsets;
 import java.util.Arrays;
 import java.util.HashSet;
+import libcore.util.EmptyArray;
 
 /**
- * This class provides an implementation of {@code FilterOutputStream} that
- * compresses data entries into a <i>ZIP-archive</i> output stream.
- * <p>
- * {@code ZipOutputStream} is used to write {@code ZipEntries} to the underlying
- * stream. Output from {@code ZipOutputStream} conforms to the {@code ZipFile}
- * file format.
- * <p>
- * While {@code DeflaterOutputStream} can write a compressed <i>ZIP-archive</i>
- * entry, this extension can write uncompressed entries as well. In this case
- * special rules apply, for this purpose refer to the <a
- * href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">file format
- * specification</a>.
+ * Used to write (compress) data into zip files.
+ *
+ * <p>{@code ZipOutputStream} is used to write {@link ZipEntry}s to the underlying
+ * stream. Output from {@code ZipOutputStream} can be read using {@link ZipFile}
+ * or {@link ZipInputStream}.
+ *
+ * <p>While {@code DeflaterOutputStream} can write compressed zip file
+ * entries, this extension can write uncompressed entries as well.
+ * Use {@link ZipEntry#setMethod} or {@link #setMethod} with the {@link ZipEntry#STORED} flag.
  *
  * <h3>Example</h3>
  * <p>Using {@code ZipOutputStream} is a little more complicated than {@link GZIPOutputStream}
- * because ZIP archives are containers that can contain multiple files. This code creates a ZIP
- * archive containing several files, similar to the {@code zip(1)} utility.
+ * because zip files are containers that can contain multiple files. This code creates a zip
+ * file containing several files, similar to the {@code zip(1)} utility.
  * <pre>
  * OutputStream os = ...
  * ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
@@ -58,9 +56,6 @@
  *     zos.close();
  * }
  * </pre>
- *
- * @see ZipEntry
- * @see ZipFile
  */
 public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
 
@@ -76,13 +71,13 @@
 
     private static final int ZIPLocalHeaderVersionNeeded = 20;
 
-    private String comment;
+    private byte[] commentBytes = EmptyArray.BYTE;
 
     private final HashSet<String> entries = new HashSet<String>();
 
-    private int compressMethod = DEFLATED;
+    private int defaultCompressionMethod = DEFLATED;
 
-    private int compressLevel = Deflater.DEFAULT_COMPRESSION;
+    private int compressionLevel = Deflater.DEFAULT_COMPRESSION;
 
     private ByteArrayOutputStream cDir = new ByteArrayOutputStream();
 
@@ -95,14 +90,11 @@
     private byte[] nameBytes;
 
     /**
-     * Constructs a new {@code ZipOutputStream} with the specified output
-     * stream.
-     *
-     * @param p1
-     *            the {@code OutputStream} to write the data to.
+     * Constructs a new {@code ZipOutputStream} that writes a zip file
+     * to the given {@code OutputStream}.
      */
-    public ZipOutputStream(OutputStream p1) {
-        super(p1, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
+    public ZipOutputStream(OutputStream os) {
+        super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
     }
 
     /**
@@ -131,7 +123,7 @@
      *             If an error occurs closing the entry.
      */
     public void closeEntry() throws IOException {
-        checkClosed();
+        checkOpen();
         if (currentEntry == null) {
             return;
         }
@@ -186,12 +178,13 @@
         } else {
             writeShort(cDir, 0);
         }
-        String c;
-        if ((c = currentEntry.getComment()) != null) {
-            writeShort(cDir, c.length());
-        } else {
-            writeShort(cDir, 0);
+
+        String comment = currentEntry.getComment();
+        byte[] commentBytes = EmptyArray.BYTE;
+        if (comment != null) {
+            commentBytes = comment.getBytes(Charsets.UTF_8);
         }
+        writeShort(cDir, commentBytes.length); // Comment length.
         writeShort(cDir, 0); // Disk Start
         writeShort(cDir, 0); // Internal File Attributes
         writeLong(cDir, 0); // External File Attributes
@@ -202,8 +195,8 @@
             cDir.write(currentEntry.extra);
         }
         offset += curOffset;
-        if (c != null) {
-            cDir.write(c.getBytes());
+        if (commentBytes.length > 0) {
+            cDir.write(commentBytes);
         }
         currentEntry = null;
         crc.reset();
@@ -220,7 +213,7 @@
      */
     @Override
     public void finish() throws IOException {
-        // TODO: is there a bug here? why not checkClosed?
+        // TODO: is there a bug here? why not checkOpen?
         if (out == null) {
             throw new IOException("Stream is closed");
         }
@@ -242,11 +235,9 @@
         writeShort(cDir, entries.size()); // Number of entries
         writeLong(cDir, cdirSize); // Size of central dir
         writeLong(cDir, offset); // Offset of central dir
-        if (comment != null) {
-            writeShort(cDir, comment.length());
-            cDir.write(comment.getBytes());
-        } else {
-            writeShort(cDir, 0);
+        writeShort(cDir, commentBytes.length);
+        if (commentBytes.length > 0) {
+            cDir.write(commentBytes);
         }
         // Write the central directory.
         cDir.writeTo(out);
@@ -269,18 +260,33 @@
         if (currentEntry != null) {
             closeEntry();
         }
-        if (ze.getMethod() == STORED || (compressMethod == STORED && ze.getMethod() == -1)) {
-            if (ze.crc == -1) {
-                throw new ZipException("CRC mismatch");
+
+        // Did this ZipEntry specify a method, or should we use the default?
+        int method = ze.getMethod();
+        if (method == -1) {
+            method = defaultCompressionMethod;
+        }
+
+        // If the method is STORED, check that the ZipEntry was configured appropriately.
+        if (method == STORED) {
+            if (ze.getCompressedSize() == -1) {
+                ze.setCompressedSize(ze.getSize());
+            } else if (ze.getSize() == -1) {
+                ze.setSize(ze.getCompressedSize());
             }
-            if (ze.size == -1 && ze.compressedSize == -1) {
-                throw new ZipException("Size mismatch");
+            if (ze.getCrc() == -1) {
+                throw new ZipException("STORED entry missing CRC");
             }
-            if (ze.size != ze.compressedSize && ze.compressedSize != -1 && ze.size != -1) {
-                throw new ZipException("Size mismatch");
+            if (ze.getSize() == -1) {
+                throw new ZipException("STORED entry missing size");
+            }
+            if (ze.size != ze.compressedSize) {
+                throw new ZipException("STORED entry size/compressed size mismatch");
             }
         }
-        checkClosed();
+
+        checkOpen();
+
         if (entries.contains(ze.name)) {
             throw new ZipException("Entry already exists: " + ze.name);
         }
@@ -294,35 +300,29 @@
             throw new IllegalArgumentException("Name too long: " + nameLength + " UTF-8 bytes");
         }
 
-        def.setLevel(compressLevel);
+        def.setLevel(compressionLevel);
+        ze.setMethod(method);
+
         currentEntry = ze;
         entries.add(currentEntry.name);
-        if (currentEntry.getMethod() == -1) {
-            currentEntry.setMethod(compressMethod);
-        }
 
         // Local file header.
         // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
-        int flags = currentEntry.getMethod() == STORED ? 0 : ZipFile.GPBF_DATA_DESCRIPTOR_FLAG;
+        int flags = (method == STORED) ? 0 : ZipFile.GPBF_DATA_DESCRIPTOR_FLAG;
         // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used
         // modified UTF-8. From Java 7, it sets this flag and uses normal UTF-8.)
         flags |= ZipFile.GPBF_UTF8_FLAG;
         writeLong(out, LOCSIG); // Entry header
         writeShort(out, ZIPLocalHeaderVersionNeeded); // Extraction version
         writeShort(out, flags);
-        writeShort(out, currentEntry.getMethod());
+        writeShort(out, method);
         if (currentEntry.getTime() == -1) {
             currentEntry.setTime(System.currentTimeMillis());
         }
         writeShort(out, currentEntry.time);
         writeShort(out, currentEntry.modDate);
 
-        if (currentEntry.getMethod() == STORED) {
-            if (currentEntry.size == -1) {
-                currentEntry.size = currentEntry.compressedSize;
-            } else if (currentEntry.compressedSize == -1) {
-                currentEntry.compressedSize = currentEntry.size;
-            }
+        if (method == STORED) {
             writeLong(out, currentEntry.crc);
             writeLong(out, currentEntry.size);
             writeLong(out, currentEntry.size);
@@ -344,47 +344,42 @@
     }
 
     /**
-     * Sets the {@code ZipFile} comment associated with the file being written.
-     *
-     * @param comment
-     *            the comment associated with the file.
+     * Sets the comment associated with the file being written.
+     * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes.
      */
     public void setComment(String comment) {
-        if (comment.length() > 0xFFFF) {
-            throw new IllegalArgumentException("Comment too long: " + comment.length() + " characters");
+        if (comment == null) {
+            this.commentBytes = null;
+            return;
         }
-        this.comment = comment;
+
+        byte[] newCommentBytes = comment.getBytes(Charsets.UTF_8);
+        if (newCommentBytes.length > 0xffff) {
+            throw new IllegalArgumentException("Comment too long: " + newCommentBytes.length + " bytes");
+        }
+        this.commentBytes = newCommentBytes;
     }
 
     /**
-     * Sets the compression level to be used for writing entry data. This level
-     * may be set on a per entry basis. The level must have a value between -1
-     * and 8 according to the {@code Deflater} compression level bounds.
-     *
-     * @param level
-     *            the compression level (ranging from -1 to 8).
-     * @see Deflater
+     * Sets the <a href="Deflater.html#compression_level">compression level</a> to be used
+     * for writing entry data.
      */
     public void setLevel(int level) {
         if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad level: " + level);
         }
-        compressLevel = level;
+        compressionLevel = level;
     }
 
     /**
-     * Sets the compression method to be used when compressing entry data.
-     * method must be one of {@code STORED} (for no compression) or {@code
-     * DEFLATED}.
-     *
-     * @param method
-     *            the compression method to use.
+     * Sets the default compression method to be used when a {@code ZipEntry} doesn't
+     * explicitly specify a method. See {@link ZipEntry#setMethod} for more details.
      */
     public void setMethod(int method) {
         if (method != STORED && method != DEFLATED) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad method: " + method);
         }
-        compressMethod = method;
+        defaultCompressionMethod = method;
     }
 
     private long writeLong(OutputStream os, long i) throws IOException {
@@ -423,7 +418,7 @@
         crc.update(buffer, offset, byteCount);
     }
 
-    private void checkClosed() throws IOException {
+    private void checkOpen() throws IOException {
         if (cDir == null) {
             throw new IOException("Stream is closed");
         }
diff --git a/luni/src/main/java/javax/crypto/Mac.java b/luni/src/main/java/javax/crypto/Mac.java
index 46be141..c208456 100644
--- a/luni/src/main/java/javax/crypto/Mac.java
+++ b/luni/src/main/java/javax/crypto/Mac.java
@@ -265,7 +265,9 @@
             return;
         }
         if ((offset < 0) || (len < 0) || ((offset + len) > input.length)) {
-            throw new IllegalArgumentException("Incorrect arguments");
+            throw new IllegalArgumentException("Incorrect arguments."
+                                               + " input.length=" + input.length
+                                               + " offset=" + offset + ", len=" + len);
         }
         spiImpl.engineUpdate(input, offset, len);
     }
diff --git a/luni/src/main/java/javax/net/package.html b/luni/src/main/java/javax/net/package.html
deleted file mode 100644
index 5674d06..0000000
--- a/luni/src/main/java/javax/net/package.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<html>
-  <body>
-    <p>
-      This package provides factory classes to create sockets and server-sockets. This classes can be subclassed to create factories for other kinds of socket for example the SSL-capable sockets from the package javax.net.ssl.
-    </p>
-  </body>
-</html>
diff --git a/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java b/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
index e68baca..fa11371 100644
--- a/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
+++ b/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
@@ -80,7 +80,8 @@
 
         if (!hasDns) {
             X500Principal principal = certificate.getSubjectX500Principal();
-            String cn = new DistinguishedNameParser(principal).find("cn");
+            // RFC 2818 advises using the most specific name for matching.
+            String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
             if (cn != null) {
                 return verifyHostName(hostName, cn);
             }
diff --git a/luni/src/main/java/javax/net/ssl/DistinguishedNameParser.java b/luni/src/main/java/javax/net/ssl/DistinguishedNameParser.java
index fa8ed1b..c3c1606 100644
--- a/luni/src/main/java/javax/net/ssl/DistinguishedNameParser.java
+++ b/luni/src/main/java/javax/net/ssl/DistinguishedNameParser.java
@@ -39,6 +39,9 @@
     private char[] chars;
 
     public DistinguishedNameParser(X500Principal principal) {
+        // RFC2253 is used to ensure we get attributes in the reverse
+        // order of the underlying ASN.1 encoding, so that the most
+        // significant values of repeated attributes occur first.
         this.dn = principal.getName(X500Principal.RFC2253);
         this.length = this.dn.length();
     }
@@ -196,7 +199,7 @@
             case '+':
             case ',':
             case ';':
-                // separator char has beed found
+                // separator char has been found
                 return new String(chars, beg, end - beg);
             case '\\':
                 // escaped char
@@ -216,7 +219,7 @@
                 }
                 if (pos == length || chars[pos] == ',' || chars[pos] == '+'
                         || chars[pos] == ';') {
-                    // separator char or the end of DN has beed found
+                    // separator char or the end of DN has been found
                     return new String(chars, beg, cur - beg);
                 }
                 break;
@@ -340,12 +343,12 @@
     }
 
     /**
-     * Parses the DN and returns the attribute value for an attribute type.
+     * Parses the DN and returns the most significant attribute value
+     * for an attribute type, or null if none found.
      *
      * @param attributeType attribute type to look for (e.g. "ca")
-     * @return value of the attribute that first found, or null if none found
      */
-    public String find(String attributeType) {
+    public String findMostSpecific(String attributeType) {
         // Initialize internal state.
         pos = 0;
         beg = 0;
@@ -380,6 +383,9 @@
                 attValue = escapedAV();
             }
 
+            // Values are ordered from most specific to least specific
+            // due to the RFC2253 formatting. So take the first match
+            // we see.
             if (attributeType.equalsIgnoreCase(attType)) {
                 return attValue;
             }
diff --git a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
index 4112dc4..ab86a9b 100644
--- a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
+++ b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
@@ -44,7 +44,8 @@
  * <p>For example, to trust a set of certificates specified by a {@code KeyStore}:
  * <pre>   {@code
  *   KeyStore keyStore = ...;
- *   TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ *   String algorithm = TrustManagerFactory.getDefaultAlgorithm();
+ *   TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
  *   tmf.init(keyStore);
  *
  *   SSLContext context = SSLContext.getInstance("TLS");
@@ -77,7 +78,8 @@
  * <p>For example, to supply client certificates from a {@code KeyStore}:
  * <pre>   {@code
  *   KeyStore keyStore = ...;
- *   KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
+ *   String algorithm = KeyManagerFactory.getDefaultAlgorithm();
+ *   KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
  *   kmf.init(keyStore);
  *
  *   SSLContext context = SSLContext.getInstance("TLS");
diff --git a/luni/src/main/java/javax/net/ssl/KeyManagerFactory.java b/luni/src/main/java/javax/net/ssl/KeyManagerFactory.java
index 0b3db61..ef085e4 100644
--- a/luni/src/main/java/javax/net/ssl/KeyManagerFactory.java
+++ b/luni/src/main/java/javax/net/ssl/KeyManagerFactory.java
@@ -40,6 +40,9 @@
     // Store default property name
     private static final String PROPERTY_NAME = "ssl.KeyManagerFactory.algorithm";
 
+    // Default value of KeyManagerFactory type.
+    private static final String DEFAULT_PROPERTY = "PKIX";
+
     /**
      * Returns the default key manager factory algorithm name.
      * <p>
@@ -49,7 +52,8 @@
      * @return the default algorithm name.
      */
     public static final String getDefaultAlgorithm() {
-        return Security.getProperty(PROPERTY_NAME);
+        String algorithm = Security.getProperty(PROPERTY_NAME);
+        return (algorithm != null ? algorithm : DEFAULT_PROPERTY);
     }
 
     /**
diff --git a/luni/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java b/luni/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java
index bb5bd64..cdd467a 100644
--- a/luni/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java
+++ b/luni/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java
@@ -18,7 +18,7 @@
 package javax.net.ssl;
 
 /**
- * The exception that is thrown when the identity of a peer has not beed
+ * The exception that is thrown when the identity of a peer has not been
  * verified.
  */
 public class SSLPeerUnverifiedException extends SSLException {
diff --git a/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java b/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java
index be9db06..72023f5 100644
--- a/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java
+++ b/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java
@@ -38,7 +38,10 @@
     private static final Engine ENGINE = new Engine(SERVICE);
 
     // Store default property name
-    private static final String PROPERTYNAME = "ssl.TrustManagerFactory.algorithm";
+    private static final String PROPERTY_NAME = "ssl.TrustManagerFactory.algorithm";
+
+    // Default value of TrustManagerFactory type.
+    private static final String DEFAULT_PROPERTY = "PKIX";
 
     /**
      * Returns the default algorithm name for the {@code TrustManagerFactory}. The
@@ -48,7 +51,8 @@
      * @return the default algorithm name.
      */
     public static final String getDefaultAlgorithm() {
-        return Security.getProperty(PROPERTYNAME);
+        String algorithm = Security.getProperty(PROPERTY_NAME);
+        return (algorithm != null ? algorithm : DEFAULT_PROPERTY);
     }
 
     /**
diff --git a/luni/src/main/java/javax/xml/datatype/DatatypeFactory.java b/luni/src/main/java/javax/xml/datatype/DatatypeFactory.java
index 68291b6..29cf490 100644
--- a/luni/src/main/java/javax/xml/datatype/DatatypeFactory.java
+++ b/luni/src/main/java/javax/xml/datatype/DatatypeFactory.java
@@ -50,6 +50,8 @@
  *    </li>
  * </ol>
  *
+ * <p>Note that you must supply your own implementation (such as Xerces); Android does not ship with a default implementation.
+ *
  * @author <a href="mailto:Joseph.Fialli@Sun.COM">Joseph Fialli</a>
  * @author <a href="mailto:Jeff.Suttor@Sun.com">Jeff Suttor</a>
  * @version $Revision: 884950 $, $Date: 2009-11-27 10:46:18 -0800 (Fri, 27 Nov 2009) $
@@ -84,6 +86,7 @@
      *
      * <p>The implementation resolution mechanisms are <a href="#DatatypeFactory.newInstance">defined</a> in this
      * <code>Class</code>'s documentation.</p>
+     * <p>Note that you must supply your own implementation (such as Xerces); Android does not ship with a default implementation.
      *
      * @return New instance of a <code>DocumentBuilderFactory</code>
      *
diff --git a/luni/src/main/java/javax/xml/datatype/package.html b/luni/src/main/java/javax/xml/datatype/package.html
deleted file mode 100644
index 6218533..0000000
--- a/luni/src/main/java/javax/xml/datatype/package.html
+++ /dev/null
@@ -1,147 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- $Id: package.html 541939 2007-05-26 21:10:52Z mrglavas $ -->
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-	<head>
-		<title>javax.xml.datatype</title>
-
-		<meta name="@author" content="mailto:Jeff.Suttor@Sun.com" />
-		<meta name="@version" content="$Revision: 541939 $, $Date: 2007-05-26 14:10:52 -0700 (Sat, 26 May 2007) $" />
-		<meta name="@see" content='<a href="http://www.w3.org/TR/xmlschema-2/#dateTime">W3C XML Schema 1.0 Part 2, Section 3.2.7-14</a>' />
-		<meta name="@see" content='<a href="http://www.w3.org/TR/xpath-datamodel/#dayTimeDuration">XQuery 1.0 and XPath 2.0 Data Model, xdt:dayTimeDuration</a>' />
-		<meta name="@see" content='<a href="http://www.w3.org/TR/xpath-datamodel/#yearMonthDuration">XQuery 1.0 and XPath 2.0 Data Model, xdt:yearMonthDuration</a>' />
-		<meta name="@since" content="1.5" />
-	</head>
-
-	<body>
-
-		<p>XML/Java Type Mappings.</p>
-		
-		<p>The <code>javax.xml.datatype</code> API provides XML/Java type mappings.</p>
-		
-		<p>The following XML standards apply:</p>
-		<ul>
-			<li><a href="http://www.w3.org/TR/xmlschema-2/#dateTime">W3C XML Schema 1.0 Part 2, Section 3.2.7-14</a></li>
-			<li><a href="http://www.w3.org/TR/xpath-datamodel/#dayTimeDuration">XQuery 1.0 and XPath 2.0 Data Model, xdt:dayTimeDuration</a></li>
-			<li><a href="http://www.w3.org/TR/xpath-datamodel/#yearMonthDuration">XQuery 1.0 and XPath 2.0 Data Model, xdt:yearMonthDuration</a></li>
-		</ul>
-
-		<hr />
-		
-		<table border="1" cellpadding="2">
-			<thead>
-				<tr>
-					<th>W3C XML Schema Data Type</th>
-					<th>Java Data Type</th>
-				</tr>
-			</thead>
-			
-			<tbody>
-				<tr>
-					<td>xs:date</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:dateTime</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:duration</td>
-					<td>{@link javax.xml.datatype.Duration}</td>
-				</tr>
-				<tr>
-					<td>xs:gDay</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:gMonth </td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:gMonthDay</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:gYear</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:gYearMonth</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				<tr>
-					<td>xs:time</td>
-					<td>{@link javax.xml.datatype.XMLGregorianCalendar}</td>
-				</tr>
-				
-			</tbody>
-		</table>
-		
-		<hr />
-		
-		
-		<table border="1" cellpadding="2">
-			<thead>
-				<tr>
-					<th>XQuery 1.0 and XPath 2.0 Data Model</th>
-					<th>Java Data Type</th>
-				</tr>
-			</thead>
-			
-			<tbody>
-				<tr>
-					<td>xdt:dayTimeDuration</td>
-					<td>{@link javax.xml.datatype.Duration}</td>
-				</tr>
-				<tr>
-					<td>xdt:yearMonthDuration</td>
-					<td>{@link javax.xml.datatype.Duration}</td>
-				</tr>
-			</tbody>
-		</table>
-		
-		<hr />
-		
-		<p>
-			W3C XML Schema data types that have a "<em>natural</em>" mapping to Java types are defined by
-			JSR 31: Java&trade; Architecture for XML Binding (JAXB) Specification, Binding XML Schema to Java Representations.
-			JAXB defined mappings for XML Schema built-in data types include:
-		</p>
-		<ul>
-			<li>xs:anySimpleType</li>
-			<li>xs:base64Binary</li>
-			<li>xs:boolean</li>
-			<li>xs:byte</li>
-			<li>xs:decimal</li>
-			<li>xs:double</li>
-			<li>xs:float</li>
-			<li>xs:hexBinary</li>
-			<li>xs:int</li>
-			<li>xs:integer</li>
-			<li>xs:long</li>
-			<li>xs:QName</li>
-			<li>xs:short</li>
-			<li>xs:string</li>
-			<li>xs:unsignedByte</li>
-			<li>xs:unsignedInt</li>
-			<li>xs:unsignedShort</li>
-		</ul>
-		
-		<hr />
-		
-
-		<ul>
-			<li>Author <a href="mailto:Jeff.Suttor@Sun.com">Jeff Suttor</a></li>
-			<li>See <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">W3C XML Schema 1.0 Part 2, Section 3.2.7-14</a></li>
-			<li>See <a href="http://www.w3.org/TR/xpath-datamodel/#dayTimeDuration">XQuery 1.0 and XPath 2.0 Data Model, xdt:dayTimeDuration</a></li>
-			<li>See <a href="http://www.w3.org/TR/xpath-datamodel/#yearMonthDuration">XQuery 1.0 and XPath 2.0 Data Model, xdt:yearMonthDuration</a></li>
-			<li>Since 1.5</li>
-		</ul>
-		
-		<hr />
-		
-
-	</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/namespace/package.html b/luni/src/main/java/javax/xml/namespace/package.html
deleted file mode 100644
index a5296f7..0000000
--- a/luni/src/main/java/javax/xml/namespace/package.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  $Id: package.html 226187 2005-04-11 07:02:02Z neeraj $
--->
-
-<!DOCTYPE html
-     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-  <title>javax.xml.namespace</title>
-
-  <meta name="CVS"
-        content="$Id: package.html 226187 2005-04-11 07:02:02Z neeraj $" />
-  <meta name="AUTHOR"
-        content="Jeff.Suttor@Sun.com" />
-</head>
-
-<body>
-
-<p>XML Namespace processing.</p>
-
-<p>The following XML standards apply:</p>
-<ul>
-  <li><a href="http://www.w3.org/TR/xmlschema-2/#QName">XML Schema Part2: Datatypes specification</a></li>
-  <li><a href="http://www.w3.org/TR/REC-xml-names/#ns-qualnames">Namespaces in XML</a></li>
-  <li><a href="http://www.w3.org/XML/xml-names-19990114-errata">Namespaces in XML Errata</a></li>
-</ul>
-
-</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/package.html b/luni/src/main/java/javax/xml/package.html
deleted file mode 100644
index f3d8318..0000000
--- a/luni/src/main/java/javax/xml/package.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<html>
-  <body>
-    <p>
-      Provides a utility class with useful XML constants.
-    </p>
-  </body>
-</html>
diff --git a/luni/src/main/java/javax/xml/parsers/package.html b/luni/src/main/java/javax/xml/parsers/package.html
deleted file mode 100644
index 92e09d5..0000000
--- a/luni/src/main/java/javax/xml/parsers/package.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<HTML><HEAD>
-<!--
-    $Id: package.html 226183 2005-04-08 10:39:14Z neeraj $
--->
-
-</HEAD><BODY>
-
-Provides classes allowing the processing of XML documents. Two types
-of plugable parsers are supported:
-<ul>
-<li>SAX (Simple API for XML)
-<li>DOM (Document Object Model)
-</ul>
-
-
-
-</BODY></HTML>
diff --git a/luni/src/main/java/javax/xml/transform/dom/package.html b/luni/src/main/java/javax/xml/transform/dom/package.html
deleted file mode 100644
index 267e771..0000000
--- a/luni/src/main/java/javax/xml/transform/dom/package.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<title>javax.xml.transform.dom</title>
-</head>
-<body>
-<p>This package implements DOM-specific transformation APIs.</p>
-<p>The {@link javax.xml.transform.dom.DOMSource} class allows the
-client of the implementation of this API to specify a DOM
-{@link org.w3c.dom.Node} as the source of the input tree. The model of
-how the Transformer deals with the DOM tree in terms of mismatches with the
-<A href="http://www.w3.org/TR/xslt#data-model">XSLT data model</A> or
-other data models is beyond the scope of this document. Any of the nodes
-derived from {@link org.w3c.dom.Node} are legal input.</p>
-<p>The {@link javax.xml.transform.dom.DOMResult} class allows
-a {@link org.w3c.dom.Node} to be specified to which result DOM nodes will
-be appended. If an output node is not specified, the transformer will use
-{@link javax.xml.parsers.DocumentBuilder#newDocument} to create an
-output {@link org.w3c.dom.Document} node. If a node is specified, it
-should be one of the following: {@link org.w3c.dom.Document},
-{@link org.w3c.dom.Element}, or
-{@link org.w3c.dom.DocumentFragment}. Specification of any other node
-type is implementation dependent and undefined by this API. If the result is a
-{@link org.w3c.dom.Document}, the output of the transformation must have
-a single element root to set as the document element.</p>
-<p>The {@link javax.xml.transform.dom.DOMLocator} node may be passed
-to {@link javax.xml.transform.TransformerException} objects, and
-retrieved by trying to cast the result of the
-{@link javax.xml.transform.TransformerException#getLocator()} method.
-The implementation has no responsibility to use a DOMLocator instead of a
-{@link javax.xml.transform.SourceLocator} (though line numbers and the
-like do not make much sense for a DOM), so the result of getLocator must always
-be tested with an instanceof. </p>
-</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/transform/package.html b/luni/src/main/java/javax/xml/transform/package.html
deleted file mode 100644
index ca08c7c..0000000
--- a/luni/src/main/java/javax/xml/transform/package.html
+++ /dev/null
@@ -1,201 +0,0 @@
-<?xml version="1.0"?>
-<!-- $Id: package.html 226183 2005-04-08 10:39:14Z neeraj $ -->
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>javax.xml.transform</title>
-</head>
-
-<body>
-<p>This package defines the generic APIs for processing transformation
-instructions, and performing a transformation from source to result. These
-interfaces have no dependencies on SAX or the DOM standard, and try to make as
-few assumptions as possible about the details of the source and result of a
-transformation. It achieves this by defining
-{@link javax.xml.transform.Source} and
-{@link javax.xml.transform.Result} interfaces.
-</p>
-
-<p>To define concrete classes for the user, the API defines specializations
-of the interfaces found at the root level. These interfaces are found in
-{@link javax.xml.transform.sax}, {@link javax.xml.transform.dom},
-and {@link javax.xml.transform.stream}.
-</p>
-
-
-<h3>Creating Objects</h3>
-
-<p>The API allows a concrete
-{@link javax.xml.transform.TransformerFactory} object to be created from
-the static function
-{@link javax.xml.transform.TransformerFactory#newInstance}.
-</p>
-
-
-<h3>Specification of Inputs and Outputs</h3>
-
-<p>This API defines two interface objects called
-{@link javax.xml.transform.Source} and
-{@link javax.xml.transform.Result}. In order to pass Source and Result
-objects to the interfaces, concrete classes must be used.
-Three concrete representations are defined for each of these
-objects:
-{@link javax.xml.transform.stream.StreamSource} and
-{@link javax.xml.transform.stream.StreamResult},
-{@link javax.xml.transform.sax.SAXSource} and
-{@link javax.xml.transform.sax.SAXResult}, and
-{@link javax.xml.transform.dom.DOMSource} and
-{@link javax.xml.transform.dom.DOMResult}. Each of these objects defines
-a FEATURE string (which is i the form of a URL), which can be passed into
-{@link javax.xml.transform.TransformerFactory#getFeature} to see if the
-given type of Source or Result object is supported. For instance, to test if a
-DOMSource and a StreamResult is supported, you can apply the following
-test.
-</p>
-
-<pre>
-<code>
-TransformerFactory tfactory = TransformerFactory.newInstance();
-if (tfactory.getFeature(DOMSource.FEATURE) &amp;&amp; tfactory.getFeature(StreamResult.FEATURE)) {
-...
-}
-</code>
-</pre>
-
-
-<h3>
-<a name="qname-delimiter">Qualified Name Representation</a>
-</h3>
-
-<p><a href="http://www.w3.org/TR/REC-xml-names">Namespaces</a>
-present something of a problem area when dealing with XML objects. Qualified
-Names appear in XML markup as prefixed names. But the prefixes themselves do
-not hold identity. Rather, it is the URIs that they contextually map to that
-hold the identity. Therefore, when passing a Qualified Name like "xyz:foo"
-among Java programs, one must provide a means to map "xyz" to a namespace.
-</p>
-
-<p>One solution has been to create a "QName" object that holds the
-namespace URI, as well as the prefix and local name, but this is not always an
-optimal solution, as when, for example, you want to use unique strings as keys
-in a dictionary object. Not having a string representation also makes it
-difficult to specify a namespaced identity outside the context of an XML
-document.
-</p>
-
-<p>In order to pass namespaced values to transformations,
-for 
-instance when setting a property or a parameter on a 
-{@link javax.xml.transform.Transformer} object,
-this specification defines that a
-String "qname" object parameter be passed as two-part string, the namespace URI
-enclosed in curly braces ({}), followed by the local name. If the qname has a
-null URI, then the String object only contains the local name. An application
-can safely check for a non-null URI by testing to see if the first character of
-the name is a '{' character.
-</p>
-
-<p>For example, if a URI and local name were obtained from an element
-defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
-then the Qualified Name would be "{http://xyz.foo.com/yada/baz.html}foo".
-Note that the prefix is lost.
-</p>
-
-
-<h3>Result Tree Serialization</h3>
-
-<p>Serialization of the result tree to a stream can be controlled with
-the {@link javax.xml.transform.Transformer#setOutputProperties} and the
-{@link javax.xml.transform.Transformer#setOutputProperty} methods.
-These properties only apply to stream results, they have no effect when
-the result is a DOM tree or SAX event stream.</p>
-
-<p>Strings that match the <a href="http://www.w3.org/TR/xslt#output">XSLT
-specification for xsl:output attributes</a> can be referenced from the
-{@link javax.xml.transform.OutputKeys} class. Other strings can be
-specified as well.
-If the transformer does not recognize an output key, a
-{@link java.lang.IllegalArgumentException} is thrown, unless the
-key name is <a href="#qname-delimiter">namespace qualified</a>. Output key names
-that are namespace qualified are always allowed, although they may be
-ignored by some implementations.</p>
-
-<p>If all that is desired is the simple identity transformation of a
-source to a result, then {@link javax.xml.transform.TransformerFactory}
-provides a
-{@link javax.xml.transform.TransformerFactory#newTransformer()} method
-with no arguments. This method creates a Transformer that effectively copies
-the source to the result. This method may be used to create a DOM from SAX
-events or to create an XML or HTML stream from a DOM or SAX events.  </p>
-
-<h3>Exceptions and Error Reporting</h3>
-
-<p>The transformation API throw three types of specialized exceptions. A
-{@link javax.xml.transform.TransformerFactoryConfigurationError} is parallel to
-the {@link javax.xml.parsers.FactoryConfigurationError}, and is thrown
-when a configuration problem with the TransformerFactory exists. This error
-will typically be thrown when the transformation factory class specified with
-the "javax.xml.transform.TransformerFactory" system property cannot be found or
-instantiated.</p>
-
-<p>A {@link javax.xml.transform.TransformerConfigurationException}
-may be thrown if for any reason a Transformer can not be created. A
-TransformerConfigurationException may be thrown if there is a syntax error in
-the transformation instructions, for example when
-{@link javax.xml.transform.TransformerFactory#newTransformer} is
-called.</p>
-
-<p>{@link javax.xml.transform.TransformerException} is a general
-exception that occurs during the course of a transformation. A transformer
-exception may wrap another exception, and if any of the
-{@link javax.xml.transform.TransformerException#printStackTrace()}
-methods are called on it, it will produce a list of stack dumps, starting from
-the most recent. The transformer exception also provides a
-{@link javax.xml.transform.SourceLocator} object which indicates where
-in the source tree or transformation instructions the error occurred.
-{@link javax.xml.transform.TransformerException#getMessageAndLocation()}
-may be called to get an error message with location info, and
-{@link javax.xml.transform.TransformerException#getLocationAsString()}
-may be called to get just the location string.</p>
-
-<p>Transformation warnings and errors are sent to an
-{@link javax.xml.transform.ErrorListener}, at which point the
-application may decide to report the error or warning, and may decide to throw
-an <code>Exception</code> for a non-fatal error. The <code>ErrorListener</code> may be set via
-{@link javax.xml.transform.TransformerFactory#setErrorListener} for
-reporting errors that have to do with syntax errors in the transformation
-instructions, or via
-{@link javax.xml.transform.Transformer#setErrorListener} to report
-errors that occur during the transformation. The <code>ErrorListener</code> on both objects
-will always be valid and non-<code>null</code>, whether set by the application or a default
-implementation provided by the processor.
-The default implementation provided by the processor will report all warnings and errors to <code>System.err</code>
-and does not throw any <code>Exception</code>s.
-Applications are <em>strongly</em> encouraged to register and use
-<code>ErrorListener</code>s that insure proper behavior for warnings and
-errors.
-</p>
-
-
-<h3>Resolution of URIs within a transformation</h3>
-
-<p>The API provides a way for URIs referenced from within the stylesheet
-instructions or within the transformation to be resolved by the calling
-application. This can be done by creating a class that implements the
-{@link javax.xml.transform.URIResolver} interface, with its one method,
-{@link javax.xml.transform.URIResolver#resolve}, and use this class to
-set the URI resolution for the transformation instructions or transformation
-with {@link javax.xml.transform.TransformerFactory#setURIResolver} or
-{@link javax.xml.transform.Transformer#setURIResolver}. The
-<code>URIResolver.resolve</code> method takes two String arguments, the URI found in the
-stylesheet instructions or built as part of the transformation process, and the
-base URI 
-against which the first argument will be made absolute if the
-absolute URI is required.
-The returned {@link javax.xml.transform.Source} object must be usable by
-the transformer, as specified in its implemented features.</p>
-
-
-</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/transform/sax/package.html b/luni/src/main/java/javax/xml/transform/sax/package.html
deleted file mode 100644
index 7a0d707..0000000
--- a/luni/src/main/java/javax/xml/transform/sax/package.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<title>javax.xml.transform.sax</title>
-</head>
-<body>
-<p>This package implements SAX2-specific transformation APIs. It provides
-  classes which allow input from {@link org.xml.sax.ContentHandler}
-  events, and also classes that produce org.xml.sax.ContentHandler events. It
-  also provides methods to set the input source as an
-  {@link org.xml.sax.XMLReader}, or to use a
-  {@link org.xml.sax.InputSource} as the source. It also allows the
-  creation of a {@link org.xml.sax.XMLFilter}, which enables
-  transformations to "pull" from other transformations, and lets the transformer
-  to be used polymorphically as an {@link org.xml.sax.XMLReader}.</p>
-<p>The {@link javax.xml.transform.sax.SAXSource} class allows the
-  setting of an {@link org.xml.sax.XMLReader} to be used for "pulling"
-  parse events, and an {@link org.xml.sax.InputSource} that may be used to
-  specify the SAX source.</p>
-<p>The {@link javax.xml.transform.sax.SAXResult} class allows the
-  setting of a {@link org.xml.sax.ContentHandler} to be the receiver of
-  SAX2 events from the transformation. 
-<p>The {@link javax.xml.transform.sax.SAXTransformerFactory} extends
-  {@link javax.xml.transform.TransformerFactory} to provide factory
-  methods for creating {@link javax.xml.transform.sax.TemplatesHandler},
-  {@link javax.xml.transform.sax.TransformerHandler}, and
-  {@link org.xml.sax.XMLReader} instances.</p>
-<p>To obtain a {@link javax.xml.transform.sax.SAXTransformerFactory},
-  the caller must cast the {@link javax.xml.transform.TransformerFactory}
-  instance returned from
-  {@link javax.xml.transform.TransformerFactory#newInstance}. 
-
-<p>The {@link javax.xml.transform.sax.TransformerHandler} interface
-  allows a transformation to be created from SAX2 parse events, which is a "push"
-  model rather than the "pull" model that normally occurs for a transformation.
-  Normal parse events are received through the
-  {@link org.xml.sax.ContentHandler} interface, lexical events such as
-  startCDATA and endCDATA are received through the
-  {@link org.xml.sax.ext.LexicalHandler} interface, and events that signal
-  the start or end of disabling output escaping are received via
-  {@link org.xml.sax.ContentHandler#processingInstruction}, with the
-  target parameter being
-  {@link javax.xml.transform.Result#PI_DISABLE_OUTPUT_ESCAPING} and
-  {@link javax.xml.transform.Result#PI_ENABLE_OUTPUT_ESCAPING}. If
-  parameters, output properties, or other features need to be set on the
-  Transformer handler, a {@link javax.xml.transform.Transformer} reference
-  will need to be obtained from
-  {@link javax.xml.transform.sax.TransformerHandler#getTransformer}, and
-  the methods invoked from that reference. 
-
-<p>The {@link javax.xml.transform.sax.TemplatesHandler} interface
-  allows the creation of {@link javax.xml.transform.Templates} objects
-  from SAX2 parse events. Once the {@link org.xml.sax.ContentHandler}
-  events are complete, the Templates object may be obtained from
-  {@link javax.xml.transform.sax.TemplatesHandler#getTemplates}. Note that
-  {@link javax.xml.transform.sax.TemplatesHandler#setSystemId} should
-  normally be called in order to establish a base system ID from which relative
-  URLs may be resolved. 
-<p>The
-  {@link javax.xml.transform.sax.SAXTransformerFactory#newXMLFilter}
-  method allows the creation of a {@link org.xml.sax.XMLFilter}, which
-  encapsulates the SAX2 notion of a "pull" transformation. The following
-  illustrates several transformations chained together. Each filter points to a
-  parent {@link org.xml.sax.XMLReader}, and the final transformation is
-  caused by invoking {@link org.xml.sax.XMLReader#parse} on the final
-  reader in the chain.</p>
-</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/transform/stream/package.html b/luni/src/main/java/javax/xml/transform/stream/package.html
deleted file mode 100644
index df54abd..0000000
--- a/luni/src/main/java/javax/xml/transform/stream/package.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<title>javax.xml.transform.stream</title>
-</head>
-<body>
-<p>This package implements stream- and URI- specific transformation APIs.
-	 </p>
-<p>The {@link javax.xml.transform.stream.StreamSource} class
-	 provides methods for specifying {@link java.io.InputStream} input,
-	 {@link java.io.Reader} input, and URL input in the form of strings. Even
-	 if an input stream or reader is specified as the source,
-	 {@link javax.xml.transform.stream.StreamSource#setSystemId} should still
-	 be called, so that the transformer can know from where it should resolve
-	 relative URIs. The public identifier is always optional: if the application
-	 writer includes one, it will be provided as part of the
-	 {@link javax.xml.transform.SourceLocator} information.</p>
-<p>The {@link javax.xml.transform.stream.StreamResult} class
-	 provides methods for specifying {@link java.io.OutputStream},
-	 {@link java.io.Writer}, or an output system ID, as the output of the
-	 transformation result.</p>
-<p>Normally streams should be used rather than readers or writers, for
-	 both the Source and Result, since readers and writers already have the encoding
-	 established to and from the internal Unicode format. However, there are times
-	 when it is useful to write to a character stream, such as when using a
-	 StringWriter in order to write to a String, or in the case of reading source
-	 XML from a StringReader.</p>
-</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/validation/package.html b/luni/src/main/java/javax/xml/validation/package.html
deleted file mode 100644
index b9f71a4..0000000
--- a/luni/src/main/java/javax/xml/validation/package.html
+++ /dev/null
@@ -1,91 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
-<!-- $Id: package.html 570104 2007-08-27 13:28:24Z mrglavas $ -->
-<html>
-	<body bgcolor="white">
-		<p>
-		    This package provides an API for validation of XML documents.  <em>Validation</em> is the process of verifying
-		    that an XML document is an instance of a specified XML <em>schema</em>.  An XML schema defines the
-		    content model (also called a <em>grammar</em> or <em>vocabulary</em>) that its instance documents
-		    will represent.
-        </p>
-        <p>
-            There are a number of popular technologies available for creating an XML schema. Some of the most
-            popular include:
-            <ul>
-                <li><strong>Document Type Definition (DTD)</strong> - XML's built-in schema language.</li>
-                <li><strong><a href="http://www.w3.org/XML/Schema">W3C XML Schema (WXS)</a></strong> - an object-oriented XML schema
-                    language. WXS also provides a type system for constraining the character data of an XML document.
-                    WXS is maintained by the <a href="http://www.w3.org">World Wide Web Consortium (W3C)</a> and is a W3C
-                    Recommendation (that is, a ratified W3C standard specification).</li>
-                <li><strong><a href="http://www.relaxng.org">RELAX NG (RNG)</a></strong> - a pattern-based,
-                    user-friendly XML schema language. RNG schemas may also use types to constrain XML character data.
-                    RNG is maintained by the <a href="http://www.oasis-open.org">Organization for the Advancement of
-                    Structured Information Standards (OASIS)</a> and is both an OASIS and an
-                    <a href="http://www.iso.org">ISO (International Organization for Standardization)</a> standard.</li>
-                <li><strong><a href="http://www.schematron.com/">Schematron</a></strong> - a rules-based XML schema
-                language. Whereas DTD, WXS, and RNG are designed to express the structure of a content model,
-                Schematron is designed to enforce individual rules that are difficult or impossible to express
-                with other schema languages. Schematron is intended to supplement a schema written in
-                structural schema language such as the aforementioned. Schematron is in the process
-                of becoming an ISO standard.</li>
-            </ul>
-        </p>
-        <p>
-		    Previous versions of JAXP supported validation as a feature of an XML parser, represented by
-		    either a {@link javax.xml.parsers.SAXParser} or {@link javax.xml.parsers.DocumentBuilder} instance.
-        </p>
-        <p>
-		    The JAXP validation API decouples the validation of an instance document from the parsing of an
-		    XML document. This is advantageous for several reasons, some of which are:
-		    <ul>
-		        <li><strong>Support for additional schema languages.</strong> As of JDK 1.5, the two most
-		        popular JAXP parser implementations, Crimson and Xerces, only support a subset of the available
-		        XML schema languages. The Validation API provides a standard mechanism through which applications
-		        may take of advantage of specialization validation libraries which support additional schema
-		        languages.</li>
-		        <li><strong>Easy runtime coupling of an XML instance and schema.</strong> Specifying the location
-		        of a schema to use for validation with JAXP parsers can be confusing. The Validation API makes this
-		        process simple (see <a href="#example-1">example</a> below).</li>
-          </ul>
-		</p>
-		<p>
-            <a name="example-1"><strong>Usage example</strong>.</a> The following example demonstrates validating
-            an XML document with the Validation API (for readability, some exception handling is not shown):
-            <pre>
-            
-    // parse an XML document into a DOM tree
-    DocumentBuilderFactory parserFactory = DocumentBuilderFactory.newInstance();
-    parserFactory.setNamespaceAware(true);
-    DocumentBuilder parser = parserFactory.newDocumentBuilder();
-    Document document = parser.parse(new File("instance.xml"));
-
-    // create a SchemaFactory capable of understanding WXS schemas
-    SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
-    // load a WXS schema, represented by a Schema instance
-    Source schemaFile = new StreamSource(new File("mySchema.xsd"));
-    Schema schema = factory.newSchema(schemaFile);
-
-    // create a Validator instance, which can be used to validate an instance document
-    Validator validator = schema.newValidator();
-
-    // validate the DOM tree
-    try {
-        validator.validate(new DOMSource(document));
-    } catch (SAXException e) {
-        // instance document is invalid!
-    }
-</pre>
-		</p>
-		<p>
-		    The JAXP parsing API has been integrated with the Validation API. Applications may create a {@link javax.xml.validation.Schema} with the validation API
-		    and associate it with a {@link javax.xml.parsers.DocumentBuilderFactory} or a {@link javax.xml.parsers.SAXParserFactory} instance
-		    by using the {@link javax.xml.parsers.DocumentBuilderFactory#setSchema(Schema)} and {@link javax.xml.parsers.SAXParserFactory#setSchema(Schema)}
-		    methods. <strong>You should not</strong> both set a schema and call <code>setValidating(true)</code> on a parser factory. The former technique
-		    will cause parsers to use the new validation API; the latter will cause parsers to use their own internal validation
-		    facilities. <strong>Turning on both of these options simultaneously will cause either redundant behavior or error conditions.</strong>
-        </p>
-        <p>
-
-	</body>
-</html>
diff --git a/luni/src/main/java/javax/xml/xpath/package.html b/luni/src/main/java/javax/xml/xpath/package.html
deleted file mode 100644
index 202a29c..0000000
--- a/luni/src/main/java/javax/xml/xpath/package.html
+++ /dev/null
@@ -1,261 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- $Id: package.html 570107 2007-08-27 13:30:19Z mrglavas $ -->
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title>javax.xml.xpath</title>
-<meta name="@author" content="mailto:Ben@galbraiths.org" />
-<meta name="@author" content="mailto:Norman.Walsh@Sun.com" />
-<meta name="@author" content="mailto:Jeff.Suttor@Sun.com" />
-<meta name="@version" content="$Revision: 570107 $, $Date: 2007-08-27 06:30:19 -0700 (Mon, 27 Aug 2007) $" />
-<meta name="@see" content="http://www.w3.org/TR/xpath" />
-<meta name="@since" content="1.5" />
-</head>
-
-<body>
-
-<p>This package provides an <em>object-model neutral</em> API for the
-evaluation of XPath expressions and access to the evaluation
-environment.
-</p>
-
-<p>The following XML standards apply:</p>
-
-<ul>
-<li><a href="http://www.w3.org/TR/xpath">XML Path Language (XPath) Version 1.0</a></li>
-</ul>
-
-<hr />
-
-<h2>XPath Overview</h2>
-
-<p>The XPath language provides a simple, concise syntax for selecting
-nodes from an XML document. XPath also provides rules for converting a
-node in an XML document object model (DOM) tree to a boolean, double,
-or string value. XPath is a W3C-defined language and an official W3C
-recommendation; the W3C hosts the XML Path Language (XPath) Version
-1.0 specification.
-</p>
-
-<p>XPath started in life in 1999 as a supplement to the XSLT and
-XPointer languages, but has more recently become popular as a
-stand-alone language, as a single XPath expression can be used to
-replace many lines of DOM API code.
-</p>
-
-<h3>XPath Expressions</h3>
-
-<p>An XPath <em>expression</em> is composed of a <em>location
-path</em> and one or more optional <em>predicates</em>. Expressions
-may also include XPath variables.
-</p>
-
-<p>The following is an example of a simple XPath expression:</p>
-
-<pre>
-/foo/bar
-</pre>
-
-<p>This example would select the <code>&lt;bar&gt;</code> element in
-an XML document such as the following:</p>
-
-<pre>
-&lt;foo&gt;
-&lt;bar/&gt;
-&lt;/foo&gt;
-</pre>
-
-<p>The expression <code>/foo/bar</code> is an example of a location
-path. While XPath location paths resemble Unix-style file system
-paths, an important distinction is that XPath expressions return
-<em>all</em> nodes that match the expression. Thus, all three
-<code>&lt;bar&gt;</code> elements in the following document would be
-selected by the <code>/foo/bar</code> expression:</p>
-
-<pre>
-&lt;foo&gt;
-&lt;bar/&gt;
-&lt;bar/&gt;
-&lt;bar/&gt;
-&lt;/foo&gt;
-</pre>
-
-<p>A special location path operator, <code>//</code>, selects nodes at
-any depth in an XML document. The following example selects all
-<code>&lt;bar&gt;</code> elements regardless of their location in a
-document:</p>
-
-<pre>
-//bar
-</pre>
-
-<p>A wildcard operator, *, causes all element nodes to be selected.
-The following example selects all children elements of a
-<code>&lt;foo&gt;</code> element:</p>
-
-<pre>
-/foo/*
-</pre>
-
-<p>In addition to element nodes, XPath location paths may also address
-attribute nodes, text nodes, comment nodes, and processing instruction
-nodes. The following table gives examples of location paths for each
-of these node types:</p>
-
-<table border="1">
-<tr>
-<td>Location Path</td>
-<td>Description</td>
-</tr>
-<tr>
-<td>
-<code>/foo/bar/<strong>@id</strong></code>
-</td>
-<td>Selects the attribute <code>id</code> of the <code>&lt;bar&gt;</code> element
-</td>
-</tr>
-<tr>
-<td><code>/foo/bar/<strong>text()</strong></code>
-</td>
-<td>Selects the text nodes of the <code>&lt;bar&gt;</code> element. No
-distinction is made between escaped and non-escaped character data.
-</td>
-</tr>
-<tr>
-<td><code>/foo/bar/<strong>comment()</strong></code>
-</td>
-<td>Selects all comment nodes contained in the <code>&lt;bar&gt;</code> element.
-</td>
-</tr>
-<tr>
-<td><code>/foo/bar/<strong>processing-instruction()</strong></code>
-</td>
-<td>Selects all processing-instruction nodes contained in the
-<code>&lt;bar&gt;</code> element.
-</td>
-</tr>
-</table>
-
-<p>Predicates allow for refining the nodes selected by an XPath
-location path. Predicates are of the form
-<code>[<em>expression</em>]</code>. The following example selects all
-<code>&lt;foo&gt;</code> elements that contain an <code>include</code>
-attribute with the value of <code>true</code>:</p>
-
-<pre>
-//foo[@include='true']
-</pre>
-
-<p>Predicates may be appended to each other to further refine an
-expression, such as:</p>
-
-<pre>
-//foo[@include='true'][@mode='bar']
-</pre>
-
-<h3>Using the XPath API</h3>
-
-<p>
-The following example demonstrates using the XPath API to select one
-or more nodes from an XML document:</p>
-
-<pre>
-XPath xpath = XPathFactory.newInstance().newXPath();
-String expression = "/widgets/widget";
-InputSource inputSource = new InputSource("widgets.xml");
-NodeList nodes = (NodeList) xpath.evaluate(expression, inputSource, XPathConstants.NODESET);
-</pre>
-
-<h3>XPath Expressions and Types</h3>
-
-<p>While XPath expressions select nodes in the XML document, the XPath
-API allows the selected nodes to be coalesced into one of the
-following other data types:</p>
-
-<ul>
-<li><code>Boolean</code></li>
-<li><code>Number</code></li>
-<li><code>String</code></li>
-</ul>
-
-<p>The desired return type is specified by a {@link
-javax.xml.namespace.QName} parameter in method call used to evaluate
-the expression, which is either a call to
-<code>XPathExpression.evaluate(...)</code> or to one of the
-<code>XPath.evaluate(...)</code> convenience methods. The allowed
-QName values are specified as constants in the {@link
-javax.xml.xpath.XPathConstants} class; they are:</p>
-
-<ul>
-<li>{@link javax.xml.xpath.XPathConstants#NODESET}</li>
-<li>{@link javax.xml.xpath.XPathConstants#NODE}</li>
-<li>{@link javax.xml.xpath.XPathConstants#STRING}</li>
-<li>{@link javax.xml.xpath.XPathConstants#BOOLEAN}</li>
-<li>{@link javax.xml.xpath.XPathConstants#NUMBER}</li>
-</ul>
-
-<p>When a <code>Boolean</code> return type is requested,
-<code>Boolean.TRUE</code> is returned if one or more nodes were
-selected; otherwise, <code>Boolean.FALSE</code> is returned.</p>
-
-<p>The <code>String</code> return type is a convenience for retrieving
-the character data from a text node, attribute node, comment node, or
-processing-instruction node. When used on an element node, the value
-of the child text nodes is returned.
-</p>
-
-<p>The <code>Number</code> return type attempts to coalesce the text
-of a node to a <code>double</code> data type.
-</p>
-
-<h3>XPath Context</h3>
-
-<p>XPath location paths may be relative to a particular node in the
-document, known as the <code>context</code>. Consider the following
-XML document:</p>
-
-<pre>
-&lt;widgets&gt;
-&lt;widget&gt;
-&lt;manufacturer/&gt;
-&lt;dimensions/&gt;
-&lt;/widget&gt;
-&lt;/widgets&gt;
-</pre>
-
-<p>The <code>&lt;widget&gt;</code> element can be selected with the
-following XPath API code:</p>
-
-<pre>
-// parse the XML as a W3C Document
-DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
-builderFactory.setNamespaceAware(true);
-DocumentBuilder builder = builderFactory.newDocumentBuilder();
-Document document = builder.parse(new File("/widgets.xml"));
-
-XPath xpath = XPathFactory.newInstance().newXPath();
-String expression = "/widgets/widget";
-Node widgetNode = (Node) xpath.evaluate(expression, document, XPathConstants.NODE);
-</pre>
-
-<p>With a reference to the <code>&lt;widget&gt;</code> element, a
-relative XPath expression can now written to select the
-<code>&lt;manufacturer&gt;</code> child element:</p>
-
-<pre>
-XPath xpath = XPathFactory.newInstance().newXPath();
-<strong>String expression = "manufacturer";</strong>
-Node manufacturerNode = (Node) xpath.evaluate(expression, <strong>widgetNode</strong>, XPathConstants.NODE);
-</pre>
-
-<ul>
-<li>Author <a href="mailto:Ben@galbraiths.org">Ben Galbraith</a></li>
-<li>Author <a href="mailto:Norman.Walsh@Sun.com">Norman Walsh</a></li>
-<li>Author <a href="mailto:Jeff.Suttor@Sun.com">Jeff Suttor</a></li>
-<li>See <a href="http://www.w3.org/TR/xpath">XML Path Language (XPath) Version 1.0</a></li>
-<li>Since 1.5</li>
-</ul>		
-</body>
-</html>
diff --git a/luni/src/main/java/libcore/icu/AlphabeticIndex.java b/luni/src/main/java/libcore/icu/AlphabeticIndex.java
new file mode 100644
index 0000000..efd45e9
--- /dev/null
+++ b/luni/src/main/java/libcore/icu/AlphabeticIndex.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2013 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.icu;
+
+import java.util.Locale;
+
+/**
+ * Exposes icu4c's AlphabeticIndex.
+ */
+public final class AlphabeticIndex {
+
+  /**
+   * Exposes icu4c's ImmutableIndex (new to icu 51). This exposes a read-only,
+   * thread safe snapshot view of an AlphabeticIndex at the moment it was
+   * created, and allows for random access to buckets by index.
+   */
+  public static final class ImmutableIndex {
+    private long peer;
+
+    private ImmutableIndex(long peer) {
+      this.peer = peer;
+    }
+
+    @Override protected synchronized void finalize() throws Throwable {
+      try {
+        destroy(peer);
+        peer = 0;
+      } finally {
+        super.finalize();
+      }
+    }
+
+    /**
+     * Returns the number of the label buckets in this index.
+     */
+    public int getBucketCount() {
+      return getBucketCount(peer);
+    }
+
+    /**
+     * Returns the index of the bucket in which 's' should appear.
+     * Function is synchronized because underlying routine walks an iterator
+     * whose state is maintained inside the index object.
+     */
+    public int getBucketIndex(String s) {
+      return getBucketIndex(peer, s);
+    }
+
+    /**
+     * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+     */
+    public String getBucketLabel(int index) {
+      return getBucketLabel(peer, index);
+    }
+
+    private static native int getBucketCount(long peer);
+    private static native int getBucketIndex(long peer, String s);
+    private static native String getBucketLabel(long peer, int index);
+  }
+
+  private long peer;
+
+  /**
+   * Creates a new AlphabeticIndex for the given locale.
+   */
+  public AlphabeticIndex(Locale locale) {
+    peer = create(locale.toString());
+  }
+
+  @Override protected synchronized void finalize() throws Throwable {
+    try {
+      destroy(peer);
+      peer = 0;
+    } finally {
+      super.finalize();
+    }
+  }
+
+  /**
+   * Adds the index characters from the given locale to the index.
+   * The labels are added to those that are already in the index;
+   * they do not replace the existing index characters.
+   * The collation order for this index is not changed;
+   * it remains that of the locale that was originally specified
+   * when creating this index.
+   */
+  public synchronized AlphabeticIndex addLabels(Locale locale) {
+    addLabels(peer, locale.toString());
+    return this;
+  }
+
+  /**
+   * Adds the index characters in the range between the specified start and
+   * end code points, inclusive.
+   */
+  public synchronized AlphabeticIndex addLabelRange(int codePointStart, int codePointEnd) {
+    addLabelRange(peer, codePointStart, codePointEnd);
+    return this;
+  }
+
+  /**
+   * Returns the number of the label buckets in this index.
+   */
+  public synchronized int getBucketCount() {
+    return getBucketCount(peer);
+  }
+
+  /**
+   * Returns the index of the bucket in which 's' should appear.
+   * Function is synchronized because underlying routine walks an iterator
+   * whose state is maintained inside the index object.
+   */
+  public synchronized int getBucketIndex(String s) {
+    return getBucketIndex(peer, s);
+  }
+
+  /**
+   * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+   */
+  public synchronized String getBucketLabel(int index) {
+    return getBucketLabel(peer, index);
+  }
+
+  /**
+   * Returns an ImmutableIndex created from this AlphabeticIndex.
+   */
+  public synchronized ImmutableIndex getImmutableIndex() {
+    return new ImmutableIndex(buildImmutableIndex(peer));
+  }
+
+  private static native long create(String locale);
+  private static native void destroy(long peer);
+  private static native void addLabels(long peer, String locale);
+  private static native void addLabelRange(long peer, int codePointStart, int codePointEnd);
+  private static native int getBucketCount(long peer);
+  private static native int getBucketIndex(long peer, String s);
+  private static native String getBucketLabel(long peer, int index);
+  private static native long buildImmutableIndex(long peer);
+}
diff --git a/luni/src/main/java/libcore/icu/ICU.java b/luni/src/main/java/libcore/icu/ICU.java
index 9984414..8e0cd0b 100644
--- a/luni/src/main/java/libcore/icu/ICU.java
+++ b/luni/src/main/java/libcore/icu/ICU.java
@@ -127,13 +127,20 @@
         return localesFromStrings(getAvailableNumberFormatLocalesNative());
     }
 
+    public static native String getBestDateTimePattern(String skeleton, String localeName);
+
     /**
-     * Returns the ICU version in use. This is "4.4" for gingerbread, for example.
+     * Returns the version of the CLDR data in use, such as "22.1.1".
+     */
+    public static native String getCldrVersion();
+
+    /**
+     * Returns the icu4c version in use, such as "50.1.1".
      */
     public static native String getIcuVersion();
 
     /**
-     * Returns the Unicode version our ICU supports. This is "5.2" for gingerbread, for example.
+     * Returns the Unicode version our ICU supports, such as "6.2".
      */
     public static native String getUnicodeVersion();
 
diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java
index 8ec2294..7a8607b 100644
--- a/luni/src/main/java/libcore/icu/LocaleData.java
+++ b/luni/src/main/java/libcore/icu/LocaleData.java
@@ -79,6 +79,10 @@
     public String mediumDateFormat;
     public String shortDateFormat;
 
+    // Used by android.text.format.DateFormat.getTimeFormat.
+    public String timeFormat12; // "hh:mm a"
+    public String timeFormat24; // "HH:mm"
+
     // Used by DecimalFormatSymbols.
     public char zeroDigit;
     public char decimalSeparator;
diff --git a/luni/src/main/java/libcore/icu/NativeBreakIterator.java b/luni/src/main/java/libcore/icu/NativeBreakIterator.java
index 88ceb71..4156f9a 100644
--- a/luni/src/main/java/libcore/icu/NativeBreakIterator.java
+++ b/luni/src/main/java/libcore/icu/NativeBreakIterator.java
@@ -27,22 +27,27 @@
     private static final int BI_LINE_INSTANCE = 3;
     private static final int BI_SENT_INSTANCE = 4;
 
+    // The address of the native peer.
+    // Uses of this must be manually synchronized to avoid native crashes.
     private final int address;
+
     private final int type;
-    private CharacterIterator charIter;
+    private String string;
+    private CharacterIterator charIterator;
 
     private NativeBreakIterator(int address, int type) {
         this.address = address;
         this.type = type;
-        this.charIter = new StringCharacterIterator("");
+        this.charIterator = new StringCharacterIterator("");
     }
 
     @Override
     public Object clone() {
         int cloneAddr = cloneImpl(this.address);
         NativeBreakIterator clone = new NativeBreakIterator(cloneAddr, this.type);
+        clone.string = this.string;
         // The RI doesn't clone the CharacterIterator.
-        clone.charIter = this.charIter;
+        clone.charIterator = this.charIterator;
         return clone;
     }
 
@@ -56,7 +61,7 @@
         }
         // TODO: is this sufficient? shouldn't we be checking the underlying rules?
         NativeBreakIterator rhs = (NativeBreakIterator) object;
-        return type == rhs.type && charIter.equals(rhs.charIter);
+        return type == rhs.type && charIterator.equals(rhs.charIterator);
     }
 
     @Override
@@ -66,44 +71,44 @@
 
     @Override protected void finalize() throws Throwable {
         try {
-            closeBreakIteratorImpl(this.address);
+            closeImpl(this.address);
         } finally {
             super.finalize();
         }
     }
 
     public int current() {
-        return currentImpl(this.address);
+        return currentImpl(this.address, this.string);
     }
 
     public int first() {
-        return firstImpl(this.address);
+        return firstImpl(this.address, this.string);
     }
 
     public int following(int offset) {
-        return followingImpl(this.address, offset);
+        return followingImpl(this.address, this.string, offset);
     }
 
     public CharacterIterator getText() {
-        int newLoc = currentImpl(this.address);
-        this.charIter.setIndex(newLoc);
-        return this.charIter;
+        int newLocation = currentImpl(this.address, this.string);
+        this.charIterator.setIndex(newLocation);
+        return this.charIterator;
     }
 
     public int last() {
-        return lastImpl(this.address);
+        return lastImpl(this.address, this.string);
     }
 
     public int next(int n) {
-        return nextImpl(this.address, n);
+        return nextImpl(this.address, this.string, n);
     }
 
     public int next() {
-        return nextImpl(this.address, 1);
+        return nextImpl(this.address, this.string, 1);
     }
 
     public int previous() {
-        return previousImpl(this.address);
+        return previousImpl(this.address, this.string);
     }
 
     public void setText(CharacterIterator newText) {
@@ -119,16 +124,21 @@
     }
 
     private void setText(String s, CharacterIterator it) {
-        this.charIter = it;
-        setTextImpl(this.address, s);
+        this.string = s;
+        this.charIterator = it;
+        setTextImpl(this.address, this.string);
+    }
+
+    public boolean hasText() {
+        return (string != null);
     }
 
     public boolean isBoundary(int offset) {
-        return isBoundaryImpl(this.address, offset);
+        return isBoundaryImpl(this.address, this.string, offset);
     }
 
     public int preceding(int offset) {
-        return precedingImpl(this.address, offset);
+      return precedingImpl(this.address, this.string, offset);
     }
 
     public static NativeBreakIterator getCharacterInstance(Locale where) {
@@ -151,15 +161,17 @@
     private static native int getWordInstanceImpl(String locale);
     private static native int getLineInstanceImpl(String locale);
     private static native int getSentenceInstanceImpl(String locale);
-    private static native void closeBreakIteratorImpl(int address);
-    private static native void setTextImpl(int address, String text);
-    private static native int cloneImpl(int address);
-    private static native int precedingImpl(int address, int offset);
-    private static native boolean isBoundaryImpl(int address, int offset);
-    private static native int nextImpl(int address, int n);
-    private static native int previousImpl(int address);
-    private static native int currentImpl(int address);
-    private static native int firstImpl(int address);
-    private static native int followingImpl(int address, int offset);
-    private static native int lastImpl(int address);
+    private static synchronized native int cloneImpl(int address);
+
+    private static synchronized native void closeImpl(int address);
+
+    private static synchronized native void setTextImpl(int address, String text);
+    private static synchronized native int precedingImpl(int address, String text, int offset);
+    private static synchronized native boolean isBoundaryImpl(int address, String text, int offset);
+    private static synchronized native int nextImpl(int address, String text, int n);
+    private static synchronized native int previousImpl(int address, String text);
+    private static synchronized native int currentImpl(int address, String text);
+    private static synchronized native int firstImpl(int address, String text);
+    private static synchronized native int followingImpl(int address, String text, int offset);
+    private static synchronized native int lastImpl(int address, String text);
 }
diff --git a/luni/src/main/java/libcore/icu/NativeConverter.java b/luni/src/main/java/libcore/icu/NativeConverter.java
index e18f483..1b8a7e0 100644
--- a/luni/src/main/java/libcore/icu/NativeConverter.java
+++ b/luni/src/main/java/libcore/icu/NativeConverter.java
@@ -21,7 +21,7 @@
     public static native int encode(long converterHandle, char[] input, int inEnd,
             byte[] output, int outEnd, int[] data, boolean flush);
 
-    public static native long openConverter(String encoding);
+    public static native long openConverter(String charsetName);
     public static native void closeConverter(long converterHandle);
 
     public static native void resetByteToChar(long converterHandle);
diff --git a/luni/src/main/java/libcore/icu/NativeDecimalFormat.java b/luni/src/main/java/libcore/icu/NativeDecimalFormat.java
index f6b5214..376526e 100644
--- a/luni/src/main/java/libcore/icu/NativeDecimalFormat.java
+++ b/luni/src/main/java/libcore/icu/NativeDecimalFormat.java
@@ -94,7 +94,7 @@
     /**
      * The address of the ICU DecimalFormat* on the native heap.
      */
-    private int address;
+    private long address;
 
     /**
      * The last pattern we gave to ICU, so we can make repeated applications cheap.
@@ -280,8 +280,11 @@
     }
 
     public AttributedCharacterIterator formatToCharacterIterator(Object object) {
+        if (object == null) {
+            throw new NullPointerException("object == null");
+        }
         if (!(object instanceof Number)) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("object not a Number: " + object.getClass());
         }
         Number number = (Number) object;
         FieldPositionIterator fpIter = new FieldPositionIterator();
@@ -478,7 +481,7 @@
         setAttribute(this.address, UNUM_PARSE_INT_ONLY, i);
     }
 
-    private static void applyPattern(int addr, boolean localized, String pattern) {
+    private static void applyPattern(long addr, boolean localized, String pattern) {
         try {
             applyPatternImpl(addr, localized, pattern);
         } catch (NullPointerException npe) {
@@ -617,28 +620,28 @@
         }
     }
 
-    private static native void applyPatternImpl(int addr, boolean localized, String pattern);
-    private static native int cloneImpl(int addr);
-    private static native void close(int addr);
-    private static native char[] formatLong(int addr, long value, FieldPositionIterator iter);
-    private static native char[] formatDouble(int addr, double value, FieldPositionIterator iter);
-    private static native char[] formatDigitList(int addr, String value, FieldPositionIterator iter);
-    private static native int getAttribute(int addr, int symbol);
-    private static native String getTextAttribute(int addr, int symbol);
-    private static native int open(String pattern, String currencySymbol,
+    private static native void applyPatternImpl(long addr, boolean localized, String pattern);
+    private static native int cloneImpl(long addr);
+    private static native void close(long addr);
+    private static native char[] formatLong(long addr, long value, FieldPositionIterator iter);
+    private static native char[] formatDouble(long addr, double value, FieldPositionIterator iter);
+    private static native char[] formatDigitList(long addr, String value, FieldPositionIterator iter);
+    private static native int getAttribute(long addr, int symbol);
+    private static native String getTextAttribute(long addr, int symbol);
+    private static native long open(String pattern, String currencySymbol,
             char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator,
             String infinity, String internationalCurrencySymbol, char minusSign,
             char monetaryDecimalSeparator, String nan, char patternSeparator, char percent,
             char perMill, char zeroDigit);
-    private static native Number parse(int addr, String string, ParsePosition position, boolean parseBigDecimal);
-    private static native void setDecimalFormatSymbols(int addr, String currencySymbol,
+    private static native Number parse(long addr, String string, ParsePosition position, boolean parseBigDecimal);
+    private static native void setDecimalFormatSymbols(long addr, String currencySymbol,
             char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator,
             String infinity, String internationalCurrencySymbol, char minusSign,
             char monetaryDecimalSeparator, String nan, char patternSeparator, char percent,
             char perMill, char zeroDigit);
-    private static native void setSymbol(int addr, int symbol, String str);
-    private static native void setAttribute(int addr, int symbol, int i);
-    private static native void setRoundingMode(int addr, int roundingMode, double roundingIncrement);
-    private static native void setTextAttribute(int addr, int symbol, String str);
-    private static native String toPatternImpl(int addr, boolean localized);
+    private static native void setSymbol(long addr, int symbol, String str);
+    private static native void setAttribute(long addr, int symbol, int i);
+    private static native void setRoundingMode(long addr, int roundingMode, double roundingIncrement);
+    private static native void setTextAttribute(long addr, int symbol, String str);
+    private static native String toPatternImpl(long addr, boolean localized);
 }
diff --git a/luni/src/main/java/libcore/icu/TimeZones.java b/luni/src/main/java/libcore/icu/TimeZoneNames.java
similarity index 74%
rename from luni/src/main/java/libcore/icu/TimeZones.java
rename to luni/src/main/java/libcore/icu/TimeZoneNames.java
index 3e27985..65ada89 100644
--- a/luni/src/main/java/libcore/icu/TimeZones.java
+++ b/luni/src/main/java/libcore/icu/TimeZoneNames.java
@@ -16,18 +16,20 @@
 
 package libcore.icu;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.TimeZone;
 import libcore.util.BasicLruCache;
+import libcore.util.ZoneInfoDB;
 
 /**
- * Provides access to ICU's time zone data.
+ * Provides access to ICU's time zone name data.
  */
-public final class TimeZones {
-    private static final String[] availableTimeZones = TimeZone.getAvailableIDs();
+public final class TimeZoneNames {
+    private static final String[] availableTimeZoneIds = TimeZone.getAvailableIDs();
 
     /*
      * Offsets into the arrays returned by DateFormatSymbols.getZoneStrings.
@@ -59,22 +61,30 @@
             // isn't particularly large (and we remove duplicates), but is currently (Honeycomb)
             // really expensive to compute.
             // If you change this, you might want to change the scope of the intern table too.
-            super(availableTimeZones.length);
+            super(availableTimeZoneIds.length);
         }
 
         @Override protected String[][] create(Locale locale) {
-            long start, nativeStart;
-            start = nativeStart = System.currentTimeMillis();
-            String[][] result = getZoneStringsImpl(locale.toString(), availableTimeZones);
+            long start = System.currentTimeMillis();
+
+            // Set up the 2D array used to hold the names. The first column contains the Olson ids.
+            String[][] result = new String[availableTimeZoneIds.length][5];
+            for (int i = 0; i < availableTimeZoneIds.length; ++i) {
+                result[i][0] = availableTimeZoneIds[i];
+            }
+
+            long nativeStart = System.currentTimeMillis();
+            fillZoneStrings(locale.toString(), result);
             long nativeEnd = System.currentTimeMillis();
+
             internStrings(result);
             // Ending up in this method too often is an easy way to make your app slow, so we ensure
             // it's easy to tell from the log (a) what we were doing, (b) how long it took, and
             // (c) that it's all ICU's fault.
             long end = System.currentTimeMillis();
-            long duration = end - start;
             long nativeDuration = nativeEnd - nativeStart;
-            System.logI("Loaded time zone names for " + locale + " in " + duration + "ms" +
+            long duration = end - start;
+            System.logI("Loaded time zone names for \"" + locale + "\" in " + duration + "ms" +
                     " (" + nativeDuration + "ms in ICU)");
             return result;
         }
@@ -100,7 +110,7 @@
         }
     };
 
-    private TimeZones() {}
+    private TimeZoneNames() {}
 
     /**
      * Returns the appropriate string from 'zoneStrings'. Used with getZoneStrings.
@@ -132,12 +142,22 @@
     /**
      * Returns an array containing the time zone ids in use in the country corresponding to
      * the given locale. This is not necessary for Java API, but is used by telephony as a
-     * fallback.
+     * fallback. We retrieve these strings from zone.tab rather than icu4c because the latter
+     * supplies them in alphabetical order where zone.tab has them in a kind of "importance"
+     * order (as defined in the zone.tab header).
      */
     public static String[] forLocale(Locale locale) {
-        return forCountryCode(locale.getCountry());
+        String countryCode = locale.getCountry();
+        ArrayList<String> ids = new ArrayList<String>();
+        for (String line : ZoneInfoDB.getZoneTab().split("\n")) {
+            if (line.startsWith(countryCode)) {
+                int olsonIdStart = line.indexOf('\t', 4) + 1;
+                int olsonIdEnd = line.indexOf('\t', olsonIdStart);
+                ids.add(line.substring(olsonIdStart, olsonIdEnd));
+            }
+        }
+        return ids.toArray(new String[ids.size()]);
     }
 
-    private static native String[] forCountryCode(String countryCode);
-    private static native String[][] getZoneStringsImpl(String locale, String[] timeZoneIds);
+    private static native void fillZoneStrings(String locale, String[][] result);
 }
diff --git a/luni/src/main/java/libcore/icu/Transliterator.java b/luni/src/main/java/libcore/icu/Transliterator.java
new file mode 100644
index 0000000..77b5ba7
--- /dev/null
+++ b/luni/src/main/java/libcore/icu/Transliterator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 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.icu;
+
+/**
+ * Exposes icu4c's Transliterator.
+ */
+public final class Transliterator {
+  private long peer;
+
+  /**
+   * Creates a new Transliterator for the given id.
+   */
+  public Transliterator(String id) {
+    peer = create(id);
+  }
+
+  @Override protected synchronized void finalize() throws Throwable {
+    try {
+      destroy(peer);
+      peer = 0;
+    } finally {
+      super.finalize();
+    }
+  }
+
+  /**
+   * Returns the ids of all known transliterators.
+   */
+  public static native String[] getAvailableIDs();
+
+  /**
+   * Transliterates the specified string.
+   */
+  public String transliterate(String s) {
+    return transliterate(peer, s);
+  }
+
+  private static native long create(String id);
+  private static native void destroy(long peer);
+  private static native String transliterate(long peer, String s);
+}
diff --git a/luni/src/main/java/libcore/io/DiskLruCache.java b/luni/src/main/java/libcore/io/DiskLruCache.java
index 8338983..b9cc0a1 100644
--- a/luni/src/main/java/libcore/io/DiskLruCache.java
+++ b/luni/src/main/java/libcore/io/DiskLruCache.java
@@ -243,28 +243,38 @@
                         + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
             }
 
+            int lineCount = 0;
             while (true) {
                 try {
                     readJournalLine(reader.readLine());
+                    lineCount++;
                 } catch (EOFException endOfJournal) {
                     break;
                 }
             }
+            redundantOpCount = lineCount - lruEntries.size();
         } finally {
             IoUtils.closeQuietly(reader);
         }
     }
 
     private void readJournalLine(String line) throws IOException {
-        String[] parts = line.split(" ");
-        if (parts.length < 2) {
+        int firstSpace = line.indexOf(' ');
+        if (firstSpace == -1) {
             throw new IOException("unexpected journal line: " + line);
         }
 
-        String key = parts[1];
-        if (parts[0].equals(REMOVE) && parts.length == 2) {
-            lruEntries.remove(key);
-            return;
+        int keyBegin = firstSpace + 1;
+        int secondSpace = line.indexOf(' ', keyBegin);
+        final String key;
+        if (secondSpace == -1) {
+            key = line.substring(keyBegin);
+            if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+                lruEntries.remove(key);
+                return;
+            }
+        } else {
+            key = line.substring(keyBegin, secondSpace);
         }
 
         Entry entry = lruEntries.get(key);
@@ -273,13 +283,14 @@
             lruEntries.put(key, entry);
         }
 
-        if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
+        if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+            String[] parts = line.substring(secondSpace + 1).split(" ");
             entry.readable = true;
             entry.currentEditor = null;
-            entry.setLengths(Arrays.copyOfRange(parts, 2, parts.length));
-        } else if (parts[0].equals(DIRTY) && parts.length == 2) {
+            entry.setLengths(parts);
+        } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
             entry.currentEditor = new Editor(entry);
-        } else if (parts[0].equals(READ) && parts.length == 2) {
+        } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
             // this work was already done by calling lruEntries.get()
         } else {
             throw new IOException("unexpected journal line: " + line);
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index ee26d04..2de13ae 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -45,6 +45,8 @@
     public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return os.dup(oldFd); }
     public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return os.dup2(oldFd, newFd); }
     public String[] environ() { return os.environ(); }
+    public void execv(String filename, String[] argv) throws ErrnoException { os.execv(filename, argv); }
+    public void execve(String filename, String[] argv, String[] envp) throws ErrnoException { os.execve(filename, argv, envp); }
     public void fchmod(FileDescriptor fd, int mode) throws ErrnoException { os.fchmod(fd, mode); }
     public void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { os.fchown(fd, uid, gid); }
     public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return os.fcntlVoid(fd, cmd); }
@@ -62,6 +64,7 @@
     public int getgid() { return os.getgid(); }
     public String getenv(String name) { return os.getenv(name); }
     public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); }
+    public SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return os.getpeername(fd); }
     public int getpid() { return os.getpid(); }
     public int getppid() { return os.getppid(); }
     public StructPasswd getpwnam(String name) throws ErrnoException { return os.getpwnam(name); }
@@ -72,6 +75,7 @@
     public int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptInt(fd, level, option); }
     public StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptLinger(fd, level, option); }
     public StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptTimeval(fd, level, option); }
+    public StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptUcred(fd, level, option); }
     public int getuid() { return os.getuid(); }
     public String if_indextoname(int index) { return os.if_indextoname(index); }
     public InetAddress inet_pton(int family, String address) { return os.inet_pton(family, address); }
@@ -108,6 +112,7 @@
     public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return os.sendto(fd, buffer, flags, inetAddress, port); }
     public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); }
     public void setegid(int egid) throws ErrnoException { os.setegid(egid); }
+    public void setenv(String name, String value, boolean overwrite) throws ErrnoException { os.setenv(name, value, overwrite); }
     public void seteuid(int euid) throws ErrnoException { os.seteuid(euid); }
     public void setgid(int gid) throws ErrnoException { os.setgid(gid); }
     public int setsid() throws ErrnoException { return os.setsid(); }
@@ -125,11 +130,14 @@
     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); }
+    public String strsignal(int signal) { return os.strsignal(signal); }
     public void symlink(String oldPath, String newPath) throws ErrnoException { os.symlink(oldPath, newPath); }
     public long sysconf(int name) { return os.sysconf(name); }
     public void tcdrain(FileDescriptor fd) throws ErrnoException { os.tcdrain(fd); }
+    public void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { os.tcsendbreak(fd, duration); }
     public int umask(int mask) { return os.umask(mask); }
     public StructUtsname uname() { return os.uname(); }
+    public void unsetenv(String name) throws ErrnoException { os.unsetenv(name); }
     public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); }
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); }
     public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); }
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index 2d60a86..089d0f8 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -216,18 +216,11 @@
                 cause = errnoException;
             }
         }
-        // TODO: is it really helpful/necessary to throw so many different exceptions?
         String detail = connectDetail(inetAddress, port, timeoutMs, cause);
-        if (cause.errno == ECONNRESET || cause.errno == ECONNREFUSED ||
-                cause.errno == EADDRNOTAVAIL || cause.errno == EADDRINUSE ||
-                cause.errno == ENETUNREACH) {
-            throw new ConnectException(detail, cause);
-        } else if (cause.errno == EACCES) {
-            throw new SecurityException(detail, cause);
-        } else if (cause.errno == ETIMEDOUT) {
+        if (cause.errno == ETIMEDOUT) {
             throw new SocketTimeoutException(detail, cause);
         }
-        throw new SocketException(detail, cause);
+        throw new ConnectException(detail, cause);
     }
 
     // Socket options used by java.net but not exposed in SocketOptions.
@@ -280,7 +273,7 @@
         case SocketOptions.SO_OOBINLINE:
             return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE));
         case SocketOptions.SO_RCVBUF:
-            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
+            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_RCVBUF);
         case SocketOptions.SO_REUSEADDR:
             return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR));
         case SocketOptions.SO_SNDBUF:
@@ -497,7 +490,7 @@
                 return 0;
             }
         } else {
-            if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+            if (errnoException.errno == EAGAIN) {
                 // We were asked to write to a non-blocking socket, but were told
                 // it would block, so report "no bytes written".
                 return 0;
@@ -546,7 +539,7 @@
 
     private static int maybeThrowAfterRecvfrom(boolean isRead, boolean isConnected, ErrnoException errnoException) throws SocketException, SocketTimeoutException {
         if (isRead) {
-            if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+            if (errnoException.errno == EAGAIN) {
                 return 0;
             } else {
                 throw errnoException.rethrowAsSocketException();
@@ -554,7 +547,7 @@
         } else {
             if (isConnected && errnoException.errno == ECONNREFUSED) {
                 throw new PortUnreachableException("", errnoException);
-            } else if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) {
+            } else if (errnoException.errno == EAGAIN) {
                 throw new SocketTimeoutException(errnoException);
             } else {
                 throw errnoException.rethrowAsSocketException();
diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java
index 243d8d8..0373ea9 100644
--- a/luni/src/main/java/libcore/io/IoUtils.java
+++ b/luni/src/main/java/libcore/io/IoUtils.java
@@ -19,6 +19,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.io.RandomAccessFile;
 import java.net.Socket;
 import java.nio.charset.Charsets;
@@ -145,4 +146,29 @@
             }
         }
     }
+
+    /**
+     * Checks whether {@code path} can be opened read-only. Similar to File.exists, but doesn't
+     * require read permission on the parent, so it'll work in more cases, and allow you to
+     * remove read permission from more directories.
+     */
+    public static boolean canOpenReadOnly(String path) {
+        try {
+            // Use open(2) rather than stat(2) so we require fewer permissions. http://b/6485312.
+            FileDescriptor fd = Libcore.os.open(path, O_RDONLY, 0);
+            Libcore.os.close(fd);
+            return true;
+        } catch (ErrnoException errnoException) {
+            return false;
+        }
+    }
+
+    public static void throwInterruptedIoException() throws InterruptedIOException {
+        // This is typically thrown in response to an
+        // InterruptedException which does not leave the thread in an
+        // interrupted state, so explicitly interrupt here.
+        Thread.currentThread().interrupt();
+        // TODO: set InterruptedIOException.bytesTransferred
+        throw new InterruptedIOException();
+    }
 }
diff --git a/luni/src/main/java/libcore/io/Memory.java b/luni/src/main/java/libcore/io/Memory.java
index 9bf1acb..5743949 100644
--- a/luni/src/main/java/libcore/io/Memory.java
+++ b/luni/src/main/java/libcore/io/Memory.java
@@ -150,29 +150,29 @@
      */
     public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount);
 
-    public static native byte peekByte(int address);
-    public static native int peekInt(int address, boolean swap);
-    public static native long peekLong(int address, boolean swap);
-    public static native short peekShort(int address, boolean swap);
+    public static native byte peekByte(long address);
+    public static native int peekInt(long address, boolean swap);
+    public static native long peekLong(long address, boolean swap);
+    public static native short peekShort(long address, boolean swap);
 
-    public static native void peekByteArray(int address, byte[] dst, int dstOffset, int byteCount);
-    public static native void peekCharArray(int address, char[] dst, int dstOffset, int charCount, boolean swap);
-    public static native void peekDoubleArray(int address, double[] dst, int dstOffset, int doubleCount, boolean swap);
-    public static native void peekFloatArray(int address, float[] dst, int dstOffset, int floatCount, boolean swap);
-    public static native void peekIntArray(int address, int[] dst, int dstOffset, int intCount, boolean swap);
-    public static native void peekLongArray(int address, long[] dst, int dstOffset, int longCount, boolean swap);
-    public static native void peekShortArray(int address, short[] dst, int dstOffset, int shortCount, boolean swap);
+    public static native void peekByteArray(long address, byte[] dst, int dstOffset, int byteCount);
+    public static native void peekCharArray(long address, char[] dst, int dstOffset, int charCount, boolean swap);
+    public static native void peekDoubleArray(long address, double[] dst, int dstOffset, int doubleCount, boolean swap);
+    public static native void peekFloatArray(long address, float[] dst, int dstOffset, int floatCount, boolean swap);
+    public static native void peekIntArray(long address, int[] dst, int dstOffset, int intCount, boolean swap);
+    public static native void peekLongArray(long address, long[] dst, int dstOffset, int longCount, boolean swap);
+    public static native void peekShortArray(long address, short[] dst, int dstOffset, int shortCount, boolean swap);
 
-    public static native void pokeByte(int address, byte value);
-    public static native void pokeInt(int address, int value, boolean swap);
-    public static native void pokeLong(int address, long value, boolean swap);
-    public static native void pokeShort(int address, short value, boolean swap);
+    public static native void pokeByte(long address, byte value);
+    public static native void pokeInt(long address, int value, boolean swap);
+    public static native void pokeLong(long address, long value, boolean swap);
+    public static native void pokeShort(long address, short value, boolean swap);
 
-    public static native void pokeByteArray(int address, byte[] src, int offset, int count);
-    public static native void pokeCharArray(int address, char[] src, int offset, int count, boolean swap);
-    public static native void pokeDoubleArray(int address, double[] src, int offset, int count, boolean swap);
-    public static native void pokeFloatArray(int address, float[] src, int offset, int count, boolean swap);
-    public static native void pokeIntArray(int address, int[] src, int offset, int count, boolean swap);
-    public static native void pokeLongArray(int address, long[] src, int offset, int count, boolean swap);
-    public static native void pokeShortArray(int address, short[] src, int offset, int count, boolean swap);
+    public static native void pokeByteArray(long address, byte[] src, int offset, int count);
+    public static native void pokeCharArray(long address, char[] src, int offset, int count, boolean swap);
+    public static native void pokeDoubleArray(long address, double[] src, int offset, int count, boolean swap);
+    public static native void pokeFloatArray(long address, float[] src, int offset, int count, boolean swap);
+    public static native void pokeIntArray(long address, int[] src, int offset, int count, boolean swap);
+    public static native void pokeLongArray(long address, long[] src, int offset, int count, boolean swap);
+    public static native void pokeShortArray(long address, short[] src, int offset, int count, boolean swap);
 }
diff --git a/luni/src/main/java/libcore/io/MemoryMappedFile.java b/luni/src/main/java/libcore/io/MemoryMappedFile.java
index 1c106de..2d8aa2b 100644
--- a/luni/src/main/java/libcore/io/MemoryMappedFile.java
+++ b/luni/src/main/java/libcore/io/MemoryMappedFile.java
@@ -74,14 +74,14 @@
      * Returns a new iterator that treats the mapped data as big-endian.
      */
     public BufferIterator bigEndianIterator() {
-        return new NioBufferIterator((int) address, (int) size, ByteOrder.nativeOrder() != ByteOrder.BIG_ENDIAN);
+        return new NioBufferIterator(address, (int) size, ByteOrder.nativeOrder() != ByteOrder.BIG_ENDIAN);
     }
 
     /**
      * Returns a new iterator that treats the mapped data as little-endian.
      */
     public BufferIterator littleEndianIterator() {
-        return new NioBufferIterator((int) address, (int) size, ByteOrder.nativeOrder() != ByteOrder.LITTLE_ENDIAN);
+        return new NioBufferIterator(address, (int) size, ByteOrder.nativeOrder() != ByteOrder.LITTLE_ENDIAN);
     }
 
     /**
diff --git a/luni/src/main/java/libcore/io/NioBufferIterator.java b/luni/src/main/java/libcore/io/NioBufferIterator.java
index ae29e59..3dd05a5 100644
--- a/luni/src/main/java/libcore/io/NioBufferIterator.java
+++ b/luni/src/main/java/libcore/io/NioBufferIterator.java
@@ -25,13 +25,13 @@
  * @hide don't make this public without adding bounds checking.
  */
 public final class NioBufferIterator extends BufferIterator {
-    private final int address;
+    private final long address;
     private final int size;
     private final boolean swap;
 
     private int position;
 
-    NioBufferIterator(int address, int size, boolean swap) {
+    NioBufferIterator(long address, int size, boolean swap) {
         this.address = address;
         this.size = size;
         this.swap = swap;
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 1f42497..f3d2383 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -36,6 +36,8 @@
     public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException;
     public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException;
     public String[] environ();
+    public void execv(String filename, String[] argv) throws ErrnoException;
+    public void execve(String filename, String[] argv, String[] envp) throws ErrnoException;
     public void fchmod(FileDescriptor fd, int mode) throws ErrnoException;
     public void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException;
     public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException;
@@ -54,6 +56,7 @@
     public String getenv(String name);
     /* TODO: break into getnameinfoHost and getnameinfoService? */
     public String getnameinfo(InetAddress address, int flags) throws GaiException;
+    public SocketAddress getpeername(FileDescriptor fd) throws ErrnoException;
     public int getpid();
     public int getppid();
     public StructPasswd getpwnam(String name) throws ErrnoException;
@@ -64,6 +67,7 @@
     public int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException;
     public StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException;
     public StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException;
+    public StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException;
     public int getuid();
     public String if_indextoname(int index);
     public InetAddress inet_pton(int family, String address);
@@ -101,6 +105,7 @@
     public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException;
     public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException;
     public void setegid(int egid) throws ErrnoException;
+    public void setenv(String name, String value, boolean overwrite) throws ErrnoException;
     public void seteuid(int euid) throws ErrnoException;
     public void setgid(int gid) throws ErrnoException;
     public int setsid() throws ErrnoException;
@@ -119,11 +124,14 @@
     /* TODO: replace statfs with statvfs. */
     public StructStatFs statfs(String path) throws ErrnoException;
     public String strerror(int errno);
+    public String strsignal(int signal);
     public void symlink(String oldPath, String newPath) throws ErrnoException;
     public long sysconf(int name);
     public void tcdrain(FileDescriptor fd) throws ErrnoException;
+    public void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException;
     public int umask(int mask);
     public StructUtsname uname();
+    public void unsetenv(String name) throws ErrnoException;
     public int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException;
     public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/libcore/io/OsConstants.java
index 9f381f5..bb5c766 100644
--- a/luni/src/main/java/libcore/io/OsConstants.java
+++ b/luni/src/main/java/libcore/io/OsConstants.java
@@ -134,7 +134,7 @@
     public static final int ETIME = placeholder();
     public static final int ETIMEDOUT = placeholder();
     public static final int ETXTBSY = placeholder();
-    public static final int EWOULDBLOCK = placeholder();
+    // On Linux, EWOULDBLOCK == EAGAIN. Use EAGAIN instead, to reduce confusion.
     public static final int EXDEV = placeholder();
     public static final int EXIT_FAILURE = placeholder();
     public static final int EXIT_SUCCESS = placeholder();
@@ -302,6 +302,8 @@
     public static final int SO_KEEPALIVE = placeholder();
     public static final int SO_LINGER = placeholder();
     public static final int SO_OOBINLINE = placeholder();
+    public static final int SO_PASSCRED = placeholder();
+    public static final int SO_PEERCRED = placeholder();
     public static final int SO_RCVBUF = placeholder();
     public static final int SO_RCVLOWAT = placeholder();
     public static final int SO_RCVTIMEO = placeholder();
@@ -705,9 +707,6 @@
         if (errno == ETXTBSY) {
             return "ETXTBSY";
         }
-        if (errno == EWOULDBLOCK) {
-            return "EWOULDBLOCK";
-        }
         if (errno == EXDEV) {
             return "EXDEV";
         }
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index 2fb187e..e9d1da3 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -39,6 +39,8 @@
     public native FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException;
     public native FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException;
     public native String[] environ();
+    public native void execv(String filename, String[] argv) throws ErrnoException;
+    public native void execve(String filename, String[] argv, String[] envp) throws ErrnoException;
     public native void fchmod(FileDescriptor fd, int mode) throws ErrnoException;
     public native void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException;
     public native int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException;
@@ -56,6 +58,7 @@
     public native int getgid();
     public native String getenv(String name);
     public native String getnameinfo(InetAddress address, int flags) throws GaiException;
+    public native SocketAddress getpeername(FileDescriptor fd) throws ErrnoException;
     public native int getpid();
     public native int getppid();
     public native StructPasswd getpwnam(String name) throws ErrnoException;
@@ -66,6 +69,7 @@
     public native int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException;
+    public native StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException;
     public native int getuid();
     public native String if_indextoname(int index);
     public native InetAddress inet_pton(int family, String address);
@@ -152,6 +156,7 @@
     }
     private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException;
     public native void setegid(int egid) throws ErrnoException;
+    public native void setenv(String name, String value, boolean overwrite) throws ErrnoException;
     public native void seteuid(int euid) throws ErrnoException;
     public native void setgid(int gid) throws ErrnoException;
     public native int setsid() throws ErrnoException;
@@ -169,11 +174,20 @@
     public native StructStat stat(String path) throws ErrnoException;
     public native StructStatFs statfs(String path) throws ErrnoException;
     public native String strerror(int errno);
+    public native String strsignal(int signal);
     public native void symlink(String oldPath, String newPath) throws ErrnoException;
     public native long sysconf(int name);
     public native void tcdrain(FileDescriptor fd) throws ErrnoException;
-    public native int umask(int mask);
+    public native void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException;
+    public int umask(int mask) {
+        if ((mask & 0777) != mask) {
+            throw new IllegalArgumentException("Invalid umask: " + mask);
+        }
+        return umaskImpl(mask);
+    }
+    private native int umaskImpl(int mask);
     public native StructUtsname uname();
+    public native void unsetenv(String name) throws ErrnoException;
     public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException;
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
         if (buffer.isDirect()) {
diff --git a/luni/src/main/java/libcore/io/StructUcred.java b/luni/src/main/java/libcore/io/StructUcred.java
new file mode 100644
index 0000000..359995d
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructUcred.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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;
+
+/**
+ * Corresponds to C's {@code struct ucred}.
+ */
+public final class StructUcred {
+  /** The peer's process id. */
+  public final int pid;
+
+  /** The peer process' uid. */
+  public final int uid;
+
+  /** The peer process' gid. */
+  public final int gid;
+
+  private StructUcred(int pid, int uid, int gid) {
+    this.pid = pid;
+    this.uid = uid;
+    this.gid = gid;
+  }
+
+  @Override public String toString() {
+    return "StructUcred[pid=" + pid + ",uid=" + uid + ",gid=" + gid + "]";
+  }
+}
diff --git a/luni/src/main/java/libcore/net/MimeUtils.java b/luni/src/main/java/libcore/net/MimeUtils.java
index 6ea0baf..6e8c168 100644
--- a/luni/src/main/java/libcore/net/MimeUtils.java
+++ b/luni/src/main/java/libcore/net/MimeUtils.java
@@ -43,6 +43,10 @@
         //
         // and "active" MIME types (due to potential security issues).
 
+        // Note that this list is _not_ in alphabetical order and must not be sorted.
+        // The "most popular" extension must come first, so that it's the one returned
+        // by guessExtensionFromMimeType.
+
         add("application/andrew-inset", "ez");
         add("application/dsptype", "tsp");
         add("application/futuresplash", "spl");
@@ -139,8 +143,8 @@
         add("application/x-gnumeric", "gnumeric");
         add("application/x-go-sgf", "sgf");
         add("application/x-graphing-calculator", "gcf");
-        add("application/x-gtar", "gtar");
         add("application/x-gtar", "tgz");
+        add("application/x-gtar", "gtar");
         add("application/x-gtar", "taz");
         add("application/x-hdf", "hdf");
         add("application/x-ica", "ica");
@@ -212,10 +216,11 @@
         add("audio/midi", "kar");
         add("audio/midi", "xmf");
         add("audio/mobile-xmf", "mxmf");
+        // add ".mp3" first so it will be the default for guessExtensionFromMimeType
+        add("audio/mpeg", "mp3");
         add("audio/mpeg", "mpga");
         add("audio/mpeg", "mpega");
         add("audio/mpeg", "mp2");
-        add("audio/mpeg", "mp3");
         add("audio/mpeg", "m4a");
         add("audio/mpegurl", "m3u");
         add("audio/prs.sid", "sid");
@@ -282,7 +287,7 @@
         add("text/h323", "323");
         add("text/iuls", "uls");
         add("text/mathml", "mml");
-        // add ".txt" first so it will be the default for ExtensionFromMimeType
+        // add ".txt" first so it will be the default for guessExtensionFromMimeType
         add("text/plain", "txt");
         add("text/plain", "asc");
         add("text/plain", "text");
@@ -296,12 +301,13 @@
         add("text/xml", "xml");
         add("text/x-bibtex", "bib");
         add("text/x-boo", "boo");
-        add("text/x-c++hdr", "h++");
         add("text/x-c++hdr", "hpp");
+        add("text/x-c++hdr", "h++");
         add("text/x-c++hdr", "hxx");
         add("text/x-c++hdr", "hh");
-        add("text/x-c++src", "c++");
         add("text/x-c++src", "cpp");
+        add("text/x-c++src", "c++");
+        add("text/x-c++src", "cc");
         add("text/x-c++src", "cxx");
         add("text/x-chdr", "h");
         add("text/x-component", "htc");
diff --git a/luni/src/main/java/libcore/net/UriCodec.java b/luni/src/main/java/libcore/net/UriCodec.java
index 6624474..3db95d0 100644
--- a/luni/src/main/java/libcore/net/UriCodec.java
+++ b/luni/src/main/java/libcore/net/UriCodec.java
@@ -111,7 +111,7 @@
                 }
                 if (c == '%' && isPartiallyEncoded) {
                     // this is an encoded 3-character sequence like "%20"
-                    builder.append(s, i, i + 3);
+                    builder.append(s, i, Math.min(i + 3, s.length()));
                     i += 2;
                 } else if (c == ' ') {
                     builder.append('+');
diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java
index 42c9b96..8d81c38 100644
--- a/luni/src/main/java/libcore/net/http/HttpEngine.java
+++ b/luni/src/main/java/libcore/net/http/HttpEngine.java
@@ -473,6 +473,12 @@
         }
     }
 
+    public final void markConnectionAsRecycled() {
+        if (connection != null) {
+            connection.setRecycled();
+        }
+    }
+
     /**
      * Releases this engine so that its resources may be either reused or
      * closed.
diff --git a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
index 260a9ad..3e6503f 100644
--- a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
@@ -329,6 +329,8 @@
 
             if (retry == Retry.DIFFERENT_CONNECTION) {
                 httpEngine.automaticallyReleaseConnectionToPool();
+            } else {
+                httpEngine.markConnectionAsRecycled();
             }
 
             httpEngine.release(true);
@@ -406,7 +408,7 @@
     final boolean processAuthHeader(int responseCode, ResponseHeaders response,
             RawHeaders successorRequestHeaders) throws IOException {
         if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("Bad response code: " + responseCode);
         }
 
         // keep asking for username/password until authorized
diff --git a/luni/src/main/java/libcore/util/ZoneInfo.java b/luni/src/main/java/libcore/util/ZoneInfo.java
index ac48b23..54ee667 100644
--- a/luni/src/main/java/libcore/util/ZoneInfo.java
+++ b/luni/src/main/java/libcore/util/ZoneInfo.java
@@ -16,11 +16,10 @@
 
 package libcore.util;
 
-import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
-import java.util.Formatter;
 import java.util.TimeZone;
+import libcore.io.BufferIterator;
 
 /**
  * Our concrete TimeZone implementation, backed by zoneinfo data.
@@ -43,17 +42,57 @@
     };
 
     private int mRawOffset;
-
     private final int mEarliestRawOffset;
+    private final boolean mUseDst;
+    private final int mDstSavings; // Implements TimeZone.getDSTSavings.
 
     private final int[] mTransitions;
     private final int[] mOffsets;
     private final byte[] mTypes;
     private final byte[] mIsDsts;
-    private final boolean mUseDst;
-    private final int mDstSavings; // Implements TimeZone.getDSTSavings.
 
-    ZoneInfo(String name, int[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts) {
+    public static TimeZone makeTimeZone(String id, BufferIterator it) {
+        // Variable names beginning tzh_ correspond to those in "tzfile.h".
+
+        // Check tzh_magic.
+        if (it.readInt() != 0x545a6966) { // "TZif"
+            return null;
+        }
+
+        // Skip the uninteresting part of the header.
+        it.skip(28);
+
+        // Read the sizes of the arrays we're about to read.
+        int tzh_timecnt = it.readInt();
+        int tzh_typecnt = it.readInt();
+
+        it.skip(4); // Skip tzh_charcnt.
+
+        int[] transitions = new int[tzh_timecnt];
+        it.readIntArray(transitions, 0, transitions.length);
+
+        byte[] type = new byte[tzh_timecnt];
+        it.readByteArray(type, 0, type.length);
+
+        int[] gmtOffsets = new int[tzh_typecnt];
+        byte[] isDsts = new byte[tzh_typecnt];
+        for (int i = 0; i < tzh_typecnt; ++i) {
+            gmtOffsets[i] = it.readInt();
+            isDsts[i] = it.readByte();
+            // We skip the abbreviation index. This would let us provide historically-accurate
+            // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in
+            // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current
+            // names, though, so even if we did use this data to provide the correct abbreviations
+            // for en_US, we wouldn't be able to provide correct abbreviations for other locales,
+            // nor would we be able to provide correct long forms (such as "Yukon Standard Time")
+            // for any locale. (The RI doesn't do any better than us here either.)
+            it.skip(1);
+        }
+
+        return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts);
+    }
+
+    private ZoneInfo(String name, int[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts) {
         mTransitions = transitions;
         mTypes = types;
         mIsDsts = isDsts;
@@ -254,29 +293,12 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder();
-        // First the basics...
-        sb.append(getClass().getName() + "[" + getID() + ",mRawOffset=" + mRawOffset +
-                ",mUseDst=" + mUseDst + ",mDstSavings=" + mDstSavings + "]");
-        // ...followed by a zdump(1)-like description of all our transition data.
-        sb.append("\n");
-        Formatter f = new Formatter(sb);
-        for (int i = 0; i < mTransitions.length; ++i) {
-            int type = mTypes[i] & 0xff;
-            String utcTime = formatTime(mTransitions[i], TimeZone.getTimeZone("UTC"));
-            String localTime = formatTime(mTransitions[i], this);
-            int offset = mOffsets[type];
-            int gmtOffset = mRawOffset/1000 + offset;
-            f.format("%4d : time=%11d %s = %s isDst=%d offset=%5d gmtOffset=%d\n",
-                    i, mTransitions[i], utcTime, localTime, mIsDsts[type], offset, gmtOffset);
-        }
-        return sb.toString();
-    }
-
-    private static String formatTime(int s, TimeZone tz) {
-        SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy zzz");
-        sdf.setTimeZone(tz);
-        long ms = ((long) s) * 1000L;
-        return sdf.format(new Date(ms));
+        return getClass().getName() + "[id=\"" + getID() + "\"" +
+            ",mRawOffset=" + mRawOffset +
+            ",mEarliestRawOffset=" + mEarliestRawOffset +
+            ",mUseDst=" + mUseDst +
+            ",mDstSavings=" + mDstSavings +
+            ",transitions=" + mTransitions.length +
+            "]";
     }
 }
diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java
index cc7cc4f..b211c93 100644
--- a/luni/src/main/java/libcore/util/ZoneInfoDB.java
+++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java
@@ -35,36 +35,16 @@
 import org.apache.harmony.luni.internal.util.TimezoneGetter;
 
 /**
- * A class used to initialize the time zone database.  This implementation uses the
- * 'zoneinfo' database as the source of time zone information.  However, to conserve
- * disk space the data for all time zones are concatenated into a single file, and a
- * second file is used to indicate the starting position of each time zone record.  A
- * third file indicates the version of the zoneinfo database used to generate the data.
+ * A class used to initialize the time zone database. This implementation uses the
+ * Olson tzdata as the source of time zone information. However, to conserve
+ * disk space (inodes) and reduce I/O, all the data is concatenated into a single file,
+ * with an index to indicate the starting position of each time zone record.
  *
  * @hide - used to implement TimeZone
  */
 public final class ZoneInfoDB {
-    /**
-     * The directory containing the time zone database files.
-     */
-    private static final String ZONE_DIRECTORY_NAME =
-            System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/";
-
-    /**
-     * The name of the file containing the concatenated time zone records.
-     */
-    private static final String ZONE_FILE_NAME = ZONE_DIRECTORY_NAME + "zoneinfo.dat";
-
-    /**
-     * The name of the file containing the index to each time zone record within
-     * the zoneinfo.dat file.
-     */
-    private static final String INDEX_FILE_NAME = ZONE_DIRECTORY_NAME + "zoneinfo.idx";
-
     private static final Object LOCK = new Object();
 
-    private static final String VERSION = readVersion();
-
     /**
      * Rather than open, read, and close the big data file each time we look up a time zone,
      * we map the big data file during startup, and then just use the MemoryMappedFile.
@@ -72,68 +52,78 @@
      * At the moment, this "big" data file is about 500 KiB. At some point, that will be small
      * enough that we'll just keep the byte[] in memory.
      */
-    private static final MemoryMappedFile ALL_ZONE_DATA = mapData();
+    private static final MemoryMappedFile TZDATA = mapData();
+
+    private static String version;
+    private static String zoneTab;
 
     /**
      * The 'ids' array contains time zone ids sorted alphabetically, for binary searching.
      * The other two arrays are in the same order. 'byteOffsets' gives the byte offset
-     * into "zoneinfo.dat" of each time zone, and 'rawUtcOffsets' gives the time zone's
-     * raw UTC offset.
+     * of each time zone, and 'rawUtcOffsets' gives the time zone's raw UTC offset.
      */
     private static String[] ids;
     private static int[] byteOffsets;
     private static int[] rawUtcOffsets;
+
     static {
-        readIndex();
+        readHeader();
     }
 
     private ZoneInfoDB() {
     }
 
-    /**
-     * Reads the file indicating the database version in use.
-     */
-    private static String readVersion() {
-        try {
-            byte[] bytes = IoUtils.readFileAsByteArray(ZONE_DIRECTORY_NAME + "zoneinfo.version");
-            return new String(bytes, 0, bytes.length, Charsets.ISO_8859_1).trim();
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
+    private static void readHeader() {
+        // byte[12] tzdata_version  -- "tzdata2012f\0"
+        // int index_offset
+        // int data_offset
+        // int zonetab_offset
+        BufferIterator it = TZDATA.bigEndianIterator();
+
+        byte[] tzdata_version = new byte[12];
+        it.readByteArray(tzdata_version, 0, tzdata_version.length);
+        String magic = new String(tzdata_version, 0, 6, Charsets.US_ASCII);
+        if (!magic.equals("tzdata") || tzdata_version[11] != 0) {
+            throw new RuntimeException("bad tzdata magic: " + Arrays.toString(tzdata_version));
         }
+        version = new String(tzdata_version, 6, 5, Charsets.US_ASCII);
+
+        int index_offset = it.readInt();
+        int data_offset = it.readInt();
+        int zonetab_offset = it.readInt();
+
+        readIndex(it, index_offset, data_offset);
+        readZoneTab(it, zonetab_offset);
     }
 
     private static MemoryMappedFile mapData() {
+        MemoryMappedFile result = mapData(System.getenv("ANDROID_DATA") + "/misc/zoneinfo/");
+        if (result == null) {
+            result = mapData(System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/");
+            if (result == null) {
+                throw new AssertionError("Couldn't find any tzdata!");
+            }
+        }
+        return result;
+    }
+
+    private static MemoryMappedFile mapData(String directory) {
         try {
-            return MemoryMappedFile.mmapRO(ZONE_FILE_NAME);
+            return MemoryMappedFile.mmapRO(directory + "tzdata");
         } catch (ErrnoException errnoException) {
-            throw new AssertionError(errnoException);
+            return null;
         }
     }
 
-    /**
-     * Traditionally, Unix systems have one file per time zone. We have one big data file, which
-     * is just a concatenation of regular time zone files. To allow random access into this big
-     * data file, we also have an index. We read the index at startup, and keep it in memory so
-     * we can binary search by id when we need time zone data.
-     *
-     * The format of this file is, I believe, Android's own, and undocumented.
-     *
-     * All this code assumes strings are US-ASCII.
-     */
-    private static void readIndex() {
-        MemoryMappedFile mappedFile = null;
-        try {
-            mappedFile = MemoryMappedFile.mmapRO(INDEX_FILE_NAME);
-            readIndex(mappedFile);
-        } catch (Exception ex) {
-            throw new AssertionError(ex);
-        } finally {
-            IoUtils.closeQuietly(mappedFile);
-        }
+    private static void readZoneTab(BufferIterator it, int zoneTabOffset) {
+        byte[] bytes = new byte[(int) TZDATA.size() - zoneTabOffset];
+        it.seek(zoneTabOffset);
+        it.readByteArray(bytes, 0, bytes.length);
+        zoneTab = new String(bytes, 0, bytes.length, Charsets.US_ASCII);
     }
 
-    private static void readIndex(MemoryMappedFile mappedFile) throws ErrnoException, IOException {
-        BufferIterator it = mappedFile.bigEndianIterator();
+    private static void readIndex(BufferIterator it, int indexOffset, int dataOffset) {
+        it.seek(indexOffset);
 
         // The database reserves 40 bytes for each id.
         final int SIZEOF_TZNAME = 40;
@@ -141,18 +131,22 @@
         final int SIZEOF_TZINT = 4;
 
         byte[] idBytes = new byte[SIZEOF_TZNAME];
-        int numEntries = (int) mappedFile.size() / (SIZEOF_TZNAME + 3*SIZEOF_TZINT);
+        int indexSize = (dataOffset - indexOffset);
+        int entryCount = indexSize / (SIZEOF_TZNAME + 3*SIZEOF_TZINT);
 
-        char[] idChars = new char[numEntries * SIZEOF_TZNAME];
-        int[] idEnd = new int[numEntries];
+        char[] idChars = new char[entryCount * SIZEOF_TZNAME];
+        int[] idEnd = new int[entryCount];
         int idOffset = 0;
 
-        byteOffsets = new int[numEntries];
-        rawUtcOffsets = new int[numEntries];
+        byteOffsets = new int[entryCount];
+        rawUtcOffsets = new int[entryCount];
 
-        for (int i = 0; i < numEntries; i++) {
+        for (int i = 0; i < entryCount; i++) {
             it.readByteArray(idBytes, 0, idBytes.length);
+
             byteOffsets[i] = it.readInt();
+            byteOffsets[i] += dataOffset; // TODO: change the file format so this is included.
+
             int length = it.readInt();
             if (length < 44) {
                 throw new AssertionError("length in index file < sizeof(tzhead)");
@@ -174,8 +168,8 @@
         // We create one string containing all the ids, and then break that into substrings.
         // This way, all ids share a single char[] on the heap.
         String allIds = new String(idChars, 0, idOffset);
-        ids = new String[numEntries];
-        for (int i = 0; i < numEntries; i++) {
+        ids = new String[entryCount];
+        for (int i = 0; i < entryCount; i++) {
             ids[i] = allIds.substring(i == 0 ? 0 : idEnd[i - 1], idEnd[i]);
         }
     }
@@ -187,46 +181,10 @@
             return null;
         }
 
-        BufferIterator data = ALL_ZONE_DATA.bigEndianIterator();
-        data.skip(byteOffsets[index]);
+        BufferIterator it = TZDATA.bigEndianIterator();
+        it.skip(byteOffsets[index]);
 
-        // Variable names beginning tzh_ correspond to those in "tzfile.h".
-        // Check tzh_magic.
-        if (data.readInt() != 0x545a6966) { // "TZif"
-            return null;
-        }
-
-        // Skip the uninteresting part of the header.
-        data.skip(28);
-
-        // Read the sizes of the arrays we're about to read.
-        int tzh_timecnt = data.readInt();
-        int tzh_typecnt = data.readInt();
-
-        data.skip(4); // Skip tzh_charcnt.
-
-        int[] transitions = new int[tzh_timecnt];
-        data.readIntArray(transitions, 0, transitions.length);
-
-        byte[] type = new byte[tzh_timecnt];
-        data.readByteArray(type, 0, type.length);
-
-        int[] gmtOffsets = new int[tzh_typecnt];
-        byte[] isDsts = new byte[tzh_typecnt];
-        for (int i = 0; i < tzh_typecnt; ++i) {
-            gmtOffsets[i] = data.readInt();
-            isDsts[i] = data.readByte();
-            // We skip the abbreviation index. This would let us provide historically-accurate
-            // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in
-            // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current
-            // names, though, so even if we did use this data to provide the correct abbreviations
-            // for en_US, we wouldn't be able to provide correct abbreviations for other locales,
-            // nor would we be able to provide correct long forms (such as "Yukon Standard Time")
-            // for any locale. (The RI doesn't do any better than us here either.)
-            data.skip(1);
-        }
-
-        return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts);
+        return ZoneInfo.makeTimeZone(id, it);
     }
 
     public static String[] getAvailableIDs() {
@@ -235,7 +193,7 @@
 
     public static String[] getAvailableIDs(int rawOffset) {
         List<String> matches = new ArrayList<String>();
-        for (int i = 0, end = rawUtcOffsets.length; i < end; i++) {
+        for (int i = 0, end = rawUtcOffsets.length; i < end; ++i) {
             if (rawUtcOffsets[i] == rawOffset) {
                 matches.add(ids[i]);
             }
@@ -260,6 +218,10 @@
     }
 
     public static String getVersion() {
-        return VERSION;
+        return version;
+    }
+
+    public static String getZoneTab() {
+        return zoneTab;
     }
 }
diff --git a/luni/src/main/java/org/apache/harmony/security/asn1/ASN1GeneralizedTime.java b/luni/src/main/java/org/apache/harmony/security/asn1/ASN1GeneralizedTime.java
index e64ebe0..64d7ced 100644
--- a/luni/src/main/java/org/apache/harmony/security/asn1/ASN1GeneralizedTime.java
+++ b/luni/src/main/java/org/apache/harmony/security/asn1/ASN1GeneralizedTime.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.nio.charset.Charsets;
 import java.text.SimpleDateFormat;
+import java.util.Locale;
 import java.util.TimeZone;
 
 /**
@@ -83,7 +84,7 @@
     private static final String GEN_PATTERN = "yyyyMMddHHmmss.SSS";
 
     public void setEncodingContent(BerOutputStream out) {
-        SimpleDateFormat sdf = new SimpleDateFormat(GEN_PATTERN);
+        SimpleDateFormat sdf = new SimpleDateFormat(GEN_PATTERN, Locale.US);
         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
         String temp = sdf.format(out.content);
         // cut off trailing 0s
diff --git a/luni/src/main/java/org/apache/harmony/security/asn1/ASN1StringType.java b/luni/src/main/java/org/apache/harmony/security/asn1/ASN1StringType.java
index 71f5b0e..024b65f 100644
--- a/luni/src/main/java/org/apache/harmony/security/asn1/ASN1StringType.java
+++ b/luni/src/main/java/org/apache/harmony/security/asn1/ASN1StringType.java
@@ -32,43 +32,39 @@
  */
 public abstract class ASN1StringType extends ASN1Type {
 
-    // TODO: what about defining them as separate classes?
-    // TODO: check decoded/encoded characters
-    public static final ASN1StringType BMPSTRING = new ASN1StringType(
-            TAG_BMPSTRING) {
-    };
+    private static class ASN1StringUTF8Type extends ASN1StringType {
+        public ASN1StringUTF8Type(int tagNumber) {
+            super(tagNumber);
+        }
 
-    public static final ASN1StringType IA5STRING = new ASN1StringType(
-            TAG_IA5STRING) {
-    };
-
-    public static final ASN1StringType GENERALSTRING = new ASN1StringType(
-            TAG_GENERALSTRING) {
-    };
-
-    public static final ASN1StringType PRINTABLESTRING = new ASN1StringType(
-            TAG_PRINTABLESTRING) {
-    };
-
-    public static final ASN1StringType TELETEXSTRING = new ASN1StringType(
-            TAG_TELETEXSTRING) {
-    };
-
-    public static final ASN1StringType UNIVERSALSTRING = new ASN1StringType(
-            TAG_UNIVERSALSTRING) {
-    };
-
-    public static final ASN1StringType UTF8STRING = new ASN1StringType(TAG_UTF8STRING) {
-        @Override public Object getDecodedObject(BerInputStream in) throws IOException {
+        @Override
+        public Object getDecodedObject(BerInputStream in) throws IOException {
             return new String(in.buffer, in.contentOffset, in.length, Charsets.UTF_8);
         }
 
-        @Override public void setEncodingContent(BerOutputStream out) {
+        @Override
+        public void setEncodingContent(BerOutputStream out) {
             byte[] bytes = ((String) out.content).getBytes(Charsets.UTF_8);
             out.content = bytes;
             out.length = bytes.length;
         }
-    };
+    }
+
+    // TODO: what about defining them as separate classes?
+    // TODO: check decoded/encoded characters
+    public static final ASN1StringType BMPSTRING = new ASN1StringType(TAG_BMPSTRING) {};
+
+    public static final ASN1StringType IA5STRING = new ASN1StringType(TAG_IA5STRING) {};
+
+    public static final ASN1StringType GENERALSTRING = new ASN1StringType(TAG_GENERALSTRING) {};
+
+    public static final ASN1StringType PRINTABLESTRING = new ASN1StringType(TAG_PRINTABLESTRING) {};
+
+    public static final ASN1StringType TELETEXSTRING = new ASN1StringUTF8Type(TAG_TELETEXSTRING) {};
+
+    public static final ASN1StringType UNIVERSALSTRING = new ASN1StringType(TAG_UNIVERSALSTRING) {};
+
+    public static final ASN1StringType UTF8STRING = new ASN1StringUTF8Type(TAG_UTF8STRING) {};
 
     public ASN1StringType(int tagNumber) {
         super(tagNumber);
diff --git a/luni/src/main/java/org/apache/harmony/security/asn1/ASN1UTCTime.java b/luni/src/main/java/org/apache/harmony/security/asn1/ASN1UTCTime.java
index 2bc8f4b..7c355f8 100644
--- a/luni/src/main/java/org/apache/harmony/security/asn1/ASN1UTCTime.java
+++ b/luni/src/main/java/org/apache/harmony/security/asn1/ASN1UTCTime.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.nio.charset.Charsets;
 import java.text.SimpleDateFormat;
+import java.util.Locale;
 import java.util.TimeZone;
 
 /**
@@ -94,7 +95,7 @@
     private static final String UTC_PATTERN = "yyMMddHHmmss'Z'";
 
     @Override public void setEncodingContent(BerOutputStream out) {
-        SimpleDateFormat sdf = new SimpleDateFormat(UTC_PATTERN);
+        SimpleDateFormat sdf = new SimpleDateFormat(UTC_PATTERN, Locale.US);
         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
         out.content = sdf.format(out.content).getBytes(Charsets.UTF_8);
         out.length = ((byte[]) out.content).length;
diff --git a/luni/src/main/java/org/apache/harmony/security/asn1/ObjectIdentifier.java b/luni/src/main/java/org/apache/harmony/security/asn1/ObjectIdentifier.java
index 24140e5..eb1e3e3 100644
--- a/luni/src/main/java/org/apache/harmony/security/asn1/ObjectIdentifier.java
+++ b/luni/src/main/java/org/apache/harmony/security/asn1/ObjectIdentifier.java
@@ -190,7 +190,7 @@
             if (! shouldThrow) {
                 return null;
             }
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("str == null");
         }
 
         int length = str.length();
diff --git a/luni/src/main/java/org/apache/harmony/security/pkcs7/SignerInfo.java b/luni/src/main/java/org/apache/harmony/security/pkcs7/SignerInfo.java
index baaa090..4015e70 100644
--- a/luni/src/main/java/org/apache/harmony/security/pkcs7/SignerInfo.java
+++ b/luni/src/main/java/org/apache/harmony/security/pkcs7/SignerInfo.java
@@ -93,10 +93,18 @@
         return digestAlgorithm.getAlgorithm();
     }
 
+    public String getDigestAlgorithmName() {
+        return digestAlgorithm.getAlgorithmName();
+    }
+
     public String getDigestEncryptionAlgorithm() {
         return digestEncryptionAlgorithm.getAlgorithm();
     }
 
+    public String getDigestEncryptionAlgorithmName() {
+        return digestEncryptionAlgorithm.getAlgorithmName();
+    }
+
     public List<AttributeTypeAndValue> getAuthenticatedAttributes() {
         if (authenticatedAttributes == null) {
             return null;
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
index 7207002..9453164 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
@@ -197,7 +197,8 @@
                 // some Certificates have been read
                 return result;
             } else if (ch == -1) {
-                throw new CertificateException("There is no data in the stream");
+                /* No data in the stream, so return the empty collection. */
+                return result;
             }
             // else: check if it is PKCS7
             if (second_asn1_tag == ASN1Constants.TAG_OID) {
@@ -414,7 +415,7 @@
      * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
      * method documentation for more info
      */
-    public CertPath engineGenerateCertPath(List certificates)
+    public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)
             throws CertificateException {
         return new X509CertPathImpl(certificates);
     }
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
index b15e8ac..68bcec6 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java
@@ -305,9 +305,9 @@
 
     public int getBasicConstraints() {
         if (extensions == null) {
-            return Integer.MAX_VALUE;
+            return -1;
         }
-        return extensions.valueOfBasicConstrains();
+        return extensions.valueOfBasicConstraints();
     }
 
     public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java
index b65d345..3699700 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java
@@ -34,6 +34,7 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+
 import org.apache.harmony.security.asn1.ASN1Any;
 import org.apache.harmony.security.asn1.ASN1Explicit;
 import org.apache.harmony.security.asn1.ASN1Implicit;
@@ -72,118 +73,151 @@
  * &nbsp;
  */
 public class X509CertPathImpl extends CertPath {
-
     /**
      * @serial
      */
     private static final long serialVersionUID = 7989755106209515436L;
 
-    // supported encoding types:
-    public static final int PKI_PATH = 0;
-    public static final int PKCS7 = 1;
-
-    // supported encoding names
-    private static final String[] encodingsArr = new String[] {"PkiPath", "PKCS7"};
-    static final List encodings = Collections.unmodifiableList(Arrays.asList(encodingsArr));
-    // the list of certificates representing this certification path
-    private final List certificates;
-    // PkiPath encoding of the certification path
-    private byte[] pkiPathEncoding;
-    // PKCS7 encoding of the certification path
-    private byte[] pkcs7Encoding;
-
     /**
-     * Creates an instance of X.509 Certification Path over the specified
-     * list of certificates.
-     * @throws CertificateException if some of the object in the list
-     * is not an instance of subclass of X509Certificate.
+     * Supported encoding types for CerthPath. Used by the various APIs that
+     * encode this into bytes such as {@link #getEncoded()}.
      */
-    public X509CertPathImpl(List certs) throws CertificateException {
-        super("X.509");
-        int size = certs.size();
-        certificates = new ArrayList(size);
-        for (int i=0; i<size; i++) {
-            Object cert = certs.get(i);
-            if (!(cert instanceof X509Certificate) ) {
-                throw new CertificateException(
-                        "One of the provided certificates is not an X509 certificate");
+    private enum Encoding {
+        PKI_PATH("PkiPath"),
+        PKCS7("PKCS7");
+
+        private final String apiName;
+
+        Encoding(String apiName) {
+            this.apiName = apiName;
+        }
+
+        static Encoding findByApiName(String apiName) throws CertificateEncodingException {
+            for (Encoding element : values()) {
+                if (element.apiName.equals(apiName)) {
+                    return element;
+                }
             }
-            certificates.add(cert);
+
+            return null;
         }
     }
 
-    /*
-     * Internally used constructor.
-     * Creates an X.509 Certification Path over the specified
-     * list of certificates and their encoded form of specified type.
-     * @param certs - the list of certificates
-     * @param type - the type of the encoded form on the base of which
-     * this list of certificates had been built.
-     * @param encoding - encoded form of certification path.
+    /** Unmodifiable list of encodings for the API. */
+    static final List<String> encodings = Collections.unmodifiableList(Arrays.asList(new String[] {
+            Encoding.PKI_PATH.apiName,
+            Encoding.PKCS7.apiName,
+    }));
+
+    /** The list of certificates in the order of target toward trust anchor. */
+    private final List<X509Certificate> certificates;
+
+    /** PkiPath encoding of the certification path. */
+    private byte[] pkiPathEncoding;
+
+    /** PKCS7 encoding of the certification path. */
+    private byte[] pkcs7Encoding;
+
+    /**
+     * Creates an instance of X.509 CertPath over the specified list of
+     * certificates.
+     *
+     * @throws CertificateException if some of the object in the list is not an
+     *             instance of subclass of X509Certificate.
      */
-    private X509CertPathImpl(List certs, int type, byte[] encoding) {
+    public X509CertPathImpl(List<? extends java.security.cert.Certificate> certs)
+            throws CertificateException {
         super("X.509");
-        if (type == PKI_PATH) {
-            this.pkiPathEncoding = encoding;
-        } else { // PKCS7
-            this.pkcs7Encoding = encoding;
+
+        final int size = certs.size();
+        certificates = new ArrayList<X509Certificate>(size);
+
+        for (int i = 0; i < size; i++) {
+            final java.security.cert.Certificate cert = certs.get(i);
+            if (!(cert instanceof X509Certificate)) {
+                throw new CertificateException("Certificate " + i + " is not an X.509 certificate");
+            }
+
+            certificates.add((X509Certificate) cert);
         }
-        // We do not need the type check and list cloning here,
-        // because it has been done during decoding.
+    }
+
+    /**
+     * Creates an X.509 CertPath over the specified {@code certs}. The
+     * {@code certs} should be sorted correctly when calling into the
+     * constructor. Additionally, the {@code encodedPath} should match the
+     * expected output for the {@code type} of encoding.
+     */
+    private X509CertPathImpl(List<X509Certificate> certs, Encoding type) {
+        super("X.509");
+
         certificates = certs;
     }
 
     /**
-     * Generates certification path object on the base of PkiPath
-     * encoded form provided via input stream.
-     * @throws CertificateException if some problems occurred during
-     * the decoding.
+     * Extract a CertPath from a PKCS#7 {@code contentInfo} object.
+     */
+    private static X509CertPathImpl getCertPathFromContentInfo(ContentInfo contentInfo)
+            throws CertificateException {
+        final SignedData sd = contentInfo.getSignedData();
+        if (sd == null) {
+            throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data");
+        }
+
+        List<Certificate> certs = sd.getCertificates();
+        if (certs == null) {
+            certs = Collections.emptyList();
+        }
+
+        final List<X509Certificate> result = new ArrayList<X509Certificate>(certs.size());
+        for (Certificate cert : certs) {
+            result.add(new X509CertImpl(cert));
+        }
+
+        return new X509CertPathImpl(result, Encoding.PKCS7);
+    }
+
+    /**
+     * Generates certification path object on the base of PkiPath encoded form
+     * provided via input stream.
+     *
+     * @throws CertificateException if some problems occurred during the
+     *         decoding.
      */
     public static X509CertPathImpl getInstance(InputStream in) throws CertificateException {
         try {
             return (X509CertPathImpl) ASN1.decode(in);
         } catch (IOException e) {
-            throw new CertificateException("Incorrect encoded form: " + e.getMessage());
+            throw new CertificateException("Failed to decode CertPath", e);
         }
     }
 
     /**
-     * Generates certification path object on the base of encoding provided via
+     * Generates certification path object on the basis of encoding provided via
      * input stream. The format of provided encoded form is specified by
      * parameter <code>encoding</code>.
+     *
      * @throws CertificateException if specified encoding form is not supported,
-     * or some problems occurred during the decoding.
+     *         or some problems occurred during the decoding.
      */
     public static X509CertPathImpl getInstance(InputStream in, String encoding)
             throws CertificateException {
-        if (!encodings.contains(encoding)) {
-            throw new CertificateException("Unsupported encoding");
-        }
         try {
-            if (encodingsArr[0].equals(encoding)) {
-                // generate the object from PkiPath encoded form
-                return (X509CertPathImpl) ASN1.decode(in);
-            } else {
-                // generate the object from PKCS #7 encoded form
-                ContentInfo ci = (ContentInfo) ContentInfo.ASN1.decode(in);
-                SignedData sd = ci.getSignedData();
-                if (sd == null) {
-                    throw new CertificateException(
-                            "Incorrect PKCS7 encoded form: missing signed data");
-                }
-                List<Certificate> certs = sd.getCertificates();
-                if (certs == null) {
-                    // empty chain of certificates
-                    certs = new ArrayList<Certificate>();
-                }
-                List<X509CertImpl> result = new ArrayList<X509CertImpl>();
-                for (Certificate cert : certs) {
-                    result.add(new X509CertImpl(cert));
-                }
-                return new X509CertPathImpl(result, PKCS7, ci.getEncoded());
+            final Encoding encType = Encoding.findByApiName(encoding);
+            if (encType == null) {
+                throw new CertificateException("Unsupported encoding: " + encoding);
+            }
+
+            switch (encType) {
+                case PKI_PATH:
+                    return (X509CertPathImpl) ASN1.decode(in);
+                case PKCS7:
+                    return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in));
+                default:
+                    throw new CertificateException("Unsupported encoding: " + encoding);
             }
         } catch (IOException e) {
-            throw new CertificateException("Incorrect encoded form: " + e.getMessage());
+            throw new CertificateException("Failed to decode CertPath", e);
         }
     }
 
@@ -197,45 +231,36 @@
         try {
             return (X509CertPathImpl) ASN1.decode(in);
         } catch (IOException e) {
-            throw new CertificateException("Incorrect encoded form: " + e.getMessage());
+            throw new CertificateException("Failed to decode CertPath", e);
         }
     }
 
     /**
      * Generates certification path object on the base of encoding provided via
      * array of bytes. The format of provided encoded form is specified by
-     * parameter <code>encoding</code>.
+     * parameter {@code encoding}.
+     *
      * @throws CertificateException if specified encoding form is not supported,
-     * or some problems occurred during the decoding.
+     *             or some problems occurred during the decoding.
      */
     public static X509CertPathImpl getInstance(byte[] in, String encoding)
             throws CertificateException {
-        if (!encodings.contains(encoding)) {
-            throw new CertificateException("Unsupported encoding");
-        }
         try {
-            if (encodingsArr[0].equals(encoding)) {
-                // generate the object from PkiPath encoded form
-                return (X509CertPathImpl) ASN1.decode(in);
-            } else {
-                // generate the object from PKCS #7 encoded form
-                ContentInfo ci = (ContentInfo) ContentInfo.ASN1.decode(in);
-                SignedData sd = ci.getSignedData();
-                if (sd == null) {
-                    throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data");
-                }
-                List<Certificate> certs = sd.getCertificates();
-                if (certs == null) {
-                    certs = new ArrayList<Certificate>();
-                }
-                List<X509CertImpl> result = new ArrayList<X509CertImpl>();
-                for (Certificate cert : certs) {
-                    result.add(new X509CertImpl(cert));
-                }
-                return new X509CertPathImpl(result, PKCS7, ci.getEncoded());
+            final Encoding encType = Encoding.findByApiName(encoding);
+            if (encType == null) {
+                throw new CertificateException("Unsupported encoding: " + encoding);
+            }
+
+            switch (encType) {
+                case PKI_PATH:
+                    return (X509CertPathImpl) ASN1.decode(in);
+                case PKCS7:
+                    return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in));
+                default:
+                    throw new CertificateException("Unsupported encoding: " + encoding);
             }
         } catch (IOException e) {
-            throw new CertificateException("Incorrect encoded form: " + e.getMessage());
+            throw new CertificateException("Failed to decode CertPath", e);
         }
     }
 
@@ -247,81 +272,90 @@
      * @see java.security.cert.CertPath#getCertificates()
      * method documentation for more info
      */
-    public List getCertificates() {
+    @Override
+    public List<X509Certificate> getCertificates() {
         return Collections.unmodifiableList(certificates);
     }
 
     /**
+     * Returns in PkiPath format which is our default encoding.
+     *
      * @see java.security.cert.CertPath#getEncoded()
-     * method documentation for more info
      */
+    @Override
     public byte[] getEncoded() throws CertificateEncodingException {
-        if (pkiPathEncoding == null) {
-            pkiPathEncoding = ASN1.encode(this);
+        return getEncoded(Encoding.PKI_PATH);
+    }
+
+    /**
+     * @see #getEncoded(String)
+     */
+    private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
+        switch (encoding) {
+            case PKI_PATH:
+                if (pkiPathEncoding == null) {
+                    pkiPathEncoding = ASN1.encode(this);
+                }
+
+                return pkiPathEncoding.clone();
+            case PKCS7:
+                if (pkcs7Encoding == null) {
+                    pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this);
+                }
+
+                return pkcs7Encoding.clone();
+            default:
+                throw new CertificateEncodingException("Unsupported encoding: " + encoding);
         }
-        byte[] result = new byte[pkiPathEncoding.length];
-        System.arraycopy(pkiPathEncoding, 0, result, 0, pkiPathEncoding.length);
-        return result;
     }
 
     /**
      * @see java.security.cert.CertPath#getEncoded(String)
-     * method documentation for more info
      */
+    @Override
     public byte[] getEncoded(String encoding) throws CertificateEncodingException {
-        if (!encodings.contains(encoding)) {
-            throw new CertificateEncodingException("Unsupported encoding");
+        final Encoding encType = Encoding.findByApiName(encoding);
+        if (encType == null) {
+            throw new CertificateEncodingException("Unsupported encoding: " + encoding);
         }
-        if (encodingsArr[0].equals(encoding)) {
-            // PkiPath encoded form
-            return getEncoded();
-        } else {
-            // PKCS7 encoded form
-            if (pkcs7Encoding == null) {
-                pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this);
-            }
-            byte[] result = new byte[pkcs7Encoding.length];
-            System.arraycopy(pkcs7Encoding, 0, result, 0,
-                                        pkcs7Encoding.length);
-            return result;
-        }
+
+        return getEncoded(encType);
     }
 
     /**
      * @see java.security.cert.CertPath#getEncodings()
      * method documentation for more info
      */
-    public Iterator getEncodings() {
+    @Override
+    public Iterator<String> getEncodings() {
         return encodings.iterator();
     }
 
     /**
      * ASN.1 DER Encoder/Decoder for PkiPath structure.
      */
-    public static final ASN1SequenceOf ASN1 =
-                                    new ASN1SequenceOf(ASN1Any.getInstance()) {
-
+    public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf(ASN1Any.getInstance()) {
         /**
-         * Builds the instance of X509CertPathImpl on the base of the list
-         * of ASN.1 encodings of X.509 certificates provided via
-         * PkiPath structure.
+         * Builds the instance of X509CertPathImpl on the base of the list of
+         * ASN.1 encodings of X.509 certificates provided via PkiPath structure.
          * This method participates in decoding process.
          */
         public Object getDecodedObject(BerInputStream in) throws IOException {
             // retrieve the decoded content
-            List encodings = (List) in.content;
-            int size = encodings.size();
-            List certificates = new ArrayList(size);
-            for (int i=0; i<size; i++) {
+            final List<byte[]> encodedCerts = (List<byte[]>) in.content;
+
+            final int size = encodedCerts.size();
+            final List<X509Certificate> certificates = new ArrayList<X509Certificate>(size);
+
+            for (int i = size - 1; i >= 0; i--) {
                 // create the X.509 certificate on the base of its encoded form
                 // and add it to the list.
-                certificates.add(
-                    new X509CertImpl((Certificate)
-                        Certificate.ASN1.decode((byte[]) encodings.get(i))));
+                certificates.add(new X509CertImpl((Certificate) Certificate.ASN1
+                        .decode(encodedCerts.get(i))));
             }
+
             // create and return the resulting object
-            return new X509CertPathImpl(
-                    certificates, PKI_PATH, in.getEncoded());
+            return new X509CertPathImpl(certificates, Encoding.PKI_PATH);
         }
 
         /**
@@ -329,36 +363,37 @@
          * in the X509CertPathImpl object to be encoded.
          * This method participates in encoding process.
          */
-        public Collection getValues(Object object) {
+        public Collection<byte[]> getValues(Object object) {
             // object to be encoded
-            X509CertPathImpl cp = (X509CertPathImpl) object;
+            final X509CertPathImpl cp = (X509CertPathImpl) object;
+
             // if it has no certificates in it - create the sequence of size 0
             if (cp.certificates == null) {
-                return new ArrayList();
+                return Collections.emptyList();
             }
-            int size = cp.certificates.size();
-            List encodings = new ArrayList(size);
+
+            final int size = cp.certificates.size();
+            final List<byte[]> encodings = new ArrayList<byte[]>(size);
+
             try {
-                for (int i=0; i<size; i++) {
+                for (int i = size - 1; i >= 0; i--) {
                     // get the encoded form of certificate and place it into the
                     // list to be encoded in PkiPath format
-                    encodings.add(((X509Certificate) cp.certificates.get(i)).getEncoded());
+                    encodings.add(cp.certificates.get(i).getEncoded());
                 }
             } catch (CertificateEncodingException e) {
-                throw new IllegalArgumentException("Encoding Error occurred");
+                throw new IllegalArgumentException("Encoding error occurred", e);
             }
+
             return encodings;
         }
     };
 
 
-    //
-    // encoder for PKCS#7 SignedData
-    // it is assumed that only certificate field is important
-    // all other fields contain precalculated encodings:
-    //
-    // encodes X509CertPathImpl objects
-    //
+    /**
+     * Encoder for PKCS#7 SignedData. It is assumed that only certificate field
+     * is important all other fields contain pre-calculated encodings.
+     */
     private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence(
             new ASN1Type[] {
                     // version ,digestAlgorithms, content info
diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/RandomBitsSupplier.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/RandomBitsSupplier.java
index 3862132..ad3a280 100644
--- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/RandomBitsSupplier.java
+++ b/luni/src/main/java/org/apache/harmony/security/provider/crypto/RandomBitsSupplier.java
@@ -18,144 +18,39 @@
 
 package org.apache.harmony.security.provider.crypto;
 
-
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.security.ProviderException;
+import libcore.io.Streams;
 
 /**
- *  The static class providing access on Linux platform
- *  to system means for generating true random bits. <BR>
- *
- *  The source for true random bits is one of Linux's devices "/dev/urandom" or
- *  "/dev/random" depends on which one is available; if both the first is used. <BR>
- *
- *  If no device available the service is not available,
- *  that is, provider shouldn't register the algorithm. <BR>
+ * Supplies random bits from /dev/urandom.
  */
 public class RandomBitsSupplier implements SHA1_Data {
-
-
-    /**
-     * InputStream to read from device
-     *
-     * Using a BufferedInputStream leads to problems
-     * on Android in rare cases, since the
-     * BufferedInputStream's available() issues an
-     * ioctl(), and the pseudo device doesn't seem
-     * to like that. Since we're reading bigger
-     * chunks and not single bytes, the FileInputStream
-     * shouldn't be slower, so we use that. Same might
-     * apply to other Linux platforms.
-     *
-     * TODO: the above doesn't sound true.
-     */
-    private static FileInputStream fis = null;
-
-    /**
-     * File to connect to device
-     */
-    private static File randomFile = null;
-
-    /**
-     * value of field is "true" only if a device is available
-     */
-    private static boolean serviceAvailable = false;
-
-    /**
-     *  names of random devices on Linux platform
-     */
-    private static final String DEVICE_NAMES[] = { "/dev/urandom" /*, "/dev/random" */ };
-
+    private static FileInputStream devURandom;
     static {
-        for (String deviceName : DEVICE_NAMES) {
-            try {
-                File file = new File(deviceName);
-                if (file.canRead()) {
-                    fis = new FileInputStream(file);
-                    randomFile = file;
-                    serviceAvailable = true;
-                }
-            } catch (FileNotFoundException e) {
-            }
-        }
-    }
-
-
-    /**
-     * The method is called by provider to determine if a device is available.
-     */
-    static boolean isServiceAvailable() {
-        return serviceAvailable;
-    }
-
-
-    /**
-     * On platforms with "random" devices available,
-     * the method reads random bytes from the device.  <BR>
-     *
-     * In case of any runtime failure ProviderException gets thrown.
-     */
-    private static synchronized byte[] getUnixDeviceRandom(int numBytes) {
-
-        byte[] bytes = new byte[numBytes];
-
-        int total = 0;
-        int bytesRead;
-        int offset = 0;
         try {
-            for ( ; ; ) {
-
-                bytesRead = fis.read(bytes, offset, numBytes-total);
-
-
-                // the below case should not occur because /dev/random or /dev/urandom is a special file
-                // hence, if it is happened there is some internal problem
-                if ( bytesRead == -1 ) {
-                    throw new ProviderException("bytesRead == -1");
-                }
-
-                total  += bytesRead;
-                offset += bytesRead;
-
-                if ( total >= numBytes ) {
-                    break;
-                }
-            }
-        } catch (IOException e) {
-
-            // actually there should be no IOException because device is a special file;
-            // hence, there is either some internal problem or, for instance,
-            // device was removed in runtime, or something else
-            throw new ProviderException("ATTENTION: IOException in RandomBitsSupplier.getLinuxRandomBits(): " + e);
+            devURandom = new FileInputStream(new File("/dev/urandom"));
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
         }
-        return bytes;
     }
 
-    /**
-     * The method returns byte array of requested length provided service is available.
-     * ProviderException gets thrown otherwise.
-     *
-     * @param
-     *       numBytes - length of bytes requested
-     * @return
-     *       byte array
-     * @throws
-     *       InvalidArgumentException - if numBytes <= 0
-     */
-    public static byte[] getRandomBits(int numBytes) {
-        if (numBytes <= 0) {
-            throw new IllegalArgumentException(Integer.toString(numBytes));
-        }
+    static boolean isServiceAvailable() {
+        return (devURandom != null);
+    }
 
-        // We have been unable to get a random device or fall back to the
-        // native security module code - throw an exception.
-        if ( !serviceAvailable ) {
-            throw new ProviderException("ATTENTION: service is not available : no random devices");
+    public static byte[] getRandomBits(int byteCount) {
+        if (byteCount <= 0) {
+            throw new IllegalArgumentException("Too few bytes requested: " + byteCount);
         }
-
-        return getUnixDeviceRandom(numBytes);
+        try {
+            byte[] result = new byte[byteCount];
+            Streams.readFully(devURandom, result, 0, byteCount);
+            return result;
+        } catch (Exception ex) {
+            throw new ProviderException("Couldn't read " + byteCount + " random bytes", ex);
+        }
     }
 }
diff --git a/luni/src/main/java/org/apache/harmony/security/utils/AlgNameMapper.java b/luni/src/main/java/org/apache/harmony/security/utils/AlgNameMapper.java
index 9ad5373..465748e 100644
--- a/luni/src/main/java/org/apache/harmony/security/utils/AlgNameMapper.java
+++ b/luni/src/main/java/org/apache/harmony/security/utils/AlgNameMapper.java
@@ -30,6 +30,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import org.apache.harmony.security.asn1.ObjectIdentifier;
+import org.apache.harmony.xnet.provider.jsse.NativeCrypto;
 
 /**
  * Provides Algorithm Name to OID and OID to Algorithm Name mappings. Some known
@@ -113,7 +114,12 @@
      */
     public static String map2OID(String algName) {
         // alg2OidMap map contains upper case keys
-        return alg2OidMap.get(algName.toUpperCase(Locale.US));
+        final String result = alg2OidMap.get(algName.toUpperCase(Locale.US));
+        if (result != null) {
+            return result;
+        }
+
+        return NativeCrypto.OBJ_txt2nid_oid(algName);
     }
 
     /**
@@ -126,7 +132,12 @@
         // oid2AlgMap map contains upper case values
         String algUC = oid2AlgMap.get(oid);
         // if not null there is always map UC->Orig
-        return algUC == null ? null : algAliasesMap.get(algUC);
+        if (algUC != null) {
+            return algAliasesMap.get(algUC);
+        }
+
+        // If we don't know about this OID, ask OpenSSL if it does.
+        return NativeCrypto.OBJ_txt2nid_longName(oid);
     }
 
     /**
diff --git a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
index f6efb8a..f31754b 100644
--- a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
+++ b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java
@@ -114,27 +114,56 @@
         }
 
         // Get Signature instance
-        Signature sig = null;
-        String da = sigInfo.getDigestAlgorithm();
-        String dea = sigInfo.getDigestEncryptionAlgorithm();
+        final String daOid = sigInfo.getDigestAlgorithm();
+        final String daName = sigInfo.getDigestAlgorithmName();
+        final String deaOid = sigInfo.getDigestEncryptionAlgorithm();
+
         String alg = null;
-        if (da != null && dea != null) {
-            alg = da + "with" +  dea;
+        Signature sig = null;
+
+        if (daOid != null && deaOid != null) {
+            alg = daOid + "with" + deaOid;
             try {
-                sig = Signature.getInstance(alg, OpenSSLProvider.PROVIDER_NAME);
-            } catch (NoSuchAlgorithmException e) {}
-        }
-        if (sig == null) {
-            alg = da;
-            if (alg == null) {
-                return null;
-            }
-            try {
-                sig = Signature.getInstance(alg, OpenSSLProvider.PROVIDER_NAME);
+                sig = Signature.getInstance(alg);
             } catch (NoSuchAlgorithmException e) {
-                return null;
+            }
+
+            // Try to convert to names instead of OID.
+            if (sig == null) {
+                final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
+                alg = daName + "with" + deaName;
+                try {
+                    sig = Signature.getInstance(alg);
+                } catch (NoSuchAlgorithmException e) {
+                }
             }
         }
+
+        /*
+         * TODO figure out the case in which we'd only use digestAlgorithm and
+         * add a test for it.
+         */
+        if (sig == null && daOid != null) {
+            alg = daOid;
+            try {
+                sig = Signature.getInstance(alg);
+            } catch (NoSuchAlgorithmException e) {
+            }
+
+            if (sig == null && daName != null) {
+                alg = daName;
+                try {
+                    sig = Signature.getInstance(alg);
+                } catch (NoSuchAlgorithmException e) {
+                }
+            }
+        }
+
+        // We couldn't find a valid Signature type.
+        if (sig == null) {
+            return null;
+        }
+
         sig.initVerify(certs[issuerSertIndex]);
 
         // If the authenticatedAttributes field of SignerInfo contains more than zero attributes,
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/AlgorithmIdentifier.java b/luni/src/main/java/org/apache/harmony/security/x509/AlgorithmIdentifier.java
index 1ab12e6..185151c 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/AlgorithmIdentifier.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/AlgorithmIdentifier.java
@@ -72,6 +72,14 @@
     }
 
     /**
+     * For testing when algorithmName is not known, but algorithm OID is.
+     */
+    public AlgorithmIdentifier(String algorithm, String algorithmName) {
+        this(algorithm, null, null);
+        this.algorithmName = algorithmName;
+    }
+
+    /**
      * Returns the value of algorithm field of the structure.
      */
     public String getAlgorithm() {
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/BasicConstraints.java b/luni/src/main/java/org/apache/harmony/security/x509/BasicConstraints.java
index 6a473f5..fbdaec4 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/BasicConstraints.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/BasicConstraints.java
@@ -58,6 +58,10 @@
         }
     }
 
+    public boolean getCa() {
+        return ca;
+    }
+
     public int getPathLenConstraint() {
         return pathLenConstraint;
     }
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/DNParser.java b/luni/src/main/java/org/apache/harmony/security/x509/DNParser.java
index 980ab05..4946419 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/DNParser.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/DNParser.java
@@ -227,7 +227,7 @@
             case '+':
             case ',':
             case ';':
-                // separator char has beed found
+                // separator char has been found
                 return new String(chars, beg, end - beg);
             case '\\':
                 // escaped char
@@ -247,7 +247,7 @@
                 }
                 if (pos == chars.length || chars[pos] == ',' || chars[pos] == '+'
                         || chars[pos] == ';') {
-                    // separator char or the end of DN has beed found
+                    // separator char or the end of DN has been found
                     return new String(chars, beg, cur - beg);
                 }
                 break;
@@ -380,7 +380,7 @@
     /**
      * Parses DN
      *
-     * @return a list of Relative Distinguished Names(RND),
+     * @return a list of Relative Distinguished Names(RDN),
      *         each RDN is represented as a list of AttributeTypeAndValue objects
      */
     public List<List<AttributeTypeAndValue>> parse() throws IOException {
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java b/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java
index 3336b0d..9539054 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java
@@ -26,6 +26,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -222,14 +223,20 @@
      * </pre>
      * (as specified in RFC 3280)
      *
-     * @return the value of pathLenConstraint field if extension presents,
-     * and Integer.MAX_VALUE if does not.
+     * @return-1 if the Basic Constraints Extension is not present or
+     * it is present but it indicates the certificate is not a
+     * certificate authority. If the certificate is a certificate
+     * authority, returns the path length constraint if present, or
+     * Integer.MAX_VALUE if it is not.
      */
-    public int valueOfBasicConstrains() {
+    public int valueOfBasicConstraints() {
         Extension extension = getExtensionByOID("2.5.29.19");
-        BasicConstraints bc;
-        if ((extension == null) || ((bc = extension.getBasicConstraintsValue()) == null)) {
-            return Integer.MAX_VALUE;
+        if (extension == null) {
+            return -1;
+        }
+        BasicConstraints bc = extension.getBasicConstraintsValue();
+        if (bc == null || !bc.getCa()) {
+            return -1;
         }
         return bc.getPathLenConstraint();
     }
@@ -250,11 +257,7 @@
      * null if does not.
      */
     public Collection<List<?>> valueOfSubjectAlternativeName() throws IOException {
-        Extension extension = getExtensionByOID("2.5.29.17");
-        if (extension == null) {
-            return null;
-        }
-        return ((GeneralNames) GeneralNames.ASN1.decode(extension.getExtnValue())).getPairsList();
+        return decodeGeneralNames(getExtensionByOID("2.5.29.17"));
     }
 
     /**
@@ -273,11 +276,31 @@
      * null if does not.
      */
     public Collection<List<?>> valueOfIssuerAlternativeName() throws IOException {
-        Extension extension = getExtensionByOID("2.5.29.18");
+        return decodeGeneralNames(getExtensionByOID("2.5.29.18"));
+    }
+
+    /**
+     * Given an X.509 extension that encodes GeneralNames, return it in the
+     * format expected by APIs.
+     */
+    private static Collection<List<?>> decodeGeneralNames(Extension extension)
+            throws IOException {
         if (extension == null) {
             return null;
         }
-        return ((GeneralNames) GeneralNames.ASN1.decode(extension.getExtnValue())).getPairsList();
+
+        Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension
+                .getExtnValue())).getPairsList();
+
+        /*
+         * If the extension had any invalid entries, we may have an empty
+         * collection at this point, so just return null.
+         */
+        if (collection.size() == 0) {
+            return null;
+        }
+
+        return Collections.unmodifiableCollection(collection);
     }
 
     /**
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/GeneralName.java b/luni/src/main/java/org/apache/harmony/security/x509/GeneralName.java
index e216029..99bd0ef 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/GeneralName.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/GeneralName.java
@@ -29,6 +29,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -37,6 +38,7 @@
 import org.apache.harmony.security.asn1.ASN1Implicit;
 import org.apache.harmony.security.asn1.ASN1OctetString;
 import org.apache.harmony.security.asn1.ASN1Oid;
+import org.apache.harmony.security.asn1.ASN1SequenceOf;
 import org.apache.harmony.security.asn1.ASN1StringType;
 import org.apache.harmony.security.asn1.ASN1Type;
 import org.apache.harmony.security.asn1.BerInputStream;
@@ -219,10 +221,6 @@
      *  component is doubled (to 8 and 32 bytes respectively).
      */
     public GeneralName(byte[] name) throws IllegalArgumentException {
-        int length = name.length;
-        if (length != 4 && length != 8 && length != 16 && length != 32) {
-            throw new IllegalArgumentException("name.length invalid");
-        }
         this.tag = IP_ADDR;
         this.name = new byte[name.length];
         System.arraycopy(name, 0, this.name, 0, name.length);
@@ -390,6 +388,20 @@
                 byte[] address = (byte[]) name;
                 byte[] _address = (byte[]) gname.getName();
                 int length = address.length;
+
+                /*
+                 * For IP v4, as specified in RFC 791, the address must contain
+                 * exactly 4 byte component. For IP v6, as specified in RFC
+                 * 1883, the address must contain exactly 16 byte component. If
+                 * GeneralName structure is used as a part of Name Constraints
+                 * extension, to represent an address range the number of
+                 * address component is doubled (to 8 and 32 bytes
+                 * respectively).
+                 */
+                if (length != 4 && length != 8 && length != 16 && length != 32) {
+                    return false;
+                }
+
                 int _length = _address.length;
                 if (length == _length) {
                     return Arrays.equals(address, _address);
@@ -644,8 +656,9 @@
     }
 
     /**
-     * Returns the string form of the given IP address. Addresses of length 2x
-     * the canonical length are treated as a route/mask pair.
+     * Returns the string form of the given IP address. If the address is not 4
+     * octets for IPv4 or 16 octets for IPv6, an IllegalArgumentException will
+     * be thrown.
      */
     public static String ipBytesToStr(byte[] ip) {
         try {
@@ -655,12 +668,23 @@
         }
     }
 
+    /**
+     * The "Name" is actually a CHOICE of one entry, so we need to pretend it's
+     * a SEQUENCE OF and just grab the first entry.
+     */
+    private static final ASN1SequenceOf NAME_ASN1 = new ASN1SequenceOf(Name.ASN1) {
+        @Override
+        public Object decode(BerInputStream in) throws IOException {
+            return ((List<?>) super.decode(in)).get(0);
+        }
+    };
+
     public static final ASN1Choice ASN1 = new ASN1Choice(new ASN1Type[] {
            new ASN1Implicit(0, OtherName.ASN1),
            new ASN1Implicit(1, ASN1StringType.IA5STRING),
            new ASN1Implicit(2, ASN1StringType.IA5STRING),
            new ASN1Implicit(3, ORAddress.ASN1),
-           new ASN1Implicit(4, Name.ASN1),
+           new ASN1Implicit(4, NAME_ASN1),
            new ASN1Implicit(5, EDIPartyName.ASN1),
            new ASN1Implicit(6, ASN1StringType.IA5STRING),
            new ASN1Implicit(7, ASN1OctetString.getInstance()),
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/GeneralNames.java b/luni/src/main/java/org/apache/harmony/security/x509/GeneralNames.java
index 93c4af1..e8a78dd 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/GeneralNames.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/GeneralNames.java
@@ -84,7 +84,18 @@
             return result;
         }
         for (GeneralName generalName : generalNames) {
-            result.add(generalName.getAsList());
+            /*
+             * If we have an error decoding one of the GeneralNames, we'll just
+             * omit it from the final list.
+             */
+            final List<Object> genNameList;
+            try {
+                genNameList = generalName.getAsList();
+            } catch (IllegalArgumentException ignored) {
+                continue;
+            }
+
+            result.add(genNameList);
         }
         return result;
     }
diff --git a/luni/src/main/java/org/apache/harmony/security/x509/SubjectPublicKeyInfo.java b/luni/src/main/java/org/apache/harmony/security/x509/SubjectPublicKeyInfo.java
index 545d489..aef7a65 100644
--- a/luni/src/main/java/org/apache/harmony/security/x509/SubjectPublicKeyInfo.java
+++ b/luni/src/main/java/org/apache/harmony/security/x509/SubjectPublicKeyInfo.java
@@ -26,13 +26,13 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
 import java.security.spec.X509EncodedKeySpec;
 import org.apache.harmony.security.asn1.ASN1BitString;
 import org.apache.harmony.security.asn1.ASN1Sequence;
 import org.apache.harmony.security.asn1.ASN1Type;
 import org.apache.harmony.security.asn1.BerInputStream;
 import org.apache.harmony.security.asn1.BitString;
-import org.apache.harmony.security.utils.AlgNameMapper;
 
 /**
  * The class encapsulates the ASN.1 DER encoding/decoding work
@@ -103,32 +103,54 @@
     }
 
     /**
-     * Returns The PublicKey corresponding to this SubjectPublicKeyInfo
+     * Returns the PublicKey corresponding to this SubjectPublicKeyInfo
      * instance.
      */
     public PublicKey getPublicKey() {
         if (publicKey == null) {
-            String alg_oid = algorithmID.getAlgorithm();
-            try {
-                String alg =
-                    AlgNameMapper.map2AlgName(alg_oid);
+            final byte[] encoded = getEncoded();
+            final KeySpec keySpec = new X509EncodedKeySpec(encoded);
 
-                if (alg == null) {
-                    alg = alg_oid;
-                }
-                publicKey = KeyFactory.getInstance(alg)
-                    .generatePublic(new X509EncodedKeySpec(getEncoded()));
-            } catch (InvalidKeySpecException ignored) {
-            } catch (NoSuchAlgorithmException ignored) {
+            /* Try using the algorithm name first. */
+            final String algName = algorithmID.getAlgorithmName();
+            publicKey = generateKeyForAlgorithm(keySpec, algName);
+
+            /*
+             * Fall back to using the algorithm OID if it's not the same as the
+             * algorithm name.
+             */
+            final String algOid = algorithmID.getAlgorithm();
+            if (publicKey == null && !algOid.equals(algName)) {
+                publicKey = generateKeyForAlgorithm(keySpec, algOid);
             }
+
+            /*
+             * Encode this as an X.509 public key since we didn't have any
+             * KeyFactory that could handle this algorithm name or OID. Perhaps
+             * the thing that's using this can decode it.
+             */
             if (publicKey == null) {
-                publicKey = new X509PublicKey(alg_oid, getEncoded(),
-                        subjectPublicKey);
+                publicKey = new X509PublicKey(algOid, encoded, subjectPublicKey);
             }
         }
         return publicKey;
     }
 
+    /**
+     * Try to generate a PublicKey for a given {@code keySpec} and
+     * {@code algorithm} identifier. If there a problem generating the key like
+     * a missing {@code KeyFactory} or invalid {@code KeySpec}, it will return
+     * {@code null}.
+     */
+    private static PublicKey generateKeyForAlgorithm(KeySpec keySpec, String algorithm) {
+        try {
+            return KeyFactory.getInstance(algorithm).generatePublic(keySpec);
+        } catch (InvalidKeySpecException ignored) {
+        } catch (NoSuchAlgorithmException ignored) {
+        }
+        return null;
+    }
+
     public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {
             AlgorithmIdentifier.ASN1, ASN1BitString.getInstance() }) {
         @Override protected Object getDecodedObject(BerInputStream in) {
diff --git a/luni/src/main/java/org/apache/harmony/xml/ExpatAttributes.java b/luni/src/main/java/org/apache/harmony/xml/ExpatAttributes.java
index 5ec632c..9ce8e81 100644
--- a/luni/src/main/java/org/apache/harmony/xml/ExpatAttributes.java
+++ b/luni/src/main/java/org/apache/harmony/xml/ExpatAttributes.java
@@ -37,13 +37,13 @@
      * Gets the pointer to the parser. We need this so we can get to the
      * interned string pool.
      */
-    abstract int getParserPointer();
+    abstract long getParserPointer();
 
     /**
      * Gets the pointer to the underlying attribute array. Can be 0 if the
      * length is 0.
      */
-    public abstract int getPointer();
+    public abstract long getPointer();
 
     public String getURI(int index) {
         if (index < 0 || index >= getLength()) {
@@ -81,7 +81,7 @@
         if (localName == null) {
             throw new NullPointerException("localName == null");
         }
-        int pointer = getPointer();
+        long pointer = getPointer();
         if (pointer == 0) {
             return -1;
         }
@@ -92,7 +92,7 @@
         if (qName == null) {
             throw new NullPointerException("qName == null");
         }
-        int pointer = getPointer();
+        long pointer = getPointer();
         if (pointer == 0) {
             return -1;
         }
@@ -120,7 +120,7 @@
         if (localName == null) {
             throw new NullPointerException("localName == null");
         }
-        int pointer = getPointer();
+        long pointer = getPointer();
         if (pointer == 0) {
             return null;
         }
@@ -131,20 +131,20 @@
         if (qName == null) {
             throw new NullPointerException("qName == null");
         }
-        int pointer = getPointer();
+        long pointer = getPointer();
         if (pointer == 0) {
             return null;
         }
         return getValueForQName(pointer, qName);
     }
 
-    private static native String getURI(int pointer, int attributePointer, int index);
-    private static native String getLocalName(int pointer, int attributePointer, int index);
-    private static native String getQName(int pointer, int attributePointer, int index);
-    private static native String getValueByIndex(int attributePointer, int index);
-    private static native int getIndex(int attributePointer, String uri, String localName);
-    private static native int getIndexForQName(int attributePointer, String qName);
-    private static native String getValue(int attributePointer, String uri, String localName);
-    private static native String getValueForQName(int attributePointer, String qName);
-    protected native void freeAttributes(int pointer);
+    private static native String getURI(long pointer, long attributePointer, int index);
+    private static native String getLocalName(long pointer, long attributePointer, int index);
+    private static native String getQName(long pointer, long attributePointer, int index);
+    private static native String getValueByIndex(long attributePointer, int index);
+    private static native int getIndex(long attributePointer, String uri, String localName);
+    private static native int getIndexForQName(long attributePointer, String qName);
+    private static native String getValue(long attributePointer, String uri, String localName);
+    private static native String getValueForQName(long attributePointer, String qName);
+    protected native void freeAttributes(long pointer);
 }
diff --git a/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java b/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java
index 82c8e2a..db6f4ef 100644
--- a/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java
+++ b/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java
@@ -44,7 +44,7 @@
     private static final int BUFFER_SIZE = 8096; // in bytes
 
     /** Pointer to XML_Parser instance. */
-    private int pointer;
+    private long pointer;
 
     private boolean inStartElement = false;
     private int attributeCount = -1;
@@ -100,7 +100,7 @@
     /**
      * Used by {@link EntityParser}.
      */
-    private ExpatParser(String encoding, ExpatReader xmlReader, int pointer,
+    private ExpatParser(String encoding, ExpatReader xmlReader, long pointer,
             String publicId, String systemId) {
         this.encoding = encoding;
         this.xmlReader = xmlReader;
@@ -114,7 +114,7 @@
      *
      * @return the pointer to the native parser
      */
-    private native int initialize(String encoding, boolean namespacesEnabled);
+    private native long initialize(String encoding, boolean namespacesEnabled);
 
     /**
      * Called at the start of an element.
@@ -302,7 +302,7 @@
         }
 
         String encoding = pickEncoding(inputSource);
-        int pointer = createEntityParser(this.pointer, context);
+        long pointer = createEntityParser(this.pointer, context);
         try {
             EntityParser entityParser = new EntityParser(encoding, xmlReader,
                     pointer, inputSource.getPublicId(),
@@ -392,7 +392,7 @@
      * @param context passed to {@link #handleExternalEntity}
      * @return pointer to native parser
      */
-    private static native int createEntityParser(int parentPointer, String context);
+    private static native long createEntityParser(long parentPointer, String context);
 
     /**
      * Appends part of an XML document. This parser will parse the given XML to
@@ -409,7 +409,7 @@
         }
     }
 
-    private native void appendString(int pointer, String xml, boolean isFinal)
+    private native void appendString(long pointer, String xml, boolean isFinal)
             throws SAXException, ExpatException;
 
     /**
@@ -430,7 +430,7 @@
         }
     }
 
-    private native void appendChars(int pointer, char[] xml, int offset,
+    private native void appendChars(long pointer, char[] xml, int offset,
             int length) throws SAXException, ExpatException;
 
     /**
@@ -462,7 +462,7 @@
         }
     }
 
-    private native void appendBytes(int pointer, byte[] xml, int offset,
+    private native void appendBytes(long pointer, byte[] xml, int offset,
             int length) throws SAXException, ExpatException;
 
     /**
@@ -560,12 +560,12 @@
     /**
      * Releases all native objects.
      */
-    private native void release(int pointer);
+    private native void release(long pointer);
 
     /**
      * Releases native parser only.
      */
-    private static native void releaseParser(int pointer);
+    private static native void releaseParser(long pointer);
 
     /**
      * Initialize static resources.
@@ -583,7 +583,7 @@
         return line(this.pointer);
     }
 
-    private static native int line(int pointer);
+    private static native int line(long pointer);
 
     /**
      * Gets the current column number within the XML file.
@@ -592,7 +592,7 @@
         return column(this.pointer);
     }
 
-    private static native int column(int pointer);
+    private static native int column(long pointer);
 
     /**
      * Clones the current attributes so they can be used outside of
@@ -607,12 +607,12 @@
             return ClonedAttributes.EMPTY;
         }
 
-        int clonePointer
+        long clonePointer
                 = cloneAttributes(this.attributePointer, this.attributeCount);
         return new ClonedAttributes(pointer, clonePointer, attributeCount);
     }
 
-    private static native int cloneAttributes(int pointer, int attributeCount);
+    private static native long cloneAttributes(long pointer, int attributeCount);
 
     /**
      * Used for cloned attributes.
@@ -621,8 +621,8 @@
 
         private static final Attributes EMPTY = new ClonedAttributes(0, 0, 0);
 
-        private final int parserPointer;
-        private int pointer;
+        private final long parserPointer;
+        private long pointer;
         private final int length;
 
         /**
@@ -633,19 +633,19 @@
          *  length is 0.
          * @param length number of attributes
          */
-        private ClonedAttributes(int parserPointer, int pointer, int length) {
+        private ClonedAttributes(long parserPointer, long pointer, int length) {
             this.parserPointer = parserPointer;
             this.pointer = pointer;
             this.length = length;
         }
 
         @Override
-        public int getParserPointer() {
+        public long getParserPointer() {
             return this.parserPointer;
         }
 
         @Override
-        public int getPointer() {
+        public long getPointer() {
             return pointer;
         }
 
@@ -698,12 +698,12 @@
     private class CurrentAttributes extends ExpatAttributes {
 
         @Override
-        public int getParserPointer() {
+        public long getParserPointer() {
             return pointer;
         }
 
         @Override
-        public int getPointer() {
+        public long getPointer() {
             if (!inStartElement) {
                 throw new IllegalStateException(OUTSIDE_START_ELEMENT);
             }
@@ -766,7 +766,7 @@
         private int depth = 0;
 
         private EntityParser(String encoding, ExpatReader xmlReader,
-                int pointer, String publicId, String systemId) {
+                long pointer, String publicId, String systemId) {
             super(encoding, xmlReader, pointer, publicId, systemId);
         }
 
diff --git a/luni/src/main/java/org/apache/harmony/xml/ExpatReader.java b/luni/src/main/java/org/apache/harmony/xml/ExpatReader.java
index 90fa181..e0542b1 100644
--- a/luni/src/main/java/org/apache/harmony/xml/ExpatReader.java
+++ b/luni/src/main/java/org/apache/harmony/xml/ExpatReader.java
@@ -309,15 +309,10 @@
         parser.parseDocument(in);
     }
 
-    private void parse(InputStream in, String encoding, String publicId,
-            String systemId) throws IOException, SAXException {
-        ExpatParser parser = new ExpatParser(
-                encoding,
-                this,
-                processNamespaces,
-                publicId,
-                systemId
-        );
+    private void parse(InputStream in, String charsetName, String publicId, String systemId)
+            throws IOException, SAXException {
+        ExpatParser parser =
+            new ExpatParser(charsetName, this, processNamespaces, publicId, systemId);
         parser.parseDocument(in);
     }
 
diff --git a/luni/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java b/luni/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java
index 418789d..4bea1b4 100644
--- a/luni/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java
@@ -85,6 +85,10 @@
     public Node insertBefore(Node newChild, Node refChild) throws DOMException {
         LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild;
 
+        if (refChildImpl == null) {
+            return appendChild(newChild);
+        }
+
         if (refChildImpl.document != document) {
             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
         }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
index 2496a93..971e292 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
@@ -41,7 +41,7 @@
     volatile int maximumSize;
     volatile int timeout;
 
-    final int sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
+    final long sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
 
     /** Identifies OpenSSL sessions. */
     static final int OPEN_SSL = 1;
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ChainStrengthAnalyzer.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ChainStrengthAnalyzer.java
new file mode 100644
index 0000000..29b55a6
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ChainStrengthAnalyzer.java
@@ -0,0 +1,59 @@
+/*
+ * 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.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.util.List;
+
+public final class ChainStrengthAnalyzer {
+
+    private static final int MIN_MODULUS = 1024;
+    private static final String[] OID_BLACKLIST = {"1.2.840.113549.1.1.4"}; // MD5withRSA
+
+    public static final void check(X509Certificate[] chain) throws CertificateException {
+        for (X509Certificate cert : chain) {
+            checkCert(cert);
+        }
+    }
+
+    private static final void checkCert(X509Certificate cert) throws CertificateException {
+        checkModulusLength(cert);
+        checkNotMD5(cert);
+    }
+
+    private static final void checkModulusLength(X509Certificate cert) throws CertificateException {
+        Object pubkey = cert.getPublicKey();
+        if (pubkey instanceof RSAPublicKey) {
+            int modulusLength = ((RSAPublicKey) pubkey).getModulus().bitLength();
+            if(!(modulusLength >= MIN_MODULUS)) {
+                throw new CertificateException("Modulus is < 1024 bits");
+            }
+        }
+    }
+
+    private static final void checkNotMD5(X509Certificate cert) throws CertificateException {
+        String oid = cert.getSigAlgOID();
+        for (String blacklisted : OID_BLACKLIST) {
+            if (oid.equals(blacklisted)) {
+                throw new CertificateException("Signature uses an insecure hash function");
+            }
+        }
+    }
+}
+
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java
index 069d5db..eed6dee 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java
@@ -61,7 +61,7 @@
 
     /**
      * Returns the minimal possible size of the
-     * Generic[Stream|Generic]Cipher structure under this
+     * Generic[Stream|Block]Cipher structure under this
      * connection state.
      */
     protected int getMinFragmentSize() {
@@ -70,7 +70,7 @@
     }
 
     /**
-     * Returns the size of the Generic[Stream|Generic]Cipher structure
+     * Returns the size of the Generic[Stream|Block]Cipher structure
      * corresponding to the content data of specified size.
      */
     protected int getFragmentSize(int content_size) {
@@ -79,7 +79,7 @@
 
     /**
      * Returns the minimal upper bound of the content size enclosed
-     * into the Generic[Stream|Generic]Cipher structure of specified size.
+     * into the Generic[Stream|Block]Cipher structure of specified size.
      * For stream ciphers the returned value will be exact value.
      */
     protected int getContentSize(int generic_cipher_size) {
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java
index 00705cc..480d77d 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java
@@ -294,9 +294,9 @@
         byte[] content;
         if (block_size != 0) {
             // check padding
-            int padding_length = data[data.length-1];
+            int padding_length = data[data.length-1] & 0xFF;
             for (int i=0; i<padding_length; i++) {
-                if (data[data.length-2-i] != padding_length) {
+                if ((data[data.length-2-i] & 0xFF) != padding_length) {
                     throw new AlertException(
                             AlertProtocol.DECRYPTION_FAILED,
                             new SSLProtocolException(
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java
index 4e05192..42169da 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java
@@ -304,9 +304,9 @@
         byte[] content;
         if (block_size != 0) {
             // check padding
-            int padding_length = data[data.length-1];
+            int padding_length = data[data.length - 1] & 0xFF;
             for (int i=0; i<padding_length; i++) {
-                if (data[data.length-2-i] != padding_length) {
+                if ((data[data.length-2-i] & 0xFF) != padding_length) {
                     throw new AlertException(
                             AlertProtocol.DECRYPTION_FAILED,
                             new SSLProtocolException(
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DefaultSSLContextImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DefaultSSLContextImpl.java
index 5057518..66b9ebe 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DefaultSSLContextImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DefaultSSLContextImpl.java
@@ -83,11 +83,7 @@
             }
         }
 
-        String kmfAlg = Security.getProperty("ssl.KeyManagerFactory.algorithm");
-        if (kmfAlg == null) {
-            kmfAlg = "SunX509";
-        }
-
+        String kmfAlg = KeyManagerFactory.getDefaultAlgorithm();
         KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlg);
         kmf.init(ks, pwd);
         KEY_MANAGERS = kmf.getKeyManagers();
@@ -119,11 +115,7 @@
                 is.close();
             }
         }
-        String tmfAlg = Security.getProperty("ssl.TrustManagerFactory.algorithm");
-        if (tmfAlg == null) {
-            tmfAlg = "PKIX";
-        }
-
+        String tmfAlg = TrustManagerFactory.getDefaultAlgorithm();
         TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlg);
         tmf.init(ks);
         TRUST_MANAGERS = tmf.getTrustManagers();
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java
index d9b7659..58dad45 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java
@@ -112,8 +112,12 @@
         put("SSLContext.TLS", SSLContextImpl.class.getName());
         put("SSLContext.TLSv1", SSLContextImpl.class.getName());
 
-        put("KeyManagerFactory.X509", KeyManagerFactoryImpl.class.getName());
-        put("TrustManagerFactory.X509", TrustManagerFactoryImpl.class.getName());
+        put("KeyManagerFactory.PKIX", KeyManagerFactoryImpl.class.getName());
+        put("Alg.Alias.KeyManagerFactory.X509", "PKIX");
+
+        put("TrustManagerFactory.PKIX", TrustManagerFactoryImpl.class.getName());
+        put("Alg.Alias.TrustManagerFactory.X509", "PKIX");
+
         put("KeyStore.AndroidCAStore", TrustedCertificateKeyStoreSpi.class.getName());
     }
 }
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 65373ff..b24ad0e 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
@@ -18,21 +18,26 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.net.SocketTimeoutException;
 import java.nio.ByteOrder;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.SignatureException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
 import javax.net.ssl.SSLException;
 import javax.security.auth.x500.X500Principal;
 import libcore.io.Memory;
@@ -52,137 +57,257 @@
     // --- ENGINE functions ----------------------------------------------------
     public static native void ENGINE_load_dynamic();
 
-    public static native int ENGINE_by_id(String id);
+    public static native long ENGINE_by_id(String id);
 
-    public static native int ENGINE_add(int e);
+    public static native int ENGINE_add(long e);
 
-    public static native int ENGINE_init(int e);
+    public static native int ENGINE_init(long e);
 
-    public static native int ENGINE_finish(int e);
+    public static native int ENGINE_finish(long e);
 
-    public static native int ENGINE_free(int e);
+    public static native int ENGINE_free(long e);
 
-    public static native int ENGINE_load_private_key(int e, String key_id);
+    public static native long ENGINE_load_private_key(long e, String key_id);
+
+    public static native String ENGINE_get_id(long engineRef);
+
+    public static native int ENGINE_ctrl_cmd_string(long engineRef, String cmd, String arg,
+            int cmd_optional);
 
     // --- DSA/RSA public/private key handling functions -----------------------
 
-    public static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g,
-                                              byte[] pub_key, byte[] priv_key);
+    public static native long EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g,
+                                               byte[] pub_key, byte[] priv_key);
 
-    public static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q,
+    public static native long EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q,
             byte[] dmp1, byte[] dmq1, byte[] iqmp);
 
-    public static native int EVP_PKEY_size(int pkey);
+    public static native long EVP_PKEY_new_mac_key(int type, byte[] key);
 
-    public static native int EVP_PKEY_type(int pkey);
+    public static native int EVP_PKEY_size(long pkey);
 
-    public static native void EVP_PKEY_free(int pkey);
+    public static native int EVP_PKEY_type(long pkey);
 
-    public static native int EVP_PKEY_cmp(int pkey1, int pkey2);
+    public static native String EVP_PKEY_print_public(long pkeyRef);
 
-    public static native byte[] i2d_PKCS8_PRIV_KEY_INFO(int pkey);
+    public static native String EVP_PKEY_print_private(long pkeyRef);
 
-    public static native int d2i_PKCS8_PRIV_KEY_INFO(byte[] data);
+    public static native void EVP_PKEY_free(long pkey);
 
-    public static native byte[] i2d_PUBKEY(int pkey);
+    public static native int EVP_PKEY_cmp(long pkey1, long pkey2);
 
-    public static native int d2i_PUBKEY(byte[] data);
+    public static native byte[] i2d_PKCS8_PRIV_KEY_INFO(long pkey);
 
-    public static native int RSA_generate_key_ex(int modulusBits, byte[] publicExponent);
+    public static native long d2i_PKCS8_PRIV_KEY_INFO(byte[] data);
 
-    public static native int RSA_size(int pkey);
+    public static native byte[] i2d_PUBKEY(long pkey);
 
-    public static native int RSA_private_encrypt(int flen, byte[] from, byte[] to, int pkey,
+    public static native long d2i_PUBKEY(byte[] data);
+
+    public static native long RSA_generate_key_ex(int modulusBits, byte[] publicExponent);
+
+    public static native int RSA_size(long pkey);
+
+    public static native int RSA_private_encrypt(int flen, byte[] from, byte[] to, long pkey,
             int padding);
 
-    public static native int RSA_public_decrypt(int flen, byte[] from, byte[] to, int pkey,
+    public static native int RSA_public_decrypt(int flen, byte[] from, byte[] to, long pkey,
             int padding) throws BadPaddingException, SignatureException;
 
-    public static native int RSA_public_encrypt(int flen, byte[] from, byte[] to, int pkey,
+    public static native int RSA_public_encrypt(int flen, byte[] from, byte[] to, long pkey,
             int padding);
 
-    public static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, int pkey,
+    public static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, long pkey,
             int padding) throws BadPaddingException, SignatureException;
 
     /**
      * @return array of {n, e}
      */
-    public static native byte[][] get_RSA_public_params(int rsa);
+    public static native byte[][] get_RSA_public_params(long rsa);
 
     /**
      * @return array of {n, e, d, p, q, dmp1, dmq1, iqmp}
      */
-    public static native byte[][] get_RSA_private_params(int rsa);
+    public static native byte[][] get_RSA_private_params(long rsa);
 
-    public static native int DSA_generate_key(int primeBits, byte[] seed, byte[] g, byte[] p,
+    public static native long DSA_generate_key(int primeBits, byte[] seed, byte[] g, byte[] p,
             byte[] q);
 
     /**
      * @return array of {g, p, q, y(pub), x(priv)}
      */
-    public static native byte[][] get_DSA_params(int dsa);
+    public static native byte[][] get_DSA_params(long dsa);
 
-    public static native byte[] i2d_RSAPublicKey(int rsa);
+    public static native byte[] i2d_RSAPublicKey(long rsa);
 
-    public static native byte[] i2d_RSAPrivateKey(int rsa);
+    public static native byte[] i2d_RSAPrivateKey(long rsa);
 
-    public static native byte[] i2d_DSAPublicKey(int dsa);
+    public static native byte[] i2d_DSAPublicKey(long dsa);
 
-    public static native byte[] i2d_DSAPrivateKey(int dsa);
+    public static native byte[] i2d_DSAPrivateKey(long dsa);
+
+    // --- EC functions --------------------------
+
+    /**
+     * Used to request EC_GROUP_new_curve_GFp to EC_GROUP_new_curve
+     */
+    public static final int EC_CURVE_GFP = 1;
+
+    /**
+     * Used to request EC_GROUP_new_curve_GF2m to EC_GROUP_new_curve
+     */
+    public static final int EC_CURVE_GF2M = 2;
+
+    /**
+     * EC_GROUP_set_asn1_flag: indicates an EC_GROUP is a NamedCurve.
+     */
+    public static final int OPENSSL_EC_NAMED_CURVE = 0x001;
+
+    /**
+     * EC_GROUP_set_point_conversion_form: indicates compressed ASN.1 format
+     */
+    public static final int POINT_CONVERSION_COMPRESSED = 2;
+
+    /**
+     * EC_GROUP_set_point_conversion_form: indicates uncompressed ASN.1 format
+     */
+    public static final int POINT_CONVERSION_UNCOMPRESSED = 4;
+
+    /**
+     * EC_GROUP_set_point_conversion_form: indicates hybrid ASN.1 format
+     */
+    public static final int POINT_CONVERSION_HYBRID = 4;
+
+    public static native long EVP_PKEY_new_EC_KEY(long groupRef, long pubkeyRef, byte[] privkey);
+
+    public static native long EC_GROUP_new_by_curve_name(String curveName);
+
+    public static native long EC_GROUP_new_curve(int type, byte[] p, byte[] a, byte[] b);
+
+    public static native long EC_GROUP_dup(long groupRef);
+
+    public static native void EC_GROUP_set_asn1_flag(long groupRef, int flag);
+
+    public static native void EC_GROUP_set_point_conversion_form(long groupRef, int form);
+
+    public static native String EC_GROUP_get_curve_name(long groupRef);
+
+    public static native byte[][] EC_GROUP_get_curve(long groupRef);
+
+    public static native void EC_GROUP_clear_free(long ctx);
+
+    public static native boolean EC_GROUP_cmp(long ctx1, long ctx2);
+
+    public static native void EC_GROUP_set_generator(long groupCtx, long pointCtx, byte[] n, byte[] h);
+
+    public static native long EC_GROUP_get_generator(long groupCtx);
+
+    public static native int get_EC_GROUP_type(long groupCtx);
+
+    public static native byte[] EC_GROUP_get_order(long groupCtx);
+
+    public static native byte[] EC_GROUP_get_cofactor(long groupCtx);
+
+    public static native long EC_POINT_new(long groupRef);
+
+    public static native void EC_POINT_clear_free(long pointRef);
+
+    public static native boolean EC_POINT_cmp(long groupRef, long pointRef1, long pointRef2);
+
+    public static native byte[][] EC_POINT_get_affine_coordinates(long groupCtx, long pointCtx);
+
+    public static native void EC_POINT_set_affine_coordinates(long groupCtx, long pointCtx, byte[] x,
+            byte[] y);
+
+    public static native long EC_KEY_generate_key(long groupRef);
+
+    public static native long EC_KEY_get0_group(long pkeyRef);
+
+    public static native byte[] EC_KEY_get_private_key(long keyRef);
+
+    public static native long EC_KEY_get_public_key(long keyRef);
+
+    public static native int ECDH_compute_key(
+            byte[] out, int outOffset, long publicKeyRef, long privateKeyRef);
 
     // --- Message digest functions --------------
 
-    public static native int EVP_get_digestbyname(String name);
+    public static native long EVP_get_digestbyname(String name);
 
-    public static native int EVP_MD_size(int evp_md);
+    public static native int EVP_MD_size(long evp_md);
 
-    public static native int EVP_MD_block_size(int evp_md);
+    public static native int EVP_MD_block_size(long evp_md);
 
     // --- Message digest context functions --------------
 
-    public static native void EVP_MD_CTX_destroy(int ctx);
+    public static native long EVP_MD_CTX_create();
 
-    public static native int EVP_MD_CTX_copy(int ctx);
+    public static native void EVP_MD_CTX_init(long ctx);
+
+    public static native void EVP_MD_CTX_destroy(long ctx);
+
+    public static native long EVP_MD_CTX_copy(long ctx);
 
     // --- Digest handling functions -------------------------------------------
 
-    public static native int EVP_DigestInit(int evp_md);
+    public static native long EVP_DigestInit(long evp_md);
 
-    public static native void EVP_DigestUpdate(int ctx, byte[] buffer, int offset, int length);
+    public static native void EVP_DigestUpdate(long ctx, byte[] buffer, int offset, int length);
 
-    public static native int EVP_DigestFinal(int ctx, byte[] hash, int offset);
+    public static native int EVP_DigestFinal(long ctx, byte[] hash, int offset);
+
+    // --- MAC handling functions ----------------------------------------------
+
+    public static native void EVP_DigestSignInit(long evp_md_ctx, long evp_md, long evp_pkey);
+
+    public static native void EVP_DigestSignUpdate(long evp_md_ctx, byte[] in);
+
+    public static native byte[] EVP_DigestSignFinal(long evp_md_ctx);
 
     // --- Signature handling functions ----------------------------------------
 
-    public static native int EVP_SignInit(String algorithm);
+    public static native long EVP_SignInit(String algorithm);
 
-    public static native void EVP_SignUpdate(int ctx, byte[] buffer,
+    public static native void EVP_SignUpdate(long ctx, byte[] buffer,
                                                int offset, int length);
 
-    public static native int EVP_SignFinal(int ctx, byte[] signature, int offset, int key);
+    public static native int EVP_SignFinal(long ctx, byte[] signature, int offset, long key);
 
-    public static native int EVP_VerifyInit(String algorithm);
+    public static native long EVP_VerifyInit(String algorithm);
 
-    public static native void EVP_VerifyUpdate(int ctx, byte[] buffer,
+    public static native void EVP_VerifyUpdate(long ctx, byte[] buffer,
                                                int offset, int length);
 
-    public static native int EVP_VerifyFinal(int ctx, byte[] signature,
-                                             int offset, int length, int key);
+    public static native int EVP_VerifyFinal(long ctx, byte[] signature,
+                                             int offset, int length, long key);
 
 
     // --- Block ciphers -------------------------------------------------------
 
-    public static native int EVP_get_cipherbyname(String string);
+    public static native long EVP_get_cipherbyname(String string);
 
-    public static native int EVP_CipherInit_ex(int cipherNid, byte[] key, byte[] iv,
+    public static native void EVP_CipherInit_ex(long ctx, long evpCipher, byte[] key, byte[] iv,
             boolean encrypting);
 
-    public static native int EVP_CipherUpdate(int ctx, byte[] out, int outOffset, byte[] in,
-            int inOffset);
+    public static native int EVP_CipherUpdate(long ctx, byte[] out, int outOffset, byte[] in,
+            int inOffset, int inLength);
 
-    public static native int EVP_CipherFinal_ex(int ctx, byte[] out, int outOffset);
+    public static native int EVP_CipherFinal_ex(long ctx, byte[] out, int outOffset)
+            throws BadPaddingException, IllegalBlockSizeException;
 
-    public static native void EVP_CIPHER_CTX_cleanup(int ctx);
+    public static native int EVP_CIPHER_iv_length(long evpCipher);
+
+    public static native long EVP_CIPHER_CTX_new();
+
+    public static native int EVP_CIPHER_CTX_block_size(long ctx);
+
+    public static native int get_EVP_CIPHER_CTX_buf_len(long ctx);
+
+    public static native void EVP_CIPHER_CTX_set_padding(long ctx, boolean enablePadding);
+
+    public static native void EVP_CIPHER_CTX_set_key_length(long ctx, int keyBitSize);
+
+    public static native void EVP_CIPHER_CTX_cleanup(long ctx);
 
     // --- RAND seeding --------------------------------------------------------
 
@@ -194,6 +319,14 @@
 
     public static native void RAND_bytes(byte[] output);
 
+    // --- ASN.1 objects -------------------------------------------------------
+
+    public static native int OBJ_txt2nid(String oid);
+
+    public static native String OBJ_txt2nid_longName(String oid);
+
+    public static native String OBJ_txt2nid_oid(String oid);
+
     // --- X509_NAME -----------------------------------------------------------
 
     public static int X509_NAME_hash(X500Principal principal) {
@@ -211,6 +344,205 @@
         }
     }
 
+    public static native String X509_NAME_print_ex(long x509nameCtx, long flags);
+
+    // --- X509 ----------------------------------------------------------------
+
+    /** Used to request get_X509_GENERAL_NAME_stack get the "altname" field. */
+    public static final int GN_STACK_SUBJECT_ALT_NAME = 1;
+
+    /**
+     * Used to request get_X509_GENERAL_NAME_stack get the issuerAlternativeName
+     * extension.
+     */
+    public static final int GN_STACK_ISSUER_ALT_NAME = 2;
+
+    /**
+     * Used to request only non-critical types in get_X509*_ext_oids.
+     */
+    public static final int EXTENSION_TYPE_NON_CRITICAL = 0;
+
+    /**
+     * Used to request only critical types in get_X509*_ext_oids.
+     */
+    public static final int EXTENSION_TYPE_CRITICAL = 1;
+
+    public static native long d2i_X509_bio(long bioCtx);
+
+    public static native long d2i_X509(byte[] encoded);
+
+    public static native long PEM_read_bio_X509(long bioCtx);
+
+    public static native byte[] i2d_X509(long x509ctx);
+
+    /** Takes an X509 context not an X509_PUBKEY context. */
+    public static native byte[] i2d_X509_PUBKEY(long x509ctx);
+
+    public static native byte[] ASN1_seq_pack_X509(long[] x509CertRefs);
+
+    public static native long[] ASN1_seq_unpack_X509_bio(long bioRef);
+
+    public static native void X509_free(long x509ctx);
+
+    public static native int X509_cmp(long x509ctx1, long x509ctx2);
+
+    public static native int get_X509_hashCode(long x509ctx);
+
+    public static native void X509_print_ex(long bioCtx, long x509ctx, long nmflag, long certflag);
+
+    public static native byte[] X509_get_issuer_name(long x509ctx);
+
+    public static native byte[] X509_get_subject_name(long x509ctx);
+
+    public static native String get_X509_sig_alg_oid(long x509ctx);
+
+    public static native byte[] get_X509_sig_alg_parameter(long x509ctx);
+
+    public static native boolean[] get_X509_issuerUID(long x509ctx);
+
+    public static native boolean[] get_X509_subjectUID(long x509ctx);
+
+    public static native long X509_get_pubkey(long x509ctx) throws NoSuchAlgorithmException;
+
+    public static native String get_X509_pubkey_oid(long x509ctx);
+
+    public static native byte[] X509_get_ext_oid(long x509ctx, String oid);
+
+    public static native String[] get_X509_ext_oids(long x509ctx, int critical);
+
+    public static native Object[][] get_X509_GENERAL_NAME_stack(long x509ctx, int type)
+            throws CertificateParsingException;
+
+    public static native boolean[] get_X509_ex_kusage(long x509ctx);
+
+    public static native String[] get_X509_ex_xkusage(long x509ctx);
+
+    public static native int X509_check_ca(long x509ctx);
+
+    public static native int get_X509_ex_pathlen(long x509ctx);
+
+    public static native long X509_get_notBefore(long x509ctx);
+
+    public static native long X509_get_notAfter(long x509ctx);
+
+    public static native long X509_get_version(long x509ctx);
+
+    public static native byte[] X509_get_serialNumber(long x509ctx);
+
+    public static native void X509_verify(long x509ctx, long pkeyCtx);
+
+    public static native byte[] get_X509_cert_info_enc(long x509ctx);
+
+    public static native byte[] get_X509_signature(long x509ctx);
+
+    public static native int get_X509_ex_flags(long x509ctx);
+
+    // --- X509 EXFLAG ---------------------------------------------------------
+
+    public static final int EXFLAG_CRITICAL = 0x200;
+
+    // --- PKCS7 ---------------------------------------------------------------
+
+    /** Used as the "which" field in d2i_PKCS7_bio and PEM_read_bio_PKCS7. */
+    public static final int PKCS7_CERTS = 1;
+
+    /** Used as the "which" field in d2i_PKCS7_bio and PEM_read_bio_PKCS7. */
+    public static final int PKCS7_CRLS = 2;
+
+    /** Returns an array of X509 or X509_CRL pointers. */
+    public static native long[] d2i_PKCS7_bio(long bioCtx, int which);
+
+    /** Returns an array of X509 or X509_CRL pointers. */
+    public static native byte[] i2d_PKCS7(long[] certs);
+
+    /** Returns an array of X509 or X509_CRL pointers. */
+    public static native long[] PEM_read_bio_PKCS7(long bioCtx, int which);
+
+    // --- X509_CRL ------------------------------------------------------------
+
+    public static native long d2i_X509_CRL_bio(long bioCtx);
+
+    public static native long PEM_read_bio_X509_CRL(long bioCtx);
+
+    public static native byte[] i2d_X509_CRL(long x509CrlCtx);
+
+    public static native void X509_CRL_free(long x509CrlCtx);
+
+    public static native void X509_CRL_print(long bioCtx, long x509CrlCtx);
+
+    public static native String get_X509_CRL_sig_alg_oid(long x509CrlCtx);
+
+    public static native byte[] get_X509_CRL_sig_alg_parameter(long x509CrlCtx);
+
+    public static native byte[] X509_CRL_get_issuer_name(long x509CrlCtx);
+
+    /** Returns X509_REVOKED reference that is not duplicated! */
+    public static native long X509_CRL_get0_by_cert(long x509CrlCtx, long x509Ctx);
+
+    /** Returns X509_REVOKED reference that is not duplicated! */
+    public static native long X509_CRL_get0_by_serial(long x509CrlCtx, byte[] serial);
+
+    /** Returns an array of X509_REVOKED that are owned by the caller. */
+    public static native long[] X509_CRL_get_REVOKED(long x509CrlCtx);
+
+    public static native String[] get_X509_CRL_ext_oids(long x509ctx, int critical);
+
+    public static native byte[] X509_CRL_get_ext_oid(long x509CrlCtx, String oid);
+
+    public static native long X509_CRL_get_version(long x509CrlCtx);
+
+    public static native long X509_CRL_get_ext(long x509CrlCtx, String oid);
+
+    public static native byte[] get_X509_CRL_signature(long x509ctx);
+
+    public static native void X509_CRL_verify(long x509CrlCtx, long pkeyCtx);
+
+    public static native byte[] get_X509_CRL_crl_enc(long x509CrlCtx);
+
+    public static native long X509_CRL_get_lastUpdate(long x509CrlCtx);
+
+    public static native long X509_CRL_get_nextUpdate(long x509CrlCtx);
+
+    // --- X509_REVOKED --------------------------------------------------------
+
+    public static native long X509_REVOKED_dup(long x509RevokedCtx);
+
+    public static native byte[] i2d_X509_REVOKED(long x509RevokedCtx);
+
+    public static native String[] get_X509_REVOKED_ext_oids(long x509ctx, int critical);
+
+    public static native byte[] X509_REVOKED_get_ext_oid(long x509RevokedCtx, String oid);
+
+    public static native byte[] X509_REVOKED_get_serialNumber(long x509RevokedCtx);
+
+    public static native long X509_REVOKED_get_ext(long x509RevokedCtx, String oid);
+
+    /** Returns ASN1_TIME reference. */
+    public static native long get_X509_REVOKED_revocationDate(long x509RevokedCtx);
+
+    public static native void X509_REVOKED_print(long bioRef, long x509RevokedCtx);
+
+    // --- X509_EXTENSION ------------------------------------------------------
+
+    public static native int X509_supported_extension(long x509ExtensionRef);
+
+    // --- ASN1_TIME -----------------------------------------------------------
+
+    public static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal);
+
+    // --- BIO stream creation -------------------------------------------------
+
+    public static native long create_BIO_InputStream(OpenSSLBIOInputStream is);
+
+    public static native long create_BIO_OutputStream(OutputStream os);
+
+    public static native int BIO_read(long bioRef, byte[] buffer);
+
+    public static native void BIO_write(long bioRef, byte[] buffer, int offset, int length)
+            throws IOException;
+
+    public static native void BIO_free(long bioRef);
+
     // --- SSL handling --------------------------------------------------------
 
     private static final String SUPPORTED_PROTOCOL_SSLV3 = "SSLv3";
@@ -350,17 +682,19 @@
     }
 
     // EVP_PKEY types from evp.h and objects.h
-    public static final int EVP_PKEY_RSA = 6;   // NID_rsaEcnryption
-    public static final int EVP_PKEY_DSA = 116; // NID_dsa
-    public static final int EVP_PKEY_DH  = 28;  // NID_dhKeyAgreement
-    public static final int EVP_PKEY_EC  = 408; // NID_X9_62_id_ecPublicKey
+    public static final int EVP_PKEY_RSA  = 6;   // NID_rsaEcnryption
+    public static final int EVP_PKEY_DSA  = 116; // NID_dsa
+    public static final int EVP_PKEY_DH   = 28;  // NID_dhKeyAgreement
+    public static final int EVP_PKEY_EC   = 408; // NID_X9_62_id_ecPublicKey
+    public static final int EVP_PKEY_HMAC = 855; // NID_hmac
+    public static final int EVP_PKEY_CMAC = 894; // NID_cmac
 
     // RSA padding modes from rsa.h
     public static final int RSA_PKCS1_PADDING = 1;
     public static final int RSA_NO_PADDING    = 3;
 
     // SSL mode from ssl.h
-    public static final long SSL_MODE_HANDSHAKE_CUTTHROUGH = 0x00000040L;
+    public static final long SSL_MODE_HANDSHAKE_CUTTHROUGH = 0x00000020L;
 
     // SSL options from ssl.h
     public static final long SSL_OP_NO_TICKET                              = 0x00004000L;
@@ -370,7 +704,7 @@
     public static final long SSL_OP_NO_TLSv1_1                             = 0x10000000L;
     public static final long SSL_OP_NO_TLSv1_2                             = 0x08000000L;
 
-    public static native int SSL_CTX_new();
+    public static native long SSL_CTX_new();
 
     public static String[] getDefaultCipherSuites() {
         return new String[] {
@@ -416,11 +750,37 @@
         return SUPPORTED_CIPHER_SUITES.clone();
     }
 
-    public static native void SSL_CTX_free(int ssl_ctx);
+    public static native void SSL_CTX_free(long ssl_ctx);
 
-    public static native void SSL_CTX_set_session_id_context(int ssl_ctx, byte[] sid_ctx);
+    public static native void SSL_CTX_set_session_id_context(long ssl_ctx, byte[] sid_ctx);
 
-    public static native int SSL_new(int ssl_ctx) throws SSLException;
+    public static native long SSL_new(long ssl_ctx) throws SSLException;
+
+    public static native void SSL_enable_tls_channel_id(long ssl) throws SSLException;
+
+    public static native byte[] SSL_get_tls_channel_id(long ssl) throws SSLException;
+
+    public static native void SSL_use_OpenSSL_PrivateKey_for_tls_channel_id(long ssl, long pkey)
+            throws SSLException;
+
+    public static native void SSL_use_PKCS8_PrivateKey_for_tls_channel_id(
+            long ssl, byte[] pkcs8EncodedPrivateKey) throws SSLException;
+
+    public static void SSL_set1_tls_channel_id(long ssl, PrivateKey privateKey)
+            throws SSLException {
+        if (privateKey == null) {
+            throw new NullPointerException("privateKey == null");
+        } else if (privateKey instanceof OpenSSLECPrivateKey) {
+            OpenSSLKey openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey();
+            SSL_use_OpenSSL_PrivateKey_for_tls_channel_id(ssl, openSslPrivateKey.getPkeyContext());
+        } else if ("PKCS#8".equals(privateKey.getFormat())) {
+            byte[] pkcs8EncodedKey = privateKey.getEncoded();
+            SSL_use_PKCS8_PrivateKey_for_tls_channel_id(ssl, pkcs8EncodedKey);
+        } else {
+            throw new SSLException("Unsupported Channel ID private key type:" +
+                    " class: " + privateKey.getClass() + ", format: " + privateKey.getFormat());
+        }
+    }
 
     public static byte[][] encodeCertificates(Certificate[] certificates)
             throws CertificateEncodingException {
@@ -431,13 +791,13 @@
         return certificateBytes;
     }
 
-    public static native void SSL_use_certificate(int ssl, byte[][] asn1DerEncodedCertificateChain);
+    public static native void SSL_use_certificate(long ssl, byte[][] asn1DerEncodedCertificateChain);
 
-    public static native void SSL_use_OpenSSL_PrivateKey(int ssl, int pkey);
+    public static native void SSL_use_OpenSSL_PrivateKey(long ssl, long pkey);
 
-    public static native void SSL_use_PrivateKey(int ssl, byte[] pkcs8EncodedPrivateKey);
+    public static native void SSL_use_PrivateKey(long ssl, byte[] pkcs8EncodedPrivateKey);
 
-    public static native void SSL_check_private_key(int ssl) throws SSLException;
+    public static native void SSL_check_private_key(long ssl) throws SSLException;
 
     public static byte[][] encodeIssuerX509Principals(X509Certificate[] certificates)
             throws CertificateEncodingException {
@@ -448,19 +808,19 @@
         return principalBytes;
     }
 
-    public static native void SSL_set_client_CA_list(int ssl, byte[][] asn1DerEncodedX500Principals);
+    public static native void SSL_set_client_CA_list(long ssl, byte[][] asn1DerEncodedX500Principals);
 
-    public static native long SSL_get_mode(int ssl);
+    public static native long SSL_get_mode(long ssl);
 
-    public static native long SSL_set_mode(int ssl, long mode);
+    public static native long SSL_set_mode(long ssl, long mode);
 
-    public static native long SSL_clear_mode(int ssl, long mode);
+    public static native long SSL_clear_mode(long ssl, long mode);
 
-    public static native long SSL_get_options(int ssl);
+    public static native long SSL_get_options(long ssl);
 
-    public static native long SSL_set_options(int ssl, long options);
+    public static native long SSL_set_options(long ssl, long options);
 
-    public static native long SSL_clear_options(int ssl, long options);
+    public static native long SSL_clear_options(long ssl, long options);
 
     public static String[] getDefaultProtocols() {
         return new String[] { SUPPORTED_PROTOCOL_SSLV3,
@@ -476,7 +836,7 @@
         };
     }
 
-    public static void setEnabledProtocols(int ssl, String[] protocols) {
+    public static void setEnabledProtocols(long ssl, String[] protocols) {
         checkEnabledProtocols(protocols);
         // openssl uses negative logic letting you disable protocols.
         // so first, assume we need to set all (disable all) and clear none (enable none).
@@ -527,9 +887,9 @@
         return protocols;
     }
 
-    public static native void SSL_set_cipher_lists(int ssl, String[] ciphers);
+    public static native void SSL_set_cipher_lists(long ssl, String[] ciphers);
 
-    public static void setEnabledCipherSuites(int ssl, String[] cipherSuites) {
+    public static void setEnabledCipherSuites(long ssl, String[] cipherSuites) {
         checkEnabledCipherSuites(cipherSuites);
         List<String> opensslSuites = new ArrayList<String>();
         for (int i = 0; i < cipherSuites.length; i++) {
@@ -576,17 +936,17 @@
     public static final int SSL_VERIFY_PEER =                 0x01;
     public static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02;
 
-    public static native void SSL_set_verify(int sslNativePointer, int mode);
+    public static native void SSL_set_verify(long sslNativePointer, int mode);
 
-    public static native void SSL_set_session(int sslNativePointer, int sslSessionNativePointer)
+    public static native void SSL_set_session(long sslNativePointer, long sslSessionNativePointer)
         throws SSLException;
 
     public static native void SSL_set_session_creation_enabled(
-            int sslNativePointer, boolean creationEnabled) throws SSLException;
+            long sslNativePointer, boolean creationEnabled) throws SSLException;
 
-    public static native void SSL_set_tlsext_host_name(int sslNativePointer, String hostname)
+    public static native void SSL_set_tlsext_host_name(long sslNativePointer, String hostname)
             throws SSLException;
-    public static native String SSL_get_servername(int sslNativePointer);
+    public static native String SSL_get_servername(long sslNativePointer);
 
     /**
      * Enables NPN for all SSL connections in the context.
@@ -602,17 +962,17 @@
      * <p>In either case the caller should pass a non-null byte array of NPN
      * protocols to {@link #SSL_do_handshake}.
      */
-    public static native void SSL_CTX_enable_npn(int sslCtxNativePointer);
+    public static native void SSL_CTX_enable_npn(long sslCtxNativePointer);
 
     /**
      * Disables NPN for all SSL connections in the context.
      */
-    public static native void SSL_CTX_disable_npn(int sslCtxNativePointer);
+    public static native void SSL_CTX_disable_npn(long sslCtxNativePointer);
 
     /**
      * Returns the sslSessionNativePointer of the negotiated session
      */
-    public static native int SSL_do_handshake(int sslNativePointer,
+    public static native int SSL_do_handshake(long sslNativePointer,
                                               FileDescriptor fd,
                                               SSLHandshakeCallbacks shc,
                                               int timeoutMillis,
@@ -620,29 +980,29 @@
                                               byte[] npnProtocols)
         throws SSLException, SocketTimeoutException, CertificateException;
 
-    public static native byte[] SSL_get_npn_negotiated_protocol(int sslNativePointer);
+    public static native byte[] SSL_get_npn_negotiated_protocol(long sslNativePointer);
 
     /**
      * Currently only intended for forcing renegotiation for testing.
      * Not used within OpenSSLSocketImpl.
      */
-    public static native void SSL_renegotiate(int sslNativePointer) throws SSLException;
+    public static native void SSL_renegotiate(long sslNativePointer) throws SSLException;
 
     /**
      * Returns the local ASN.1 DER encoded X509 certificates.
      */
-    public static native byte[][] SSL_get_certificate(int sslNativePointer);
+    public static native byte[][] SSL_get_certificate(long sslNativePointer);
 
     /**
      * Returns the peer ASN.1 DER encoded X509 certificates.
      */
-    public static native byte[][] SSL_get_peer_cert_chain(int sslNativePointer);
+    public static native byte[][] SSL_get_peer_cert_chain(long sslNativePointer);
 
     /**
      * Reads with the native SSL_read function from the encrypted data stream
      * @return -1 if error or the end of the stream is reached.
      */
-    public static native int SSL_read(int sslNativePointer,
+    public static native int SSL_read(long sslNativePointer,
                                       FileDescriptor fd,
                                       SSLHandshakeCallbacks shc,
                                       byte[] b, int off, int len, int readTimeoutMillis)
@@ -651,32 +1011,32 @@
     /**
      * Writes with the native SSL_write function to the encrypted data stream.
      */
-    public static native void SSL_write(int sslNativePointer,
+    public static native void SSL_write(long sslNativePointer,
                                         FileDescriptor fd,
                                         SSLHandshakeCallbacks shc,
                                         byte[] b, int off, int len, int writeTimeoutMillis)
         throws IOException;
 
-    public static native void SSL_interrupt(int sslNativePointer);
-    public static native void SSL_shutdown(int sslNativePointer,
+    public static native void SSL_interrupt(long sslNativePointer);
+    public static native void SSL_shutdown(long sslNativePointer,
                                            FileDescriptor fd,
                                            SSLHandshakeCallbacks shc) throws IOException;
 
-    public static native void SSL_free(int sslNativePointer);
+    public static native void SSL_free(long sslNativePointer);
 
-    public static native byte[] SSL_SESSION_session_id(int sslSessionNativePointer);
+    public static native byte[] SSL_SESSION_session_id(long sslSessionNativePointer);
 
-    public static native long SSL_SESSION_get_time(int sslSessionNativePointer);
+    public static native long SSL_SESSION_get_time(long sslSessionNativePointer);
 
-    public static native String SSL_SESSION_get_version(int sslSessionNativePointer);
+    public static native String SSL_SESSION_get_version(long sslSessionNativePointer);
 
-    public static native String SSL_SESSION_cipher(int sslSessionNativePointer);
+    public static native String SSL_SESSION_cipher(long sslSessionNativePointer);
 
-    public static native void SSL_SESSION_free(int sslSessionNativePointer);
+    public static native void SSL_SESSION_free(long sslSessionNativePointer);
 
-    public static native byte[] i2d_SSL_SESSION(int sslSessionNativePointer);
+    public static native byte[] i2d_SSL_SESSION(long sslSessionNativePointer);
 
-    public static native int d2i_SSL_SESSION(byte[] data);
+    public static native long d2i_SSL_SESSION(byte[] data);
 
     /**
      * A collection of callbacks from the native OpenSSL code that are
@@ -716,4 +1076,6 @@
          */
         public void handshakeCompleted();
     }
+
+    public static native long ERR_peek_last_error();
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLBIOInputStream.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLBIOInputStream.java
new file mode 100644
index 0000000..823d30a
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLBIOInputStream.java
@@ -0,0 +1,68 @@
+/*
+ * 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.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides an interface to OpenSSL's BIO system directly from a Java
+ * InputStream. It allows an OpenSSL API to read directly from something more
+ * flexible interface than a byte array.
+ */
+public class OpenSSLBIOInputStream extends FilterInputStream {
+    private long ctx;
+
+    public OpenSSLBIOInputStream(InputStream is) {
+        super(is);
+
+        ctx = NativeCrypto.create_BIO_InputStream(this);
+    }
+
+    public long getBioContext() {
+        return ctx;
+    }
+
+    /**
+     * Similar to a {@code readLine} method, but matches what OpenSSL expects
+     * from a {@code BIO_gets} method.
+     */
+    public int gets(byte[] buffer) throws IOException {
+        if (buffer == null || buffer.length == 0) {
+            return 0;
+        }
+
+        int offset = 0;
+        int inputByte = 0;
+        while (offset < buffer.length) {
+            inputByte = read();
+            if (inputByte == '\n' || inputByte == -1) {
+                if (offset == 0) {
+                    // If we haven't read anything yet, ignore CRLF.
+                    continue;
+                } else {
+                    break;
+                }
+            }
+
+            buffer[offset++] = (byte) inputByte;
+        }
+
+        return offset;
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipher.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipher.java
new file mode 100644
index 0000000..6ca8835
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipher.java
@@ -0,0 +1,813 @@
+/*
+ * 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.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Locale;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import libcore.util.EmptyArray;
+
+public abstract class OpenSSLCipher extends CipherSpi {
+
+    /**
+     * Modes that a block cipher may support.
+     */
+    protected static enum Mode {
+        CBC,
+        CFB, CFB1, CFB8, CFB128,
+        CTR,
+        CTS,
+        ECB,
+        OFB, OFB64, OFB128,
+        PCBC,
+    }
+
+    /**
+     * Paddings that a block cipher may support.
+     */
+    protected static enum Padding {
+        NOPADDING,
+        PKCS5PADDING,
+        ISO10126PADDING,
+    }
+
+    /**
+     * Native pointer for the OpenSSL EVP_CIPHER context.
+     */
+    private OpenSSLCipherContext cipherCtx = new OpenSSLCipherContext(
+            NativeCrypto.EVP_CIPHER_CTX_new());
+
+    /**
+     * The current cipher mode.
+     */
+    private Mode mode = Mode.ECB;
+
+    /**
+     * The current cipher padding.
+     */
+    private Padding padding = Padding.PKCS5PADDING;
+
+    /**
+     * The Initial Vector (IV) used for the current cipher.
+     */
+    private byte[] iv;
+
+    /**
+     * Current cipher mode: encrypting or decrypting.
+     */
+    private boolean encrypting;
+
+    /**
+     * The block size of the current cipher.
+     */
+    private int blockSize;
+
+    /**
+     * The block size of the current mode.
+     */
+    private int modeBlockSize;
+
+    /**
+     * Whether the cipher has processed any data yet. OpenSSL doesn't like
+     * calling "doFinal()" in decryption mode without processing any updates.
+     */
+    private boolean calledUpdate;
+
+    protected OpenSSLCipher() {
+    }
+
+    protected OpenSSLCipher(Mode mode, Padding padding) {
+        this.mode = mode;
+        this.padding = padding;
+        blockSize = getCipherBlockSize();
+    }
+
+    /**
+     * Returns the OpenSSL cipher name for the particular {@code keySize} and
+     * cipher {@code mode}.
+     */
+    protected abstract String getCipherName(int keySize, Mode mode);
+
+    /**
+     * Checks whether the cipher supports this particular {@code keySize} (in
+     * bytes) and throws {@code InvalidKeyException} if it doesn't.
+     */
+    protected abstract void checkSupportedKeySize(int keySize) throws InvalidKeyException;
+
+    /**
+     * Checks whether the cipher supports this particular cipher {@code mode}
+     * and throws {@code NoSuchAlgorithmException} if it doesn't.
+     */
+    protected abstract void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException;
+
+    /**
+     * Checks whether the cipher supports this particular cipher {@code padding}
+     * and throws {@code NoSuchPaddingException} if it doesn't.
+     */
+    protected abstract void checkSupportedPadding(Padding padding) throws NoSuchPaddingException;
+
+    protected abstract int getCipherBlockSize();
+
+    protected boolean supportsVariableSizeKey() {
+        return false;
+    }
+
+    @Override
+    protected void engineSetMode(String modeStr) throws NoSuchAlgorithmException {
+        final Mode mode;
+        try {
+            mode = Mode.valueOf(modeStr.toUpperCase(Locale.US));
+        } catch (IllegalArgumentException e) {
+            NoSuchAlgorithmException newE = new NoSuchAlgorithmException("No such mode: "
+                    + modeStr);
+            newE.initCause(e);
+            throw newE;
+        }
+        checkSupportedMode(mode);
+        this.mode = mode;
+    }
+
+    @Override
+    protected void engineSetPadding(String paddingStr) throws NoSuchPaddingException {
+        final String paddingStrUpper = paddingStr.toUpperCase(Locale.US);
+        final Padding padding;
+        try {
+            padding = Padding.valueOf(paddingStrUpper);
+        } catch (IllegalArgumentException e) {
+            NoSuchPaddingException newE = new NoSuchPaddingException("No such padding: "
+                    + paddingStr);
+            newE.initCause(e);
+            throw newE;
+        }
+        checkSupportedPadding(padding);
+        this.padding = padding;
+    }
+
+    @Override
+    protected int engineGetBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * The size of output if {@code doFinal()} is called with this
+     * {@code inputLen}. If padding is enabled and the size of the input puts it
+     * right at the block size, it will add another block for the padding.
+     */
+    private int getOutputSize(int inputLen) {
+        if (modeBlockSize == 1) {
+            return inputLen;
+        } else {
+            final int buffered = NativeCrypto.get_EVP_CIPHER_CTX_buf_len(cipherCtx.getContext());
+            if (padding == Padding.NOPADDING) {
+                return buffered + inputLen;
+            } else {
+                final int totalLen = inputLen + buffered + modeBlockSize;
+                return totalLen - (totalLen % modeBlockSize);
+            }
+        }
+    }
+
+    @Override
+    protected int engineGetOutputSize(int inputLen) {
+        return getOutputSize(inputLen);
+    }
+
+    @Override
+    protected byte[] engineGetIV() {
+        return iv;
+    }
+
+    @Override
+    protected AlgorithmParameters engineGetParameters() {
+        return null;
+    }
+
+    private void engineInitInternal(int opmode, Key key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) {
+            encrypting = true;
+        } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
+            encrypting = false;
+        } else {
+            throw new InvalidParameterException("Unsupported opmode " + opmode);
+        }
+
+        if (!(key instanceof SecretKey)) {
+            throw new InvalidKeyException("Only SecretKey is supported");
+        }
+
+        final byte[] encodedKey = key.getEncoded();
+        if (encodedKey == null) {
+            throw new InvalidKeyException("key.getEncoded() == null");
+        }
+
+        checkSupportedKeySize(encodedKey.length);
+
+        final long cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length,
+                mode));
+        if (cipherType == 0) {
+            throw new InvalidAlgorithmParameterException("Cannot find name for key length = "
+                    + (encodedKey.length * 8) + " and mode = " + mode);
+        }
+
+        final int ivLength = NativeCrypto.EVP_CIPHER_iv_length(cipherType);
+        if (iv == null) {
+            iv = new byte[ivLength];
+        } else if (iv.length != ivLength) {
+            throw new InvalidAlgorithmParameterException("expected IV length of " + ivLength);
+        }
+
+        this.iv = iv;
+
+        if (supportsVariableSizeKey()) {
+            NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), cipherType, null, null,
+                    encrypting);
+            NativeCrypto.EVP_CIPHER_CTX_set_key_length(cipherCtx.getContext(), encodedKey.length);
+            NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, encodedKey, iv, encrypting);
+        } else {
+            NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), cipherType, encodedKey, iv,
+                    encrypting);
+        }
+
+        // OpenSSL only supports PKCS5 Padding.
+        NativeCrypto.EVP_CIPHER_CTX_set_padding(cipherCtx.getContext(),
+                padding == Padding.PKCS5PADDING);
+        modeBlockSize = NativeCrypto.EVP_CIPHER_CTX_block_size(cipherCtx.getContext());
+        calledUpdate = false;
+    }
+
+    @Override
+    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+        try {
+            engineInitInternal(opmode, key, null);
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        final byte[] iv;
+        if (params instanceof IvParameterSpec) {
+            IvParameterSpec ivParams = (IvParameterSpec) params;
+            iv = ivParams.getIV();
+        } else {
+            iv = null;
+        }
+
+        engineInitInternal(opmode, key, iv);
+    }
+
+    @Override
+    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
+            throws InvalidKeyException, InvalidAlgorithmParameterException {
+        final AlgorithmParameterSpec spec;
+        try {
+            spec = params.getParameterSpec(IvParameterSpec.class);
+        } catch (InvalidParameterSpecException e) {
+            throw new InvalidAlgorithmParameterException(e);
+        }
+
+        engineInit(opmode, key, spec, random);
+    }
+
+    private final int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset, int maximumLen) throws ShortBufferException {
+        final int intialOutputOffset = outputOffset;
+
+        final int bytesLeft = output.length - outputOffset;
+        if (bytesLeft < maximumLen) {
+            throw new ShortBufferException("output buffer too small during update: " + bytesLeft
+                    + " < " + maximumLen);
+        }
+
+        outputOffset += NativeCrypto.EVP_CipherUpdate(cipherCtx.getContext(), output, outputOffset,
+                input, inputOffset, inputLen);
+
+        calledUpdate = true;
+
+        return outputOffset - intialOutputOffset;
+    }
+
+    @Override
+    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+        final int maximumLen = getOutputSize(inputLen);
+
+        /* See how large our output buffer would need to be. */
+        final byte[] output;
+        if (maximumLen > 0) {
+            output = new byte[maximumLen];
+        } else {
+            output = EmptyArray.BYTE;
+        }
+
+        final int bytesWritten;
+        try {
+            bytesWritten = updateInternal(input, inputOffset, inputLen, output, 0, maximumLen);
+        } catch (ShortBufferException e) {
+            /* This shouldn't happen. */
+            throw new RuntimeException("calculated buffer size was wrong: " + maximumLen);
+        }
+
+        if (output.length == bytesWritten) {
+            return output;
+        } else if (bytesWritten == 0) {
+            return EmptyArray.BYTE;
+        } else {
+            return Arrays.copyOfRange(output, 0, bytesWritten);
+        }
+    }
+
+    @Override
+    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset) throws ShortBufferException {
+        final int maximumLen = getOutputSize(inputLen);
+        return updateInternal(input, inputOffset, inputLen, output, outputOffset, maximumLen);
+    }
+
+    /**
+     * Reset this Cipher instance state to process a new chunk of data.
+     */
+    private void reset() {
+        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, null, null, encrypting);
+        calledUpdate = false;
+    }
+
+    private int doFinalInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset, int maximumLen) throws IllegalBlockSizeException,
+            BadPaddingException, ShortBufferException {
+        /* Remember this so we can tell how many characters were written. */
+        final int initialOutputOffset = outputOffset;
+
+        if (inputLen > 0) {
+            final int updateBytesWritten = updateInternal(input, inputOffset, inputLen, output,
+                    outputOffset, maximumLen);
+            outputOffset += updateBytesWritten;
+            maximumLen -= updateBytesWritten;
+        }
+
+        /*
+         * If we're decrypting and haven't had any input, we should return null.
+         * Otherwise OpenSSL will complain if we call final.
+         */
+        if (!encrypting && !calledUpdate) {
+            return 0;
+        }
+
+        /* Allow OpenSSL to pad if necessary and clean up state. */
+        final int bytesLeft = output.length - outputOffset;
+        final int writtenBytes;
+        if (bytesLeft >= maximumLen) {
+            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), output,
+                    outputOffset);
+        } else {
+            final byte[] lastBlock = new byte[maximumLen];
+            writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx.getContext(), lastBlock, 0);
+            if (writtenBytes > bytesLeft) {
+                throw new ShortBufferException("buffer is too short: " + writtenBytes + " > "
+                        + bytesLeft);
+            } else if (writtenBytes > 0) {
+                System.arraycopy(lastBlock, 0, output, outputOffset, writtenBytes);
+            }
+        }
+        outputOffset += writtenBytes;
+
+        reset();
+
+        return outputOffset - initialOutputOffset;
+    }
+
+    @Override
+    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
+            throws IllegalBlockSizeException, BadPaddingException {
+        /*
+         * Other implementations return null if we've never called update()
+         * while decrypting.
+         */
+        if (!encrypting && !calledUpdate && inputLen == 0) {
+            reset();
+            return null;
+        }
+
+        final int maximumLen = getOutputSize(inputLen);
+        /* Assume that we'll output exactly on a byte boundary. */
+        final byte[] output = new byte[maximumLen];
+        final int bytesWritten;
+        try {
+            bytesWritten = doFinalInternal(input, inputOffset, inputLen, output, 0, maximumLen);
+        } catch (ShortBufferException e) {
+            /* This should not happen since we sized our own buffer. */
+            throw new RuntimeException("our calculated buffer was too small", e);
+        }
+
+        if (bytesWritten == output.length) {
+            return output;
+        } else if (bytesWritten == 0) {
+            return EmptyArray.BYTE;
+        } else {
+            return Arrays.copyOfRange(output, 0, bytesWritten);
+        }
+    }
+
+    @Override
+    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
+        if (output == null) {
+            throw new NullPointerException("output == null");
+        }
+
+        final int maximumLen = getOutputSize(inputLen);
+        return doFinalInternal(input, inputOffset, inputLen, output, outputOffset, maximumLen);
+    }
+
+    @Override
+    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException {
+        try {
+            byte[] encoded = key.getEncoded();
+            return engineDoFinal(encoded, 0, encoded.length);
+        } catch (BadPaddingException e) {
+            IllegalBlockSizeException newE = new IllegalBlockSizeException();
+            newE.initCause(e);
+            throw newE;
+        }
+    }
+
+    @Override
+    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)
+            throws InvalidKeyException, NoSuchAlgorithmException {
+        try {
+            byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
+            if (wrappedKeyType == Cipher.PUBLIC_KEY) {
+                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
+                return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
+            } else if (wrappedKeyType == Cipher.PRIVATE_KEY) {
+                KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
+                return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+            } else if (wrappedKeyType == Cipher.SECRET_KEY) {
+                return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
+            } else {
+                throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType);
+            }
+        } catch (IllegalBlockSizeException e) {
+            throw new InvalidKeyException(e);
+        } catch (BadPaddingException e) {
+            throw new InvalidKeyException(e);
+        } catch (InvalidKeySpecException e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    public static class AES extends OpenSSLCipher {
+        private static final int AES_BLOCK_SIZE = 16;
+
+        protected AES(Mode mode, Padding padding) {
+            super(mode, padding);
+        }
+
+        public static class CBC extends AES {
+            public CBC(Padding padding) {
+                super(Mode.CBC, padding);
+            }
+
+            public static class NoPadding extends CBC {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends CBC {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class CFB extends AES {
+            public CFB(Padding padding) {
+                super(Mode.CFB, padding);
+            }
+
+            public static class NoPadding extends CFB {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends CFB {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class CTR extends AES {
+            public CTR(Padding padding) {
+                super(Mode.CTR, padding);
+            }
+
+            public static class NoPadding extends CTR {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends CTR {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class ECB extends AES {
+            public ECB(Padding padding) {
+                super(Mode.ECB, padding);
+            }
+
+            public static class NoPadding extends ECB {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends ECB {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class OFB extends AES {
+            public OFB(Padding padding) {
+                super(Mode.OFB, padding);
+            }
+
+            public static class NoPadding extends OFB {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends OFB {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        @Override
+        protected void checkSupportedKeySize(int keyLength) throws InvalidKeyException {
+            switch (keyLength) {
+                case 16: // AES 128
+                case 24: // AES 192
+                case 32: // AES 256
+                    return;
+                default:
+                    throw new InvalidKeyException("Unsupported key size: " + keyLength + " bytes");
+            }
+        }
+
+        @Override
+        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
+            switch (mode) {
+                case CBC:
+                case CFB:
+                case CFB1:
+                case CFB8:
+                case CFB128:
+                case CTR:
+                case ECB:
+                case OFB:
+                    return;
+                default:
+                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
+            }
+        }
+
+        @Override
+        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
+            switch (padding) {
+                case NOPADDING:
+                case PKCS5PADDING:
+                    return;
+                default:
+                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
+            }
+        }
+
+        @Override
+        protected String getCipherName(int keyLength, Mode mode) {
+            return "aes-" + (keyLength * 8) + "-" + mode.toString().toLowerCase(Locale.US);
+        }
+
+        @Override
+        protected int getCipherBlockSize() {
+            return AES_BLOCK_SIZE;
+        }
+    }
+
+    public static class DESEDE extends OpenSSLCipher {
+        private static int DES_BLOCK_SIZE = 8;
+
+        public DESEDE(Mode mode, Padding padding) {
+            super(mode, padding);
+        }
+
+        public static class CBC extends DESEDE {
+            public CBC(Padding padding) {
+                super(Mode.CBC, padding);
+            }
+
+            public static class NoPadding extends CBC {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends CBC {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class CFB extends DESEDE {
+            public CFB(Padding padding) {
+                super(Mode.CFB, padding);
+            }
+
+            public static class NoPadding extends CFB {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends CFB {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class ECB extends DESEDE {
+            public ECB(Padding padding) {
+                super(Mode.ECB, padding);
+            }
+
+            public static class NoPadding extends ECB {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends ECB {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        public static class OFB extends DESEDE {
+            public OFB(Padding padding) {
+                super(Mode.OFB, padding);
+            }
+
+            public static class NoPadding extends OFB {
+                public NoPadding() {
+                    super(Padding.NOPADDING);
+                }
+            }
+
+            public static class PKCS5Padding extends OFB {
+                public PKCS5Padding() {
+                    super(Padding.PKCS5PADDING);
+                }
+            }
+        }
+
+        @Override
+        protected String getCipherName(int keySize, Mode mode) {
+            final String baseCipherName;
+            if (keySize == 16) {
+                baseCipherName = "des-ede";
+            } else {
+                baseCipherName = "des-ede3";
+            }
+
+            if (mode == Mode.ECB) {
+                return baseCipherName;
+            } else {
+                return baseCipherName + "-" + mode.toString().toLowerCase(Locale.US);
+            }
+        }
+
+        @Override
+        protected void checkSupportedKeySize(int keySize) throws InvalidKeyException {
+            if (keySize != 16 && keySize != 24) {
+                throw new InvalidKeyException("key size must be 128 or 192 bits");
+            }
+        }
+
+        @Override
+        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
+            switch (mode) {
+                case CBC:
+                case CFB:
+                case CFB1:
+                case CFB8:
+                case ECB:
+                case OFB:
+                    return;
+                default:
+                    throw new NoSuchAlgorithmException("Unsupported mode " + mode.toString());
+            }
+        }
+
+        @Override
+        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
+            switch (padding) {
+                case NOPADDING:
+                case PKCS5PADDING:
+                    return;
+                default:
+                    throw new NoSuchPaddingException("Unsupported padding " + padding.toString());
+            }
+        }
+
+        @Override
+        protected int getCipherBlockSize() {
+            return DES_BLOCK_SIZE;
+        }
+    }
+
+    public static class ARC4 extends OpenSSLCipher {
+        public ARC4() {
+        }
+
+        @Override
+        protected String getCipherName(int keySize, Mode mode) {
+            return "rc4";
+        }
+
+        @Override
+        protected void checkSupportedKeySize(int keySize) throws InvalidKeyException {
+        }
+
+        @Override
+        protected void checkSupportedMode(Mode mode) throws NoSuchAlgorithmException {
+            throw new NoSuchAlgorithmException("ARC4 does not support modes");
+        }
+
+        @Override
+        protected void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
+            throw new NoSuchPaddingException("ARC4 does not support padding");
+        }
+
+        @Override
+        protected int getCipherBlockSize() {
+            return 0;
+        }
+
+        @Override
+        protected boolean supportsVariableSizeKey() {
+            return true;
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherContext.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherContext.java
new file mode 100644
index 0000000..eb99334
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLCipherContext.java
@@ -0,0 +1,42 @@
+/*
+ * 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 OpenSSLCipherContext {
+    private final long context;
+
+    OpenSSLCipherContext(long ctx) {
+        if (ctx == 0) {
+            throw new NullPointerException("ctx == 0");
+        }
+
+        this.context = ctx;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            NativeCrypto.EVP_CIPHER_CTX_cleanup(context);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    long getContext() {
+        return context;
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java
index e3f6ed5..96eded6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java
@@ -36,20 +36,14 @@
 
     @Override
     protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
         if (keySpec instanceof DSAPublicKeySpec) {
-            DSAPublicKeySpec dsaKeySpec = (DSAPublicKeySpec) keySpec;
-
-            return new OpenSSLDSAPublicKey(dsaKeySpec);
+            return new OpenSSLDSAPublicKey((DSAPublicKeySpec) keySpec);
         } else if (keySpec instanceof X509EncodedKeySpec) {
-            X509EncodedKeySpec x509KeySpec = (X509EncodedKeySpec) keySpec;
-
-            try {
-                final OpenSSLKey key = new OpenSSLKey(
-                        NativeCrypto.d2i_PUBKEY(x509KeySpec.getEncoded()));
-                return new OpenSSLDSAPublicKey(key);
-            } catch (Exception e) {
-                throw new InvalidKeySpecException(e);
-            }
+            return OpenSSLKey.getPublicKey((X509EncodedKeySpec) keySpec, NativeCrypto.EVP_PKEY_DSA);
         }
         throw new InvalidKeySpecException("Must use DSAPublicKeySpec or X509EncodedKeySpec; was "
                 + keySpec.getClass().getName());
@@ -57,22 +51,17 @@
 
     @Override
     protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
-        if (keySpec instanceof DSAPrivateKeySpec) {
-            DSAPrivateKeySpec dsaKeySpec = (DSAPrivateKeySpec) keySpec;
-
-            return new OpenSSLDSAPrivateKey(dsaKeySpec);
-        } else if (keySpec instanceof PKCS8EncodedKeySpec) {
-            PKCS8EncodedKeySpec pkcs8KeySpec = (PKCS8EncodedKeySpec) keySpec;
-
-            try {
-                final OpenSSLKey key = new OpenSSLKey(
-                        NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(pkcs8KeySpec.getEncoded()));
-                return new OpenSSLDSAPrivateKey(key);
-            } catch (Exception e) {
-                throw new InvalidKeySpecException(e);
-            }
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
         }
-        throw new InvalidKeySpecException("Must use DSAPublicKeySpec or PKCS8EncodedKeySpec; was "
+
+        if (keySpec instanceof DSAPrivateKeySpec) {
+            return new OpenSSLDSAPrivateKey((DSAPrivateKeySpec) keySpec);
+        } else if (keySpec instanceof PKCS8EncodedKeySpec) {
+            return OpenSSLKey.getPrivateKey((PKCS8EncodedKeySpec) keySpec,
+                    NativeCrypto.EVP_PKEY_DSA);
+        }
+        throw new InvalidKeySpecException("Must use DSAPrivateKeySpec or PKCS8EncodedKeySpec; was "
                 + keySpec.getClass().getName());
     }
 
@@ -87,42 +76,63 @@
             throw new InvalidKeySpecException("keySpec == null");
         }
 
-        if (key instanceof DSAPublicKey) {
+        if (!"DSA".equals(key.getAlgorithm())) {
+            throw new InvalidKeySpecException("Key must be a DSA key");
+        }
+
+        if (key instanceof DSAPublicKey && DSAPublicKeySpec.class.isAssignableFrom(keySpec)) {
             DSAPublicKey dsaKey = (DSAPublicKey) key;
-
-            if (DSAPublicKeySpec.class.equals(keySpec)) {
-                BigInteger y = dsaKey.getY();
-
-                DSAParams params = dsaKey.getParams();
-                BigInteger p = params.getP();
-                BigInteger q = params.getQ();
-                BigInteger g = params.getG();
-
-                return (T) new DSAPublicKeySpec(y, p, q, g);
-            } else if (PKCS8EncodedKeySpec.class.equals(keySpec)) {
-                return (T) new PKCS8EncodedKeySpec(key.getEncoded());
-            } else {
-                throw new InvalidKeySpecException("Must be DSAPublicKeySpec or PKCS8EncodedKeySpec");
+            DSAParams params = dsaKey.getParams();
+            return (T) new DSAPublicKeySpec(dsaKey.getY(), params.getP(), params.getQ(),
+                    params.getG());
+        } else if (key instanceof PublicKey && DSAPublicKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid X.509 encoding");
             }
-        } else if (key instanceof DSAPrivateKey) {
+            DSAPublicKey dsaKey =
+                    (DSAPublicKey) engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            DSAParams params = dsaKey.getParams();
+            return (T) new DSAPublicKeySpec(dsaKey.getY(), params.getP(), params.getQ(),
+                    params.getG());
+        } else if (key instanceof DSAPrivateKey
+                && DSAPrivateKeySpec.class.isAssignableFrom(keySpec)) {
             DSAPrivateKey dsaKey = (DSAPrivateKey) key;
-
-            if (DSAPrivateKeySpec.class.equals(keySpec)) {
-                BigInteger x = dsaKey.getX();
-
-                DSAParams params = dsaKey.getParams();
-                BigInteger p = params.getP();
-                BigInteger q = params.getQ();
-                BigInteger g = params.getG();
-
-                return (T) new DSAPrivateKeySpec(x, p, q, g);
-            } else if (X509EncodedKeySpec.class.equals(keySpec)) {
-                return (T) new X509EncodedKeySpec(dsaKey.getEncoded());
-            } else {
-                throw new InvalidKeySpecException("Must be DSAPrivateKeySpec or X509EncodedKeySpec");
+            DSAParams params = dsaKey.getParams();
+            return (T) new DSAPrivateKeySpec(dsaKey.getX(), params.getP(), params.getQ(),
+                    params.getG());
+        } else if (key instanceof PrivateKey && DSAPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
             }
+            DSAPrivateKey dsaKey =
+                    (DSAPrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            DSAParams params = dsaKey.getParams();
+            return (T) new DSAPrivateKeySpec(dsaKey.getX(), params.getP(), params.getQ(),
+                    params.getG());
+        } else if (key instanceof PrivateKey
+                && PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be PKCS#8; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            return (T) new PKCS8EncodedKeySpec(encoded);
+        } else if (key instanceof PublicKey && X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be X.509; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            return (T) new X509EncodedKeySpec(encoded);
         } else {
-            throw new InvalidKeySpecException("Must be DSAPublicKey or DSAPrivateKey");
+            throw new InvalidKeySpecException("Unsupported key type and key spec combination; key="
+                    + key.getClass().getName() + ", keySpec=" + keySpec.getName());
         }
     }
 
@@ -158,12 +168,33 @@
             BigInteger g = params.getG();
 
             try {
-                return engineGeneratePublic(new DSAPrivateKeySpec(x, p, q, g));
+                return engineGeneratePrivate(new DSAPrivateKeySpec(x, p, q, g));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ("PKCS#8".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ("X.509".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePublic(new X509EncodedKeySpec(encoded));
             } catch (InvalidKeySpecException e) {
                 throw new InvalidKeyException(e);
             }
         } else {
-            throw new InvalidKeyException("Key is not DSAPublicKey or DSAPrivateKey");
+            throw new InvalidKeyException("Key must be DSA public or private key; was "
+                    + key.getClass().getName());
         }
     }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAParams.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAParams.java
index 08aebf0..df9968b 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAParams.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAParams.java
@@ -50,9 +50,15 @@
         }
 
         byte[][] params = NativeCrypto.get_DSA_params(key.getPkeyContext());
-        g = new BigInteger(params[0]);
-        p = new BigInteger(params[1]);
-        q = new BigInteger(params[2]);
+        if (params[0] != null) {
+            g = new BigInteger(params[0]);
+        }
+        if (params[1] != null) {
+            p = new BigInteger(params[1]);
+        }
+        if (params[2] != null) {
+            q = new BigInteger(params[2]);
+        }
         if (params[3] != null) {
             y = new BigInteger(params[3]);
         }
@@ -81,6 +87,11 @@
         return q;
     }
 
+    boolean hasParams() {
+        ensureReadParams();
+        return (g != null) && (p != null) && (q != null);
+    }
+
     BigInteger getY() {
         ensureReadParams();
         return y;
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
index 761b08e..df62c6d 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
@@ -16,6 +16,10 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.interfaces.DSAParams;
@@ -23,18 +27,19 @@
 import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.InvalidKeySpecException;
 
-public class OpenSSLDSAPrivateKey implements DSAPrivateKey {
+public class OpenSSLDSAPrivateKey implements DSAPrivateKey, OpenSSLKeyHolder {
     private static final long serialVersionUID = 6524734576187424628L;
 
-    private final OpenSSLKey key;
+    private transient OpenSSLKey key;
 
-    private OpenSSLDSAParams params;
+    private transient OpenSSLDSAParams params;
 
     OpenSSLDSAPrivateKey(OpenSSLKey key) {
         this.key = key;
     }
 
-    OpenSSLKey getOpenSSLKey() {
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
         return key;
     }
 
@@ -112,18 +117,14 @@
 
     @Override
     public BigInteger getX() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private key value X cannot be extracted");
+        }
+
         ensureReadParams();
         return params.getX();
     }
 
-    public int getPkeyContext() {
-        return key.getPkeyContext();
-    }
-
-    public String getPkeyAlias() {
-        return key.getAlias();
-    }
-
     @Override
     public boolean equals(Object o) {
         if (o == this) {
@@ -200,4 +201,34 @@
 
         return sb.toString();
     }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        final BigInteger g = (BigInteger) stream.readObject();
+        final BigInteger p = (BigInteger) stream.readObject();
+        final BigInteger q = (BigInteger) stream.readObject();
+        final BigInteger x = (BigInteger) stream.readObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DSA(
+                p.toByteArray(),
+                q.toByteArray(),
+                g.toByteArray(),
+                null,
+                x.toByteArray()));
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (getOpenSSLKey().isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        stream.defaultWriteObject();
+
+        ensureReadParams();
+        stream.writeObject(params.getG());
+        stream.writeObject(params.getP());
+        stream.writeObject(params.getQ());
+        stream.writeObject(params.getX());
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPublicKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPublicKey.java
index 25869bb..be9ff83 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPublicKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPublicKey.java
@@ -16,6 +16,10 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.interfaces.DSAParams;
@@ -23,18 +27,19 @@
 import java.security.spec.DSAPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
 
-public class OpenSSLDSAPublicKey implements DSAPublicKey {
+public class OpenSSLDSAPublicKey implements DSAPublicKey, OpenSSLKeyHolder {
     private static final long serialVersionUID = 5238609500353792232L;
 
-    private final OpenSSLKey key;
+    private transient OpenSSLKey key;
 
-    private OpenSSLDSAParams params;
+    private transient OpenSSLDSAParams params;
 
     OpenSSLDSAPublicKey(OpenSSLKey key) {
         this.key = key;
     }
 
-    OpenSSLKey getOpenSSLKey() {
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
         return key;
     }
 
@@ -74,6 +79,15 @@
     @Override
     public DSAParams getParams() {
         ensureReadParams();
+
+        /*
+         * DSA keys can lack parameters if they're part of a certificate
+         * chain. In this case, we just return null.
+         */
+        if (!params.hasParams()) {
+            return null;
+        }
+
         return params;
     }
 
@@ -147,4 +161,33 @@
 
         return sb.toString();
     }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        final BigInteger g = (BigInteger) stream.readObject();
+        final BigInteger p = (BigInteger) stream.readObject();
+        final BigInteger q = (BigInteger) stream.readObject();
+        final BigInteger y = (BigInteger) stream.readObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DSA(
+                p.toByteArray(),
+                q.toByteArray(),
+                g.toByteArray(),
+                y.toByteArray(),
+                null));
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (getOpenSSLKey().isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+        stream.defaultWriteObject();
+
+        ensureReadParams();
+        stream.writeObject(params.getG());
+        stream.writeObject(params.getP());
+        stream.writeObject(params.getQ());
+        stream.writeObject(params.getY());
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDigestContext.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDigestContext.java
new file mode 100644
index 0000000..b3bd1a0
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDigestContext.java
@@ -0,0 +1,42 @@
+/*
+ * 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 class OpenSSLDigestContext {
+    private final long context;
+
+    public OpenSSLDigestContext(long ctx) {
+        if (ctx == 0) {
+            throw new NullPointerException("ctx == 0");
+        }
+
+        this.context = ctx;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            NativeCrypto.EVP_MD_CTX_destroy(context);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    long getContext() {
+        return context;
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
new file mode 100644
index 0000000..096e300
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013 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.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
+ */
+public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
+
+    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
+    private OpenSSLKey mOpenSslPrivateKey;
+
+    /**
+     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
+     * engine has been initialized.
+     */
+    private int mExpectedResultLength;
+
+    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
+    private byte[] mResult;
+
+    @Override
+    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
+        if (mOpenSslPrivateKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+        if (!lastPhase) {
+            throw new IllegalStateException("ECDH only has one phase");
+        }
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof ECPublicKey)) {
+            throw new InvalidKeyException("This phase requires an ECPublicKey. Actual key type: "
+                + key.getClass());
+        }
+        ECPublicKey publicKey = (ECPublicKey) key;
+
+        OpenSSLKey openSslPublicKey;
+        if (publicKey instanceof OpenSSLECPublicKey) {
+            // OpenSSL-backed key
+            openSslPublicKey = ((OpenSSLECPublicKey) publicKey).getOpenSSLKey();
+        } else {
+            // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its X.509 encoding
+            if (!"X.509".equals(publicKey.getFormat())) {
+                throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+                    + ") offers unsupported encoding format: " + publicKey.getFormat());
+            }
+            byte[] encoded = publicKey.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+                    + ") does not provide encoded form");
+            }
+            try {
+                openSslPublicKey = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded));
+            } catch (Exception e) {
+                throw new InvalidKeyException("Failed to decode X.509 encoded public key", e);
+            }
+        }
+
+        byte[] buffer = new byte[mExpectedResultLength];
+        int actualResultLength = NativeCrypto.ECDH_compute_key(
+                buffer,
+                0,
+                openSslPublicKey.getPkeyContext(),
+                mOpenSslPrivateKey.getPkeyContext());
+        byte[] result;
+        if (actualResultLength == -1) {
+            throw new RuntimeException("Engine returned " + actualResultLength);
+        } else if (actualResultLength == mExpectedResultLength) {
+            // The output is as long as expected -- use the whole buffer
+            result = buffer;
+        } else if (actualResultLength < mExpectedResultLength) {
+            // The output is shorter than expected -- use only what's produced by the engine
+            result = new byte[actualResultLength];
+            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
+        } else {
+            // The output is longer than expected
+            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
+                + mExpectedResultLength + ", actual: " + actualResultLength);
+        }
+        mResult = result;
+
+        return null; // No intermediate key
+    }
+
+    @Override
+    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+            throws ShortBufferException {
+        checkCompleted();
+        int available = sharedSecret.length - offset;
+        if (mResult.length > available) {
+            throw new ShortBufferException(
+                    "Needed: " + mResult.length + ", available: " + available);
+        }
+
+        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
+        return mResult.length;
+    }
+
+    @Override
+    protected byte[] engineGenerateSecret() {
+        checkCompleted();
+        return mResult;
+    }
+
+    @Override
+    protected SecretKey engineGenerateSecret(String algorithm) {
+        checkCompleted();
+        return new SecretKeySpec(engineGenerateSecret(), algorithm);
+    }
+
+    @Override
+    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof ECPrivateKey)) {
+            throw new InvalidKeyException("Not an EC private key: " + key.getClass());
+        }
+        ECPrivateKey privateKey = (ECPrivateKey) key;
+        mExpectedResultLength =
+                (privateKey.getParams().getCurve().getField().getFieldSize() + 7) / 8;
+
+        OpenSSLKey openSslPrivateKey;
+        if (privateKey instanceof OpenSSLECPrivateKey) {
+            // OpenSSL-backed key
+            openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey();
+        } else {
+            // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its PKCS#8 encoding
+            if (!"PKCS#8".equals(privateKey.getFormat())) {
+                throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+                    + ") offers unsupported encoding format: " + privateKey.getFormat());
+            }
+            byte[] encoded = privateKey.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+                    + ") does not provide encoded form");
+            }
+            try {
+                openSslPrivateKey = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
+            } catch (Exception e) {
+                throw new InvalidKeyException("Failed to decode PKCS#8 encoded private key", e);
+            }
+        }
+        mOpenSslPrivateKey = openSslPrivateKey;
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        // ECDH doesn't need an AlgorithmParameterSpec
+        if (params != null) {
+          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
+        }
+        engineInit(key, random);
+    }
+
+    private void checkCompleted() {
+        if (mResult == null) {
+            throw new IllegalStateException("Key agreement not completed");
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECGroupContext.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECGroupContext.java
new file mode 100644
index 0000000..173ce79
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECGroupContext.java
@@ -0,0 +1,171 @@
+/*
+ * 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.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.spec.ECField;
+import java.security.spec.ECFieldF2m;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.EllipticCurve;
+
+public final class OpenSSLECGroupContext {
+    private final long groupCtx;
+
+    public OpenSSLECGroupContext(long groupCtx) {
+        this.groupCtx = groupCtx;
+    }
+
+    public static OpenSSLECGroupContext getCurveByName(String curveName) {
+        // Workaround for OpenSSL not supporting SECG names for NIST P-192 and P-256
+        // (aka ANSI X9.62 prime192v1 and prime256v1) curve names.
+        if ("secp256r1".equals(curveName)) {
+            curveName = "prime256v1";
+        } else if ("secp192r1".equals(curveName)) {
+            curveName = "prime192v1";
+        }
+
+        final long ctx = NativeCrypto.EC_GROUP_new_by_curve_name(curveName);
+        if (ctx == 0) {
+            return null;
+        }
+
+        NativeCrypto.EC_GROUP_set_point_conversion_form(ctx,
+                NativeCrypto.POINT_CONVERSION_UNCOMPRESSED);
+        NativeCrypto.EC_GROUP_set_asn1_flag(ctx, NativeCrypto.OPENSSL_EC_NAMED_CURVE);
+
+        return new OpenSSLECGroupContext(ctx);
+    }
+
+    public static OpenSSLECGroupContext getInstance(int type, BigInteger p, BigInteger a,
+            BigInteger b, BigInteger x, BigInteger y, BigInteger n, BigInteger h) {
+        final long ctx = NativeCrypto.EC_GROUP_new_curve(type, p.toByteArray(), a.toByteArray(),
+                b.toByteArray());
+        if (ctx == 0) {
+            return null;
+        }
+
+        NativeCrypto.EC_GROUP_set_point_conversion_form(ctx,
+                NativeCrypto.POINT_CONVERSION_UNCOMPRESSED);
+
+        OpenSSLECGroupContext group = new OpenSSLECGroupContext(ctx);
+
+        OpenSSLECPointContext generator = new OpenSSLECPointContext(group,
+                NativeCrypto.EC_POINT_new(ctx));
+
+        NativeCrypto.EC_POINT_set_affine_coordinates(ctx, generator.getContext(),
+                x.toByteArray(), y.toByteArray());
+
+        NativeCrypto.EC_GROUP_set_generator(ctx, generator.getContext(), n.toByteArray(),
+                h.toByteArray());
+
+        return group;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (groupCtx != 0) {
+                NativeCrypto.EC_GROUP_clear_free(groupCtx);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof OpenSSLECGroupContext)) {
+            return false;
+        }
+
+        final OpenSSLECGroupContext other = (OpenSSLECGroupContext) o;
+        return NativeCrypto.EC_GROUP_cmp(groupCtx, other.groupCtx);
+    }
+
+    @Override
+    public int hashCode() {
+        // TODO Auto-generated method stub
+        return super.hashCode();
+    }
+
+    public long getContext() {
+        return groupCtx;
+    }
+
+    public static OpenSSLECGroupContext getInstance(ECParameterSpec params)
+            throws InvalidAlgorithmParameterException {
+        final String curveName = params.getCurveName();
+        if (curveName != null) {
+            return OpenSSLECGroupContext.getCurveByName(curveName);
+        }
+
+        final EllipticCurve curve = params.getCurve();
+        final ECField field = curve.getField();
+
+        final int type;
+        final BigInteger p;
+        if (field instanceof ECFieldFp) {
+            type = NativeCrypto.EC_CURVE_GFP;
+            p = ((ECFieldFp) field).getP();
+        } else if (field instanceof ECFieldF2m) {
+            type = NativeCrypto.EC_CURVE_GF2M;
+            p = ((ECFieldF2m) field).getReductionPolynomial();
+        } else {
+            throw new InvalidParameterException("unhandled field class "
+                    + field.getClass().getName());
+        }
+
+        final ECPoint generator = params.getGenerator();
+        return OpenSSLECGroupContext.getInstance(type, p, curve.getA(), curve.getB(),
+                generator.getAffineX(), generator.getAffineY(), params.getOrder(),
+                BigInteger.valueOf(params.getCofactor()));
+    }
+
+    public ECParameterSpec getECParameterSpec() {
+        final String curveName = NativeCrypto.EC_GROUP_get_curve_name(groupCtx);
+
+        final byte[][] curveParams = NativeCrypto.EC_GROUP_get_curve(groupCtx);
+        final BigInteger p = new BigInteger(curveParams[0]);
+        final BigInteger a = new BigInteger(curveParams[1]);
+        final BigInteger b = new BigInteger(curveParams[2]);
+
+        final ECField field;
+        final int type = NativeCrypto.get_EC_GROUP_type(groupCtx);
+        if (type == NativeCrypto.EC_CURVE_GFP) {
+            field = new ECFieldFp(p);
+        } else if (type == NativeCrypto.EC_CURVE_GF2M) {
+            field = new ECFieldF2m(p.bitLength() - 1, p);
+        } else {
+            throw new RuntimeException("unknown curve type " + type);
+        }
+
+        final EllipticCurve curve = new EllipticCurve(field, a, b);
+
+        final OpenSSLECPointContext generatorCtx = new OpenSSLECPointContext(this,
+                NativeCrypto.EC_GROUP_get_generator(groupCtx));
+        final ECPoint generator = generatorCtx.getECPoint();
+
+        final BigInteger order = new BigInteger(NativeCrypto.EC_GROUP_get_order(groupCtx));
+        final BigInteger cofactor = new BigInteger(NativeCrypto.EC_GROUP_get_cofactor(groupCtx));
+
+        return new ECParameterSpec(curve, generator, order, cofactor.intValue(), curveName);
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java
new file mode 100644
index 0000000..b5100c5
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 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.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+public class OpenSSLECKeyFactory extends KeyFactorySpi {
+
+    @Override
+    protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof ECPublicKeySpec) {
+            return new OpenSSLECPublicKey((ECPublicKeySpec) keySpec);
+        } else if (keySpec instanceof X509EncodedKeySpec) {
+            return OpenSSLKey.getPublicKey((X509EncodedKeySpec) keySpec, NativeCrypto.EVP_PKEY_EC);
+        }
+        throw new InvalidKeySpecException("Must use ECPublicKeySpec or X509EncodedKeySpec; was "
+                + keySpec.getClass().getName());
+    }
+
+    @Override
+    protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof ECPrivateKeySpec) {
+            return new OpenSSLECPrivateKey((ECPrivateKeySpec) keySpec);
+        } else if (keySpec instanceof PKCS8EncodedKeySpec) {
+            return OpenSSLKey.getPrivateKey((PKCS8EncodedKeySpec) keySpec,
+                    NativeCrypto.EVP_PKEY_EC);
+        }
+        throw new InvalidKeySpecException("Must use ECPrivateKeySpec or PKCS8EncodedKeySpec; was "
+                + keySpec.getClass().getName());
+    }
+
+    @Override
+    protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
+            throws InvalidKeySpecException {
+        if (key == null) {
+            throw new InvalidKeySpecException("key == null");
+        }
+
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (!"EC".equals(key.getAlgorithm())) {
+            throw new InvalidKeySpecException("Key must be an EC key");
+        }
+
+        if (key instanceof ECPublicKey && ECPublicKeySpec.class.isAssignableFrom(keySpec)) {
+            ECPublicKey ecKey = (ECPublicKey) key;
+            return (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams());
+        } else if (key instanceof PublicKey && ECPublicKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid X.509 encoding");
+            }
+            ECPublicKey ecKey = (ECPublicKey) engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            return (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams());
+        } else if (key instanceof ECPrivateKey
+                && ECPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            ECPrivateKey ecKey = (ECPrivateKey) key;
+            return (T) new ECPrivateKeySpec(ecKey.getS(), ecKey.getParams());
+        } else if (key instanceof PrivateKey && ECPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
+            }
+            ECPrivateKey ecKey =
+                    (ECPrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            return (T) new ECPrivateKeySpec(ecKey.getS(), ecKey.getParams());
+        } else if (key instanceof PrivateKey
+                && PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be PKCS#8; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            return (T) new PKCS8EncodedKeySpec(encoded);
+        } else if (key instanceof PublicKey && X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be X.509; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            return (T) new X509EncodedKeySpec(encoded);
+        } else {
+            throw new InvalidKeySpecException("Unsupported key type and key spec combination; key="
+                    + key.getClass().getName() + ", keySpec=" + keySpec.getName());
+        }
+    }
+
+    @Override
+    protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+
+        if (key instanceof ECPublicKey) {
+            ECPublicKey ecKey = (ECPublicKey) key;
+
+            ECPoint w = ecKey.getW();
+
+            ECParameterSpec params = ecKey.getParams();
+
+            try {
+                return engineGeneratePublic(new ECPublicKeySpec(w, params));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if (key instanceof ECPrivateKey) {
+            ECPrivateKey ecKey = (ECPrivateKey) key;
+
+            BigInteger s = ecKey.getS();
+
+            ECParameterSpec params = ecKey.getParams();
+
+            try {
+                return engineGeneratePrivate(new ECPrivateKeySpec(s, params));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ("PKCS#8".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ("X.509".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else {
+            throw new InvalidKeyException("Key must be EC public or private key; was "
+                    + key.getClass().getName());
+        }
+    }
+
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyPairGenerator.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyPairGenerator.java
new file mode 100644
index 0000000..bea46e6
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyPairGenerator.java
@@ -0,0 +1,110 @@
+/*
+ * 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.InvalidAlgorithmParameterException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class OpenSSLECKeyPairGenerator extends KeyPairGenerator {
+    private static final String ALGORITHM = "EC";
+
+    private static final int DEFAULT_KEY_SIZE = 192;
+
+    private static final Map<Integer, String> SIZE_TO_CURVE_NAME = new HashMap<Integer, String>();
+
+    static {
+        /* NIST curves */
+        SIZE_TO_CURVE_NAME.put(192, "prime192v1");
+        SIZE_TO_CURVE_NAME.put(224, "secp224r1");
+        SIZE_TO_CURVE_NAME.put(256, "prime256v1");
+        SIZE_TO_CURVE_NAME.put(384, "secp384r1");
+        SIZE_TO_CURVE_NAME.put(521, "secp521r1");
+    }
+
+    private OpenSSLECGroupContext group;
+
+    public OpenSSLECKeyPairGenerator() {
+        super(ALGORITHM);
+    }
+
+    @Override
+    public KeyPair generateKeyPair() {
+        if (group == null) {
+            final String curveName = SIZE_TO_CURVE_NAME.get(DEFAULT_KEY_SIZE);
+            group = OpenSSLECGroupContext.getCurveByName(curveName);
+        }
+
+        final OpenSSLKey key = new OpenSSLKey(NativeCrypto.EC_KEY_generate_key(group.getContext()));
+        return new KeyPair(new OpenSSLECPublicKey(group, key), new OpenSSLECPrivateKey(group, key));
+    }
+
+    @Override
+    public void initialize(int keysize, SecureRandom random) {
+        final String name = SIZE_TO_CURVE_NAME.get(keysize);
+        if (name == null) {
+            throw new InvalidParameterException("unknown key size " + keysize);
+        }
+
+        /*
+         * Store the group in a temporary variable until we know this is a valid
+         * group.
+         */
+        final OpenSSLECGroupContext possibleGroup = OpenSSLECGroupContext.getCurveByName(name);
+        if (possibleGroup == null) {
+            throw new InvalidParameterException("unknown curve " + name);
+        }
+
+        group = possibleGroup;
+    }
+
+    @Override
+    public void initialize(AlgorithmParameterSpec param, SecureRandom random)
+            throws InvalidAlgorithmParameterException {
+        if (param instanceof ECParameterSpec) {
+            ECParameterSpec ecParam = (ECParameterSpec) param;
+
+            group = OpenSSLECGroupContext.getInstance(ecParam);
+        } else if (param instanceof ECGenParameterSpec) {
+            ECGenParameterSpec ecParam = (ECGenParameterSpec) param;
+
+            final String curveName = ecParam.getName();
+
+            /*
+             * Store the group in a temporary variable until we know this is a
+             * valid group.
+             */
+            final OpenSSLECGroupContext possibleGroup = OpenSSLECGroupContext
+                    .getCurveByName(curveName);
+            if (possibleGroup == null) {
+                throw new InvalidAlgorithmParameterException("unknown curve name: " + curveName);
+            }
+
+            group = possibleGroup;
+        } else {
+            throw new InvalidAlgorithmParameterException(
+                    "parameter must be ECParameterSpec or ECGenParameterSpec");
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPointContext.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPointContext.java
new file mode 100644
index 0000000..7c5c067
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPointContext.java
@@ -0,0 +1,83 @@
+/*
+ * 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.spec.ECPoint;
+
+final class OpenSSLECPointContext {
+    private final OpenSSLECGroupContext group;
+    private final long pointCtx;
+
+    OpenSSLECPointContext(OpenSSLECGroupContext group, long pointCtx) {
+        this.group = group;
+        this.pointCtx = pointCtx;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (pointCtx != 0) {
+                NativeCrypto.EC_POINT_clear_free(pointCtx);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof OpenSSLECPointContext)) {
+            return false;
+        }
+
+        final OpenSSLECPointContext other = (OpenSSLECPointContext) o;
+        if (!NativeCrypto.EC_GROUP_cmp(group.getContext(), other.group.getContext())) {
+            return false;
+        }
+
+        return NativeCrypto.EC_POINT_cmp(group.getContext(), pointCtx, other.pointCtx);
+    }
+
+    public ECPoint getECPoint() {
+        final byte[][] generatorCoords = NativeCrypto.EC_POINT_get_affine_coordinates(
+                group.getContext(), pointCtx);
+        final BigInteger x = new BigInteger(generatorCoords[0]);
+        final BigInteger y = new BigInteger(generatorCoords[1]);
+        return new ECPoint(x, y);
+    }
+
+    @Override
+    public int hashCode() {
+        // TODO Auto-generated method stub
+        return super.hashCode();
+    }
+
+    public long getContext() {
+        return pointCtx;
+    }
+
+    public static OpenSSLECPointContext getInstance(int curveType, OpenSSLECGroupContext group,
+            ECPoint javaPoint) {
+        OpenSSLECPointContext point = new OpenSSLECPointContext(group,
+                NativeCrypto.EC_POINT_new(group.getContext()));
+        NativeCrypto.EC_POINT_set_affine_coordinates(group.getContext(),
+                point.getContext(), javaPoint.getAffineX().toByteArray(),
+                javaPoint.getAffineY().toByteArray());
+        return point;
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
new file mode 100644
index 0000000..b0be998
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
@@ -0,0 +1,171 @@
+/*
+ * 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.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+public final class OpenSSLECPrivateKey implements ECPrivateKey, OpenSSLKeyHolder {
+    private static final long serialVersionUID = -4036633595001083922L;
+
+    private static final String ALGORITHM = "EC";
+
+    protected transient OpenSSLKey key;
+
+    protected transient OpenSSLECGroupContext group;
+
+    public OpenSSLECPrivateKey(OpenSSLECGroupContext group, OpenSSLKey key) {
+        this.group = group;
+        this.key = key;
+    }
+
+    public OpenSSLECPrivateKey(OpenSSLKey key) {
+        final long origGroup = NativeCrypto.EC_KEY_get0_group(key.getPkeyContext());
+        this.group = new OpenSSLECGroupContext(NativeCrypto.EC_GROUP_dup(origGroup));
+        this.key = key;
+    }
+
+    public OpenSSLECPrivateKey(ECPrivateKeySpec ecKeySpec) throws InvalidKeySpecException {
+        try {
+            group = OpenSSLECGroupContext.getInstance(ecKeySpec.getParams());
+            final BigInteger privKey = ecKeySpec.getS();
+            key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_EC_KEY(group.getContext(), 0,
+                    privKey.toByteArray()));
+        } catch (Exception e) {
+            throw new InvalidKeySpecException(e);
+        }
+    }
+
+    public static OpenSSLKey getInstance(ECPrivateKey ecPrivateKey) throws InvalidKeyException {
+        try {
+            OpenSSLECGroupContext group = OpenSSLECGroupContext.getInstance(ecPrivateKey
+                    .getParams());
+            final BigInteger privKey = ecPrivateKey.getS();
+            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_EC_KEY(group.getContext(), 0,
+                    privKey.toByteArray()));
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return ALGORITHM;
+    }
+
+    @Override
+    public String getFormat() {
+        return "PKCS#8";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext());
+    }
+
+    @Override
+    public ECParameterSpec getParams() {
+        return group.getECParameterSpec();
+    }
+
+    @Override
+    public BigInteger getS() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private key value S cannot be extracted");
+        }
+
+        return getPrivateKey();
+    }
+
+    private BigInteger getPrivateKey() {
+        return new BigInteger(NativeCrypto.EC_KEY_get_private_key(key.getPkeyContext()));
+    }
+
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
+        return key;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (o instanceof OpenSSLECPrivateKey) {
+            OpenSSLECPrivateKey other = (OpenSSLECPrivateKey) o;
+            return key.equals(other.key);
+        }
+
+        if (!(o instanceof ECPrivateKey)) {
+            return false;
+        }
+
+        final ECPrivateKey other = (ECPrivateKey) o;
+        if (!getPrivateKey().equals(other.getS())) {
+            return false;
+        }
+
+        final ECParameterSpec spec = getParams();
+        final ECParameterSpec otherSpec = other.getParams();
+
+        return spec.getCurve().equals(otherSpec.getCurve())
+                && spec.getGenerator().equals(otherSpec.getGenerator())
+                && spec.getOrder().equals(otherSpec.getOrder())
+                && spec.getCofactor() == otherSpec.getCofactor();
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext()));
+    }
+
+    @Override
+    public String toString() {
+        return NativeCrypto.EVP_PKEY_print_private(key.getPkeyContext());
+    }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        byte[] encoded = (byte[]) stream.readObject();
+
+        key = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
+
+        final long origGroup = NativeCrypto.EC_KEY_get0_group(key.getPkeyContext());
+        group = new OpenSSLECGroupContext(NativeCrypto.EC_GROUP_dup(origGroup));
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (key.isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        stream.defaultWriteObject();
+        stream.writeObject(getEncoded());
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPublicKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPublicKey.java
new file mode 100644
index 0000000..b591336
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPublicKey.java
@@ -0,0 +1,173 @@
+/*
+ * 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.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+public final class OpenSSLECPublicKey implements ECPublicKey, OpenSSLKeyHolder {
+    private static final long serialVersionUID = 3215842926808298020L;
+
+    private static final String ALGORITHM = "EC";
+
+    protected transient OpenSSLKey key;
+
+    protected transient OpenSSLECGroupContext group;
+
+    public OpenSSLECPublicKey(OpenSSLECGroupContext group, OpenSSLKey key) {
+        this.group = group;
+        this.key = key;
+    }
+
+    public OpenSSLECPublicKey(OpenSSLKey key) {
+        final long origGroup = NativeCrypto.EC_KEY_get0_group(key.getPkeyContext());
+        this.group = new OpenSSLECGroupContext(NativeCrypto.EC_GROUP_dup(origGroup));
+        this.key = key;
+    }
+
+    public OpenSSLECPublicKey(ECPublicKeySpec ecKeySpec) throws InvalidKeySpecException {
+        try {
+            group = OpenSSLECGroupContext.getInstance(ecKeySpec.getParams());
+            OpenSSLECPointContext pubKey = OpenSSLECPointContext.getInstance(
+                    NativeCrypto.get_EC_GROUP_type(group.getContext()), group, ecKeySpec.getW());
+            key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_EC_KEY(group.getContext(),
+                    pubKey.getContext(), null));
+        } catch (Exception e) {
+            throw new InvalidKeySpecException(e);
+        }
+    }
+
+    public static OpenSSLKey getInstance(ECPublicKey ecPublicKey) throws InvalidKeyException {
+        try {
+            OpenSSLECGroupContext group = OpenSSLECGroupContext
+                    .getInstance(ecPublicKey.getParams());
+            OpenSSLECPointContext pubKey = OpenSSLECPointContext.getInstance(
+                    NativeCrypto.get_EC_GROUP_type(group.getContext()), group, ecPublicKey.getW());
+            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_EC_KEY(group.getContext(),
+                    pubKey.getContext(), null));
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return ALGORITHM;
+    }
+
+    @Override
+    public String getFormat() {
+        return "X.509";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        return NativeCrypto.i2d_PUBKEY(key.getPkeyContext());
+    }
+
+    @Override
+    public ECParameterSpec getParams() {
+        return group.getECParameterSpec();
+    }
+
+    private ECPoint getPublicKey() {
+        final OpenSSLECPointContext pubKey = new OpenSSLECPointContext(group,
+                NativeCrypto.EC_KEY_get_public_key(key.getPkeyContext()));
+
+        return pubKey.getECPoint();
+    }
+
+    @Override
+    public ECPoint getW() {
+        return getPublicKey();
+    }
+
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
+        return key;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (o instanceof OpenSSLECPrivateKey) {
+            OpenSSLECPrivateKey other = (OpenSSLECPrivateKey) o;
+            return key.equals(other.key);
+        }
+
+        if (!(o instanceof ECPublicKey)) {
+            return false;
+        }
+
+        final ECPublicKey other = (ECPublicKey) o;
+        if (!getPublicKey().equals(other.getW())) {
+            return false;
+        }
+
+        final ECParameterSpec spec = getParams();
+        final ECParameterSpec otherSpec = other.getParams();
+
+        return spec.getCurve().equals(otherSpec.getCurve())
+                && spec.getGenerator().equals(otherSpec.getGenerator())
+                && spec.getOrder().equals(otherSpec.getOrder())
+                && spec.getCofactor() == otherSpec.getCofactor();
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(NativeCrypto.i2d_PUBKEY(key.getPkeyContext()));
+    }
+
+    @Override
+    public String toString() {
+        return NativeCrypto.EVP_PKEY_print_public(key.getPkeyContext());
+    }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        byte[] encoded = (byte[]) stream.readObject();
+
+        key = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded));
+
+        final long origGroup = NativeCrypto.EC_KEY_get0_group(key.getPkeyContext());
+        group = new OpenSSLECGroupContext(NativeCrypto.EC_GROUP_dup(origGroup));
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (key.isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        stream.defaultWriteObject();
+        stream.writeObject(getEncoded());
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLEngine.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLEngine.java
index d01dc62..4e02aad 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLEngine.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLEngine.java
@@ -17,8 +17,11 @@
 package org.apache.harmony.xnet.provider.jsse;
 
 import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 
+import javax.crypto.SecretKey;
+
 public class OpenSSLEngine {
     static {
         NativeCrypto.ENGINE_load_dynamic();
@@ -27,14 +30,14 @@
     private static final Object mLoadingLock = new Object();
 
     /** The ENGINE's native handle. */
-    private final int ctx;
+    private final long ctx;
 
     public static OpenSSLEngine getInstance(String engine) throws IllegalArgumentException {
         if (engine == null) {
             throw new NullPointerException("engine == null");
         }
 
-        final int engineCtx;
+        final long engineCtx;
         synchronized (mLoadingLock) {
             engineCtx = NativeCrypto.ENGINE_by_id(engine);
             if (engineCtx == 0) {
@@ -47,7 +50,7 @@
         return new OpenSSLEngine(engineCtx);
     }
 
-    private OpenSSLEngine(int engineCtx) {
+    private OpenSSLEngine(long engineCtx) {
         ctx = engineCtx;
 
         if (NativeCrypto.ENGINE_init(engineCtx) == 0) {
@@ -61,23 +64,38 @@
             throw new NullPointerException("id == null");
         }
 
-        final int keyRef = NativeCrypto.ENGINE_load_private_key(ctx, id);
+        final long keyRef = NativeCrypto.ENGINE_load_private_key(ctx, id);
         if (keyRef == 0) {
             return null;
         }
 
-        final int keyType = NativeCrypto.EVP_PKEY_type(keyRef);
-        switch (keyType) {
-            case NativeCrypto.EVP_PKEY_RSA:
-                return OpenSSLRSAPrivateKey.getInstance(new OpenSSLKey(keyRef, this, id));
-            case NativeCrypto.EVP_PKEY_DSA:
-                return new OpenSSLDSAPrivateKey(new OpenSSLKey(keyRef, this, id));
-            default:
-                throw new InvalidKeyException("Unknown key type: " + keyType);
+        OpenSSLKey pkey = new OpenSSLKey(keyRef, this, id);
+        try {
+            return pkey.getPrivateKey();
+        } catch (NoSuchAlgorithmException e) {
+            throw new InvalidKeyException(e);
         }
     }
 
-    int getEngineContext() {
+    public SecretKey getSecretKeyById(String id, String algorithm) throws InvalidKeyException {
+        if (id == null) {
+            throw new NullPointerException("id == null");
+        }
+
+        final long keyRef = NativeCrypto.ENGINE_load_private_key(ctx, id);
+        if (keyRef == 0) {
+            return null;
+        }
+
+        OpenSSLKey pkey = new OpenSSLKey(keyRef, this, id);
+        try {
+            return pkey.getSecretKey(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    long getEngineContext() {
         return ctx;
     }
 
@@ -103,11 +121,20 @@
 
         OpenSSLEngine other = (OpenSSLEngine) o;
 
-        return other.getEngineContext() == ctx;
+        if (other.getEngineContext() == ctx) {
+            return true;
+        }
+
+        final String id = NativeCrypto.ENGINE_get_id(ctx);
+        if (id == null) {
+            return false;
+        }
+
+        return id.equals(NativeCrypto.ENGINE_get_id(other.getEngineContext()));
     }
 
     @Override
     public int hashCode() {
-        return ctx;
+      return (int) ctx;
     }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKey.java
index b8b9f69..921a4b6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKey.java
@@ -16,26 +16,40 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
-class OpenSSLKey {
-    private final int ctx;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.SecretKey;
+
+public class OpenSSLKey {
+    private final long ctx;
 
     private final OpenSSLEngine engine;
 
     private final String alias;
 
-    OpenSSLKey(int ctx) {
+    public OpenSSLKey(long ctx) {
         this.ctx = ctx;
         engine = null;
         alias = null;
     }
 
-    OpenSSLKey(int ctx, OpenSSLEngine engine, String alias) {
+    public OpenSSLKey(long ctx, OpenSSLEngine engine, String alias) {
         this.ctx = ctx;
         this.engine = engine;
         this.alias = alias;
     }
 
-    int getPkeyContext() {
+    /**
+     * Returns the raw pointer to the EVP_PKEY context for use in JNI calls. The
+     * life cycle of this native pointer is managed by the {@code OpenSSLKey}
+     * instance and must not be destroyed or freed by users of this API.
+     */
+    public long getPkeyContext() {
         return ctx;
     }
 
@@ -47,10 +61,90 @@
         return engine != null;
     }
 
-    String getAlias() {
+    public String getAlias() {
         return alias;
     }
 
+    public PublicKey getPublicKey() throws NoSuchAlgorithmException {
+        switch (NativeCrypto.EVP_PKEY_type(ctx)) {
+            case NativeCrypto.EVP_PKEY_RSA:
+                return new OpenSSLRSAPublicKey(this);
+            case NativeCrypto.EVP_PKEY_DSA:
+                return new OpenSSLDSAPublicKey(this);
+            case NativeCrypto.EVP_PKEY_EC:
+                return new OpenSSLECPublicKey(this);
+            default:
+                throw new NoSuchAlgorithmException("unknown PKEY type");
+        }
+    }
+
+    static PublicKey getPublicKey(X509EncodedKeySpec keySpec, int type)
+            throws InvalidKeySpecException {
+        X509EncodedKeySpec x509KeySpec = (X509EncodedKeySpec) keySpec;
+
+        final OpenSSLKey key;
+        try {
+            key = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(x509KeySpec.getEncoded()));
+        } catch (Exception e) {
+            throw new InvalidKeySpecException(e);
+        }
+
+        if (NativeCrypto.EVP_PKEY_type(key.getPkeyContext()) != type) {
+            throw new InvalidKeySpecException("Unexpected key type");
+        }
+
+        try {
+            return key.getPublicKey();
+        } catch (NoSuchAlgorithmException e) {
+            throw new InvalidKeySpecException(e);
+        }
+    }
+
+    public PrivateKey getPrivateKey() throws NoSuchAlgorithmException {
+        switch (NativeCrypto.EVP_PKEY_type(ctx)) {
+            case NativeCrypto.EVP_PKEY_RSA:
+                return new OpenSSLRSAPrivateKey(this);
+            case NativeCrypto.EVP_PKEY_DSA:
+                return new OpenSSLDSAPrivateKey(this);
+            case NativeCrypto.EVP_PKEY_EC:
+                return new OpenSSLECPrivateKey(this);
+            default:
+                throw new NoSuchAlgorithmException("unknown PKEY type");
+        }
+    }
+
+    static PrivateKey getPrivateKey(PKCS8EncodedKeySpec keySpec, int type)
+            throws InvalidKeySpecException {
+        PKCS8EncodedKeySpec pkcs8KeySpec = (PKCS8EncodedKeySpec) keySpec;
+
+        final OpenSSLKey key;
+        try {
+            key = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(pkcs8KeySpec.getEncoded()));
+        } catch (Exception e) {
+            throw new InvalidKeySpecException(e);
+        }
+
+        if (NativeCrypto.EVP_PKEY_type(key.getPkeyContext()) != type) {
+            throw new InvalidKeySpecException("Unexpected key type");
+        }
+
+        try {
+            return key.getPrivateKey();
+        } catch (NoSuchAlgorithmException e) {
+            throw new InvalidKeySpecException(e);
+        }
+    }
+
+    public SecretKey getSecretKey(String algorithm) throws NoSuchAlgorithmException {
+        switch (NativeCrypto.EVP_PKEY_type(ctx)) {
+            case NativeCrypto.EVP_PKEY_HMAC:
+            case NativeCrypto.EVP_PKEY_CMAC:
+                return new OpenSSLSecretKey(algorithm, this);
+            default:
+                throw new NoSuchAlgorithmException("unknown PKEY type");
+        }
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -73,22 +167,35 @@
         }
 
         OpenSSLKey other = (OpenSSLKey) o;
-        if (ctx != other.getPkeyContext()) {
-            return false;
+        if (ctx == other.getPkeyContext()) {
+            return true;
         }
 
+        /*
+         * ENGINE-based keys must be checked in a special way.
+         */
         if (engine == null) {
-            return other.getEngine() == null;
+            if (other.getEngine() != null) {
+                return false;
+            }
+        } else if (!engine.equals(other.getEngine())) {
+            return false;
         } else {
-            return engine.equals(other.getEngine());
+            if (alias != null) {
+                return alias.equals(other.getAlias());
+            } else if (other.getAlias() != null) {
+                return false;
+            }
         }
+
+        return NativeCrypto.EVP_PKEY_cmp(ctx, other.getPkeyContext()) == 1;
     }
 
     @Override
     public int hashCode() {
         int hash = 1;
-        hash = hash * 17 + ctx;
-        hash = hash * 31 + (engine == null ? 0 : engine.getEngineContext());
+        hash = hash * 17 + (int) ctx;
+        hash = hash * 31 + (int) (engine == null ? 0 : engine.getEngineContext());
         return hash;
     }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKeyHolder.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKeyHolder.java
new file mode 100644
index 0000000..548e9c5
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLKeyHolder.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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 interface OpenSSLKeyHolder {
+    public OpenSSLKey getOpenSSLKey();
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMac.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMac.java
new file mode 100644
index 0000000..697af5a
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMac.java
@@ -0,0 +1,167 @@
+/*
+ * 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.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.MacSpi;
+import javax.crypto.SecretKey;
+
+public abstract class OpenSSLMac extends MacSpi {
+    private final OpenSSLDigestContext ctx = new OpenSSLDigestContext(
+            NativeCrypto.EVP_MD_CTX_create());
+
+    /**
+     * Holds the EVP_MD for the hashing algorithm, e.g.
+     * EVP_get_digestbyname("sha1");
+     */
+    private final long evp_md;
+
+    /**
+     * The key type of the secret key.
+     */
+    private final int evp_pkey_type;
+
+    /**
+     * The secret key used in this keyed MAC.
+     */
+    private OpenSSLKey macKey;
+
+    /**
+     * Holds the output size of the message digest.
+     */
+    private final int size;
+
+    /**
+     * Holds a dummy buffer for writing single bytes to the digest.
+     */
+    private final byte[] singleByte = new byte[1];
+
+    private OpenSSLMac(long evp_md, int size, int evp_pkey_type) {
+        this.evp_md = evp_md;
+        this.size = size;
+        this.evp_pkey_type = evp_pkey_type;
+    }
+
+    @Override
+    protected int engineGetMacLength() {
+        return size;
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        if (!(key instanceof SecretKey)) {
+            throw new InvalidKeyException("key must be a SecretKey");
+        }
+
+        if (params != null) {
+            throw new InvalidAlgorithmParameterException("unknown parameter type");
+        }
+
+        if (key instanceof OpenSSLKeyHolder) {
+            macKey = ((OpenSSLKeyHolder) key).getOpenSSLKey();
+        } else {
+            final byte[] keyBytes = key.getEncoded();
+            if (keyBytes == null) {
+                throw new InvalidKeyException("key cannot be encoded");
+            }
+
+            macKey = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_mac_key(evp_pkey_type, keyBytes));
+        }
+
+        NativeCrypto.EVP_MD_CTX_init(ctx.getContext());
+
+        reset();
+    }
+
+    private void reset() {
+        NativeCrypto.EVP_DigestSignInit(ctx.getContext(), evp_md, macKey.getPkeyContext());
+    }
+
+    @Override
+    protected void engineUpdate(byte input) {
+        singleByte[0] = input;
+        engineUpdate(singleByte, 0, 1);
+    }
+
+    @Override
+    protected void engineUpdate(byte[] input, int offset, int len) {
+        NativeCrypto.EVP_DigestUpdate(ctx.getContext(), input, offset, len);
+    }
+
+    @Override
+    protected byte[] engineDoFinal() {
+        final byte[] output = NativeCrypto.EVP_DigestSignFinal(ctx.getContext());
+        reset();
+        return output;
+    }
+
+    @Override
+    protected void engineReset() {
+        reset();
+    }
+
+    public static class HmacMD5 extends OpenSSLMac {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("md5");
+        private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
+
+        public HmacMD5() {
+            super(EVP_MD, SIZE, NativeCrypto.EVP_PKEY_HMAC);
+        }
+    }
+
+    public static class HmacSHA1 extends OpenSSLMac {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha1");
+        private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
+
+        public HmacSHA1() {
+            super(EVP_MD, SIZE, NativeCrypto.EVP_PKEY_HMAC);
+        }
+    }
+
+    public static class HmacSHA256 extends OpenSSLMac {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha256");
+        private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
+
+        public HmacSHA256() throws NoSuchAlgorithmException {
+            super(EVP_MD, SIZE, NativeCrypto.EVP_PKEY_HMAC);
+        }
+    }
+
+    public static class HmacSHA384 extends OpenSSLMac {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha384");
+        private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
+
+        public HmacSHA384() throws NoSuchAlgorithmException {
+            super(EVP_MD, SIZE, NativeCrypto.EVP_PKEY_HMAC);
+        }
+    }
+
+    public static class HmacSHA512 extends OpenSSLMac {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha512");
+        private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
+
+        public HmacSHA512() {
+            super(EVP_MD, SIZE, NativeCrypto.EVP_PKEY_HMAC);
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java
index ba431bd..9d59a7c 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java
@@ -27,12 +27,12 @@
     /**
      * Holds a pointer to the native message digest context.
      */
-    private int ctx;
+    private long ctx;
 
     /**
      * Holds the EVP_MD for the hashing algorithm, e.g. EVP_get_digestbyname("sha1");
      */
-    private final int evp_md;
+    private final long evp_md;
 
     /**
      * Holds the output size of the message digest.
@@ -48,7 +48,7 @@
      * Creates a new OpenSSLMessageDigest instance for the given algorithm
      * name.
      */
-    private OpenSSLMessageDigestJDK(String algorithm, int evp_md, int size)
+    private OpenSSLMessageDigestJDK(String algorithm, long evp_md, int size)
             throws NoSuchAlgorithmException {
         super(algorithm);
         this.evp_md = evp_md;
@@ -90,7 +90,7 @@
         return d;
     }
 
-    private int getCtx() {
+    private long getCtx() {
         if (ctx == 0) {
             ctx = NativeCrypto.EVP_DigestInit(evp_md);
         }
@@ -113,7 +113,7 @@
     }
 
     public static class MD5 extends OpenSSLMessageDigestJDK {
-        private static final int EVP_MD = NativeCrypto.EVP_get_digestbyname("md5");
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("md5");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public MD5() throws NoSuchAlgorithmException {
             super("MD5",EVP_MD, SIZE);
@@ -121,7 +121,7 @@
     }
 
     public static class SHA1 extends OpenSSLMessageDigestJDK {
-        private static final int EVP_MD = NativeCrypto.EVP_get_digestbyname("sha1");
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha1");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA1() throws NoSuchAlgorithmException {
             super("SHA-1", EVP_MD, SIZE);
@@ -129,7 +129,7 @@
     }
 
     public static class SHA256 extends OpenSSLMessageDigestJDK {
-        private static final int EVP_MD = NativeCrypto.EVP_get_digestbyname("sha256");
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha256");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA256() throws NoSuchAlgorithmException {
             super("SHA-256", EVP_MD, SIZE);
@@ -137,7 +137,7 @@
     }
 
     public static class SHA384 extends OpenSSLMessageDigestJDK {
-        private static final int EVP_MD = NativeCrypto.EVP_get_digestbyname("sha384");
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha384");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA384() throws NoSuchAlgorithmException {
             super("SHA-384", EVP_MD, SIZE);
@@ -145,7 +145,7 @@
     }
 
     public static class SHA512 extends OpenSSLMessageDigestJDK {
-        private static final int EVP_MD = NativeCrypto.EVP_get_digestbyname("sha512");
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha512");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA512() throws NoSuchAlgorithmException {
             super("SHA-512", EVP_MD, SIZE);
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 d4aa57f..59e20d6 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
@@ -18,13 +18,23 @@
 
 import java.security.Provider;
 
+/**
+ * Provider that goes through OpenSSL for operations.
+ * <p>
+ * Every algorithm should have its IANA assigned OID as an alias. See the following URLs for each type:
+ * <ul>
+ * <li><a href="http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml">Hash functions</a></li>
+ * <li><a href="http://www.iana.org/assignments/dssc/dssc.xml">Signature algorithms</a></li>
+ * <li><a href="http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html">NIST cryptographic algorithms</a></li>
+ * </ul>
+ */
 public final class OpenSSLProvider extends Provider {
     public static final String PROVIDER_NAME = "AndroidOpenSSL";
 
     public OpenSSLProvider() {
         super(PROVIDER_NAME, 1.0, "Android's OpenSSL-backed security provider");
 
-        // SSL Contexts
+        /* === SSL Contexts === */
         put("SSLContext.SSL", OpenSSLContextImpl.class.getName());
         put("SSLContext.SSLv3", OpenSSLContextImpl.class.getName());
         put("SSLContext.TLS", OpenSSLContextImpl.class.getName());
@@ -33,7 +43,7 @@
         put("SSLContext.TLSv1.2", OpenSSLContextImpl.class.getName());
         put("SSLContext.Default", DefaultSSLContextImpl.class.getName());
 
-        // Message Digests
+        /* === Message Digests === */
         put("MessageDigest.SHA-1",
             "org.apache.harmony.xnet.provider.jsse.OpenSSLMessageDigestJDK$SHA1");
         put("Alg.Alias.MessageDigest.SHA1", "SHA-1");
@@ -55,24 +65,31 @@
         put("Alg.Alias.MessageDigest.SHA512", "SHA-512");
         put("Alg.Alias.MessageDigest.2.16.840.1.101.3.4.2.3", "SHA-512");
 
+        // iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) md5(5)
         put("MessageDigest.MD5",
             "org.apache.harmony.xnet.provider.jsse.OpenSSLMessageDigestJDK$MD5");
         put("Alg.Alias.MessageDigest.1.2.840.113549.2.5", "MD5");
 
-        // KeyPairGenerators
+        /* == KeyPairGenerators == */
         put("KeyPairGenerator.RSA", OpenSSLRSAKeyPairGenerator.class.getName());
         put("Alg.Alias.KeyPairGenerator.1.2.840.113549.1.1.1", "RSA");
 
         put("KeyPairGenerator.DSA", OpenSSLDSAKeyPairGenerator.class.getName());
 
-        // KeyFactory
+        put("KeyPairGenerator.EC", OpenSSLECKeyPairGenerator.class.getName());
 
+        /* == KeyFactory == */
         put("KeyFactory.RSA", OpenSSLRSAKeyFactory.class.getName());
         put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA");
 
-        // put("KeyFactory.DSA", OpenSSLDSAKeyFactory.class.getName());
+        put("KeyFactory.DSA", OpenSSLDSAKeyFactory.class.getName());
 
-        // Signatures
+        put("KeyFactory.EC", OpenSSLECKeyFactory.class.getName());
+
+        /* == KeyAgreement == */
+        put("KeyAgreement.ECDH", OpenSSLECDHKeyAgreement.class.getName());
+
+        /* == Signatures == */
         put("Signature.MD5WithRSA", OpenSSLSignature.MD5RSA.class.getName());
         put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5WithRSA");
         put("Alg.Alias.Signature.MD5/RSA", "MD5WithRSA");
@@ -80,7 +97,7 @@
         put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5WithRSA");
 
         put("Signature.SHA1WithRSA", OpenSSLSignature.SHA1RSA.class.getName());
-        put("Alg.Alias.Signature.SHA1WithRSA", "SHA1WithRSA");
+        put("Alg.Alias.Signature.SHA1WithRSAEncryption", "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");
@@ -91,14 +108,22 @@
         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("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1",
+                "SHA256WithRSA");
+        put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11",
+                "SHA256WithRSA");
 
         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("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1",
+                "SHA384WithRSA");
 
         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("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1",
+                "SHA512WithRSA");
 
         put("Signature.SHA1withDSA", OpenSSLSignature.SHA1DSA.class.getName());
         put("Alg.Alias.Signature.SHA/DSA", "SHA1withDSA");
@@ -110,7 +135,30 @@
 
         put("Signature.NONEwithRSA", OpenSSLSignatureRawRSA.class.getName());
 
-        // SecureRandom
+        put("Signature.ECDSA", OpenSSLSignature.SHA1ECDSA.class.getName());
+        put("Alg.Alias.Signature.SHA1withECDSA", "ECDSA");
+        put("Alg.Alias.Signature.ECDSAwithSHA1", "ECDSA");
+        // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1)
+        put("Alg.Alias.Signature.1.2.840.10045.4.1", "ECDSA");
+        put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "ECDSA");
+
+        // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
+        put("Signature.SHA256withECDSA", OpenSSLSignature.SHA256ECDSA.class.getName());
+        // ecdsa-with-SHA256(2)
+        put("Alg.Alias.Signature.1.2.840.10045.4.3.2", "SHA256withECDSA");
+        put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.10045.2.1", "SHA256withECDSA");
+
+        put("Signature.SHA384withECDSA", OpenSSLSignature.SHA384ECDSA.class.getName());
+        // ecdsa-with-SHA384(3)
+        put("Alg.Alias.Signature.1.2.840.10045.4.3.3", "SHA384withECDSA");
+        put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.10045.2.1", "SHA384withECDSA");
+
+        put("Signature.SHA512withECDSA", OpenSSLSignature.SHA512ECDSA.class.getName());
+        // ecdsa-with-SHA512(4)
+        put("Alg.Alias.Signature.1.2.840.10045.4.3.4", "SHA512withECDSA");
+        put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.10045.2.1", "SHA512withECDSA");
+
+        /* === SecureRandom === */
         /*
          * We have to specify SHA1PRNG because various documentation mentions
          * that algorithm by name instead of just recommending calling
@@ -119,10 +167,72 @@
         put("SecureRandom.SHA1PRNG", OpenSSLRandom.class.getName());
         put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
 
-        // Cipher
+        /* === Cipher === */
         put("Cipher.RSA/ECB/NoPadding", OpenSSLCipherRSA.Raw.class.getName());
         put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
         put("Cipher.RSA/ECB/PKCS1Padding", OpenSSLCipherRSA.PKCS1.class.getName());
         put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding");
+
+        /*
+         * OpenSSL only supports a subset of modes, so we'll name them
+         * explicitly here.
+         */
+        put("Cipher.AES/ECB/NoPadding", OpenSSLCipher.AES.ECB.NoPadding.class.getName());
+        put("Cipher.AES/ECB/PKCS5Padding", OpenSSLCipher.AES.ECB.PKCS5Padding.class.getName());
+        put("Cipher.AES/CBC/NoPadding", OpenSSLCipher.AES.CBC.NoPadding.class.getName());
+        put("Cipher.AES/CBC/PKCS5Padding", OpenSSLCipher.AES.CBC.PKCS5Padding.class.getName());
+        put("Cipher.AES/CFB/NoPadding", OpenSSLCipher.AES.CFB.NoPadding.class.getName());
+        put("Cipher.AES/CFB/PKCS5Padding", OpenSSLCipher.AES.CFB.PKCS5Padding.class.getName());
+        put("Cipher.AES/CTR/NoPadding", OpenSSLCipher.AES.CTR.NoPadding.class.getName());
+        put("Cipher.AES/CTR/PKCS5Padding", OpenSSLCipher.AES.CTR.PKCS5Padding.class.getName());
+        put("Cipher.AES/OFB/NoPadding", OpenSSLCipher.AES.OFB.NoPadding.class.getName());
+        put("Cipher.AES/OFB/PKCS5Padding", OpenSSLCipher.AES.OFB.PKCS5Padding.class.getName());
+
+        put("Cipher.DESEDE/CBC/NoPadding", OpenSSLCipher.DESEDE.CBC.NoPadding.class.getName());
+        put("Cipher.DESEDE/CBC/PKCS5Padding", OpenSSLCipher.DESEDE.CBC.PKCS5Padding.class.getName());
+        put("Cipher.DESEDE/CFB/NoPadding", OpenSSLCipher.DESEDE.CFB.NoPadding.class.getName());
+        put("Cipher.DESEDE/CFB/PKCS5Padding", OpenSSLCipher.DESEDE.CFB.PKCS5Padding.class.getName());
+        put("Cipher.DESEDE/ECB/NoPadding", OpenSSLCipher.DESEDE.ECB.NoPadding.class.getName());
+        put("Cipher.DESEDE/ECB/PKCS5Padding", OpenSSLCipher.DESEDE.ECB.PKCS5Padding.class.getName());
+        put("Cipher.DESEDE/OFB/NoPadding", OpenSSLCipher.DESEDE.OFB.NoPadding.class.getName());
+        put("Cipher.DESEDE/OFB/PKCS5Padding", OpenSSLCipher.DESEDE.OFB.PKCS5Padding.class.getName());
+
+        put("Cipher.ARC4", OpenSSLCipher.ARC4.class.getName());
+
+        /* === Mac === */
+
+        put("Mac.HmacMD5", OpenSSLMac.HmacMD5.class.getName());
+
+        // PKCS#2 - iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2)
+        // http://www.oid-info.com/get/1.2.840.113549.2
+
+        // HMAC-SHA-1 PRF (7)
+        put("Mac.HmacSHA1", OpenSSLMac.HmacSHA1.class.getName());
+        put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1");
+        put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1");
+        put("Alg.Alias.Mac.HMAC/SHA1", "HmacSHA1");
+
+        // id-hmacWithSHA256 (9)
+        put("Mac.HmacSHA256", OpenSSLMac.HmacSHA256.class.getName());
+        put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA256");
+        put("Alg.Alias.Mac.HMAC-SHA256", "HmacSHA256");
+        put("Alg.Alias.Mac.HMAC/SHA256", "HmacSHA256");
+
+        // id-hmacWithSHA384 (10)
+        put("Mac.HmacSHA384", OpenSSLMac.HmacSHA384.class.getName());
+        put("Alg.Alias.Mac.1.2.840.113549.2.10", "HmacSHA384");
+        put("Alg.Alias.Mac.HMAC-SHA384", "HmacSHA384");
+        put("Alg.Alias.Mac.HMAC/SHA384", "HmacSHA384");
+
+        // id-hmacWithSHA384 (11)
+        put("Mac.HmacSHA512", OpenSSLMac.HmacSHA512.class.getName());
+        put("Alg.Alias.Mac.1.2.840.113549.2.11", "HmacSHA512");
+        put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512");
+        put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
+
+        /* === Certificate === */
+
+        put("CertificateFactory.X509", OpenSSLX509CertificateFactory.class.getName());
+        put("Alg.Alias.CertificateFactory.X.509", "X509");
     }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java
index 49d31d3..3b15ed5 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java
@@ -33,24 +33,18 @@
 import java.security.spec.RSAPublicKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
-public class OpenSSLRSAKeyFactory<T, S> extends KeyFactorySpi {
+public class OpenSSLRSAKeyFactory extends KeyFactorySpi {
 
     @Override
     protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
         if (keySpec instanceof RSAPublicKeySpec) {
-            RSAPublicKeySpec rsaKeySpec = (RSAPublicKeySpec) keySpec;
-
-            return new OpenSSLRSAPublicKey(rsaKeySpec);
+            return new OpenSSLRSAPublicKey((RSAPublicKeySpec) keySpec);
         } else if (keySpec instanceof X509EncodedKeySpec) {
-            X509EncodedKeySpec x509KeySpec = (X509EncodedKeySpec) keySpec;
-
-            try {
-                final OpenSSLKey key = new OpenSSLKey(
-                        NativeCrypto.d2i_PUBKEY(x509KeySpec.getEncoded()));
-                return new OpenSSLRSAPublicKey(key);
-            } catch (Exception e) {
-                throw new InvalidKeySpecException(e);
-            }
+            return OpenSSLKey.getPublicKey((X509EncodedKeySpec) keySpec, NativeCrypto.EVP_PKEY_RSA);
         }
         throw new InvalidKeySpecException("Must use RSAPublicKeySpec or X509EncodedKeySpec; was "
                 + keySpec.getClass().getName());
@@ -58,24 +52,17 @@
 
     @Override
     protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
         if (keySpec instanceof RSAPrivateCrtKeySpec) {
-            RSAPrivateCrtKeySpec rsaKeySpec = (RSAPrivateCrtKeySpec) keySpec;
-
-            return new OpenSSLRSAPrivateCrtKey(rsaKeySpec);
+            return new OpenSSLRSAPrivateCrtKey((RSAPrivateCrtKeySpec) keySpec);
         } else if (keySpec instanceof RSAPrivateKeySpec) {
-            RSAPrivateKeySpec rsaKeySpec = (RSAPrivateKeySpec) keySpec;
-
-            return new OpenSSLRSAPrivateKey(rsaKeySpec);
+            return new OpenSSLRSAPrivateKey((RSAPrivateKeySpec) keySpec);
         } else if (keySpec instanceof PKCS8EncodedKeySpec) {
-            PKCS8EncodedKeySpec pkcs8KeySpec = (PKCS8EncodedKeySpec) keySpec;
-
-            try {
-                final OpenSSLKey key = new OpenSSLKey(
-                        NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(pkcs8KeySpec.getEncoded()));
-                return OpenSSLRSAPrivateKey.getInstance(key);
-            } catch (Exception e) {
-                throw new InvalidKeySpecException(e);
-            }
+            return OpenSSLKey.getPrivateKey((PKCS8EncodedKeySpec) keySpec,
+                    NativeCrypto.EVP_PKEY_RSA);
         }
         throw new InvalidKeySpecException("Must use RSAPublicKeySpec or PKCS8EncodedKeySpec; was "
                 + keySpec.getClass().getName());
@@ -92,62 +79,83 @@
             throw new InvalidKeySpecException("keySpec == null");
         }
 
-        if (key instanceof RSAPublicKey) {
+        if (!"RSA".equals(key.getAlgorithm())) {
+            throw new InvalidKeySpecException("Key must be a RSA key");
+        }
+
+        if (key instanceof RSAPublicKey && RSAPublicKeySpec.class.isAssignableFrom(keySpec)) {
             RSAPublicKey rsaKey = (RSAPublicKey) key;
-
-            if (RSAPublicKeySpec.class.equals(keySpec)) {
-                BigInteger modulus = rsaKey.getModulus();
-                BigInteger publicExponent = rsaKey.getPublicExponent();
-                return (T) new RSAPublicKeySpec(modulus, publicExponent);
-            } else if (X509EncodedKeySpec.class.equals(keySpec)) {
-                return (T) new X509EncodedKeySpec(key.getEncoded());
-            } else {
-                throw new InvalidKeySpecException("Must be RSAPublicKeySpec or X509EncodedKeySpec");
+            return (T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent());
+        } else if (key instanceof PublicKey && RSAPublicKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid X.509 encoding");
             }
-        } else if (key instanceof RSAPrivateCrtKey) {
+            RSAPublicKey rsaKey =
+                    (RSAPublicKey) engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            return (T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent());
+        } else if (key instanceof RSAPrivateCrtKey
+                && RSAPrivateCrtKeySpec.class.isAssignableFrom(keySpec)) {
             RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) key;
-
-            if (RSAPrivateKeySpec.class.equals(keySpec)) {
-                BigInteger modulus = rsaKey.getModulus();
-                BigInteger privateExponent = rsaKey.getPrivateExponent();
-                return (T) new RSAPrivateKeySpec(modulus, privateExponent);
-            } else if (RSAPrivateCrtKeySpec.class.equals(keySpec)) {
-                BigInteger modulus = rsaKey.getModulus();
-                BigInteger publicExponent = rsaKey.getPublicExponent();
-                BigInteger privateExponent = rsaKey.getPrivateExponent();
-                BigInteger primeP = rsaKey.getPrimeP();
-                BigInteger primeQ = rsaKey.getPrimeQ();
-                BigInteger primeExponentP = rsaKey.getPrimeExponentP();
-                BigInteger primeExponentQ = rsaKey.getPrimeExponentQ();
-                BigInteger crtCoefficient = rsaKey.getCrtCoefficient();
-                return (T) new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent,
-                        primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);
-            } else if (PKCS8EncodedKeySpec.class.equals(keySpec)) {
-                return (T) new PKCS8EncodedKeySpec(rsaKey.getEncoded());
-            } else {
-                throw new InvalidKeySpecException(
-                        "Must be RSAPrivateKeySpec or or RSAPrivateCrtKeySpec or PKCS8EncodedKeySpec");
-            }
-        } else if (key instanceof RSAPrivateKey) {
+            return (T) new RSAPrivateCrtKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent(),
+                    rsaKey.getPrivateExponent(), rsaKey.getPrimeP(), rsaKey.getPrimeQ(),
+                    rsaKey.getPrimeExponentP(), rsaKey.getPrimeExponentQ(),
+                    rsaKey.getCrtCoefficient());
+        } else if (key instanceof RSAPrivateCrtKey
+                && RSAPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) key;
+            return (T) new RSAPrivateKeySpec(rsaKey.getModulus(), rsaKey.getPrivateExponent());
+        } else if (key instanceof RSAPrivateKey
+                && RSAPrivateKeySpec.class.isAssignableFrom(keySpec)) {
             RSAPrivateKey rsaKey = (RSAPrivateKey) key;
-
-            if (RSAPrivateKeySpec.class.equals(keySpec)) {
-                BigInteger modulus = rsaKey.getModulus();
-                BigInteger privateExponent = rsaKey.getPrivateExponent();
-                return (T) new RSAPrivateKeySpec(modulus, privateExponent);
-            } else if (RSAPrivateCrtKeySpec.class.equals(keySpec)) {
-                BigInteger modulus = rsaKey.getModulus();
-                BigInteger privateExponent = rsaKey.getPrivateExponent();
-                return (T) new RSAPrivateCrtKeySpec(modulus, null, privateExponent, null, null,
-                        null, null, null);
-            } else if (PKCS8EncodedKeySpec.class.equals(keySpec)) {
-                return (T) new PKCS8EncodedKeySpec(rsaKey.getEncoded());
-            } else {
-                throw new InvalidKeySpecException(
-                        "Must be RSAPrivateKeySpec or PKCS8EncodedKeySpec");
+            return (T) new RSAPrivateKeySpec(rsaKey.getModulus(), rsaKey.getPrivateExponent());
+        } else if (key instanceof PrivateKey
+                && RSAPrivateCrtKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
             }
+            RSAPrivateKey privKey =
+                    (RSAPrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            if (privKey instanceof RSAPrivateCrtKey) {
+                RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) privKey;
+                return (T) new RSAPrivateCrtKeySpec(rsaKey.getModulus(),
+                        rsaKey.getPublicExponent(), rsaKey.getPrivateExponent(),
+                        rsaKey.getPrimeP(), rsaKey.getPrimeQ(), rsaKey.getPrimeExponentP(),
+                        rsaKey.getPrimeExponentQ(), rsaKey.getCrtCoefficient());
+            } else {
+                throw new InvalidKeySpecException("Encoded key is not an RSAPrivateCrtKey");
+            }
+        } else if (key instanceof PrivateKey && RSAPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
+            }
+            RSAPrivateKey rsaKey =
+                    (RSAPrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            return (T) new RSAPrivateKeySpec(rsaKey.getModulus(), rsaKey.getPrivateExponent());
+        } else if (key instanceof PrivateKey
+                && PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be PKCS#8; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            return (T) new PKCS8EncodedKeySpec(encoded);
+        } else if (key instanceof PublicKey && X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be X.509; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            return (T) new X509EncodedKeySpec(encoded);
         } else {
-            throw new InvalidKeySpecException("Must be RSAPublicKey or RSAPrivateKey");
+            throw new InvalidKeySpecException("Unsupported key type and key spec combination; key="
+                    + key.getClass().getName() + ", keySpec=" + keySpec.getName());
         }
     }
 
@@ -194,9 +202,29 @@
             } catch (InvalidKeySpecException e) {
                 throw new InvalidKeyException(e);
             }
+        } else if ("PKCS#8".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ("X.509".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
         } else {
-            throw new InvalidKeyException(
-                    "Key must be RSAPublicKey or RSAPrivateCrtKey or RSAPrivateKey");
+            throw new InvalidKeyException("Key must be an RSA public or private key; was "
+                    + key.getClass().getName());
         }
     }
 }
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 4303e5a..c5254ad 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
@@ -16,6 +16,10 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.interfaces.RSAPrivateCrtKey;
@@ -24,6 +28,8 @@
 import java.security.spec.RSAPrivateCrtKeySpec;
 
 public class OpenSSLRSAPrivateCrtKey extends OpenSSLRSAPrivateKey implements RSAPrivateCrtKey {
+    private static final long serialVersionUID = 3785291944868707197L;
+
     private BigInteger publicExponent;
 
     private BigInteger primeP;
@@ -193,16 +199,7 @@
 
         if (o instanceof OpenSSLRSAPrivateKey) {
             OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
-
-            /*
-             * We can shortcut the true case, but it still may be equivalent but
-             * different copies.
-             */
-            if (getOpenSSLKey().equals(other.getOpenSSLKey())) {
-                return true;
-            }
-
-            return NativeCrypto.EVP_PKEY_cmp(getPkeyContext(), other.getPkeyContext()) == 1;
+            return getOpenSSLKey().equals(other.getOpenSSLKey());
         }
 
         if (o instanceof RSAPrivateCrtKey) {
@@ -305,4 +302,28 @@
 
         return sb.toString();
     }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
+                modulus.toByteArray(),
+                publicExponent == null ? null : publicExponent.toByteArray(),
+                privateExponent.toByteArray(),
+                primeP == null ? null : primeP.toByteArray(),
+                primeQ == null ? null : primeQ.toByteArray(),
+                primeExponentP == null ? null : primeExponentP.toByteArray(),
+                primeExponentQ == null ? null : primeExponentQ.toByteArray(),
+                crtCoefficient == null ? null : crtCoefficient.toByteArray()));
+        fetchedParams = true;
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (getOpenSSLKey().isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        ensureReadParams();
+        stream.defaultWriteObject();
+    }
 }
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 adb05a9..c0e0848 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
@@ -16,22 +16,26 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateKeySpec;
 
-public class OpenSSLRSAPrivateKey implements RSAPrivateKey {
+public class OpenSSLRSAPrivateKey implements RSAPrivateKey, OpenSSLKeyHolder {
     private static final long serialVersionUID = 4872170254439578735L;
 
-    private final OpenSSLKey key;
+    protected transient OpenSSLKey key;
 
-    private boolean fetchedParams;
+    protected transient boolean fetchedParams;
 
-    private BigInteger modulus;
+    protected BigInteger modulus;
 
-    private BigInteger privateExponent;
+    protected BigInteger privateExponent;
 
     OpenSSLRSAPrivateKey(OpenSSLKey key) {
         this.key = key;
@@ -43,7 +47,8 @@
         fetchedParams = true;
     }
 
-    final OpenSSLKey getOpenSSLKey() {
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
         return key;
     }
 
@@ -134,6 +139,10 @@
 
     @Override
     public final BigInteger getPrivateExponent() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private exponent cannot be extracted");
+        }
+
         ensureReadParams();
         return privateExponent;
     }
@@ -176,14 +185,6 @@
         return "RSA";
     }
 
-    public int getPkeyContext() {
-        return key.getPkeyContext();
-    }
-
-    public String getPkeyAlias() {
-        return key.getAlias();
-    }
-
     @Override
     public boolean equals(Object o) {
         if (o == this) {
@@ -192,16 +193,7 @@
 
         if (o instanceof OpenSSLRSAPrivateKey) {
             OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
-
-            /*
-             * We can shortcut the true case, but it still may be equivalent but
-             * different copies.
-             */
-            if (key.equals(other.getOpenSSLKey())) {
-                return true;
-            }
-
-            return NativeCrypto.EVP_PKEY_cmp(getPkeyContext(), other.getPkeyContext()) == 1;
+            return key.equals(other.getOpenSSLKey());
         }
 
         if (o instanceof RSAPrivateKey) {
@@ -252,4 +244,28 @@
 
         return sb.toString();
     }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
+                modulus.toByteArray(),
+                null,
+                privateExponent.toByteArray(),
+                null,
+                null,
+                null,
+                null,
+                null));
+        fetchedParams = true;
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (getOpenSSLKey().isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        ensureReadParams();
+        stream.defaultWriteObject();
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPublicKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPublicKey.java
index 1613cf0..08b1920 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPublicKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPublicKey.java
@@ -16,28 +16,32 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.interfaces.RSAPublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPublicKeySpec;
 
-public class OpenSSLRSAPublicKey implements RSAPublicKey {
+public class OpenSSLRSAPublicKey implements RSAPublicKey, OpenSSLKeyHolder {
     private static final long serialVersionUID = 123125005824688292L;
 
-    private final OpenSSLKey key;
+    private transient OpenSSLKey key;
 
     private BigInteger publicExponent;
 
     private BigInteger modulus;
 
-    private boolean fetchedParams;
+    private transient boolean fetchedParams;
 
     OpenSSLRSAPublicKey(OpenSSLKey key) {
         this.key = key;
     }
 
-    OpenSSLKey getOpenSSLKey() {
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
         return key;
     }
 
@@ -162,4 +166,24 @@
 
         return sb.toString();
     }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
+                modulus.toByteArray(),
+                publicExponent.toByteArray(),
+                null,
+                null,
+                null,
+                null,
+                null,
+                null));
+        fetchedParams = true;
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        ensureReadParams();
+        stream.defaultWriteObject();
+    }
 }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSecretKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSecretKey.java
new file mode 100644
index 0000000..1bc59dc
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSecretKey.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 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.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+
+import javax.crypto.SecretKey;
+
+public class OpenSSLSecretKey implements SecretKey, OpenSSLKeyHolder {
+    private static final long serialVersionUID = 1831053062911514589L;
+
+    private final String algorithm;
+    private final int type;
+    private final byte[] encoded;
+
+    private transient OpenSSLKey key;
+
+    public OpenSSLSecretKey(String algorithm, byte[] encoded) {
+        this.algorithm = algorithm;
+        this.encoded = encoded;
+
+        type = NativeCrypto.EVP_PKEY_HMAC;
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_mac_key(type, encoded));
+    }
+
+    public OpenSSLSecretKey(String algorithm, OpenSSLKey key) {
+        this.algorithm = algorithm;
+        this.key = key;
+
+        type = NativeCrypto.EVP_PKEY_type(key.getPkeyContext());
+        encoded = null;
+    }
+
+    public static OpenSSLKey getInstance(SecretKey key) throws InvalidKeyException {
+        try {
+            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_mac_key(NativeCrypto.EVP_PKEY_HMAC,
+                    key.getEncoded()));
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    @Override
+    public String getFormat() {
+        if (key.isEngineBased()) {
+            return null;
+        }
+
+        return "RAW";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        if (key.isEngineBased()) {
+            return null;
+        }
+
+        return encoded;
+    }
+
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
+        return key;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof SecretKey)) {
+            return false;
+        }
+
+        SecretKey other = (SecretKey) o;
+        if (!algorithm.equals(other.getAlgorithm())) {
+            return false;
+        }
+
+        if (o instanceof OpenSSLSecretKey) {
+            OpenSSLSecretKey otherOpenSSL = (OpenSSLSecretKey) o;
+            return key.equals(otherOpenSSL.getOpenSSLKey());
+        } else if (key.isEngineBased()) {
+            return false;
+        }
+
+        if (!getFormat().equals(other.getFormat())) {
+            return false;
+        }
+
+        return Arrays.equals(encoded, other.getEncoded());
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_mac_key(type, encoded));
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (getOpenSSLKey().isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        stream.defaultWriteObject();
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
index 2f5fe59..9744fe0 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
@@ -32,6 +32,7 @@
     private final SSLParametersImpl sslParameters;
     private String[] enabledProtocols = NativeCrypto.getSupportedProtocols();
     private String[] enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
+    private boolean channelIdEnabled;
 
     protected OpenSSLServerSocketImpl(SSLParametersImpl sslParameters) throws IOException {
         this.sslParameters = sslParameters;
@@ -113,6 +114,20 @@
     }
 
     /**
+     * Enables/disables the TLS Channel ID extension for this server socket.
+     */
+    public void setChannelIdEnabled(boolean enabled) {
+      channelIdEnabled = enabled;
+    }
+
+    /**
+     * Checks whether the TLS Channel ID extension is enabled for this server socket.
+     */
+    public boolean isChannelIdEnabled() {
+      return channelIdEnabled;
+    }
+
+    /**
      * This method enables the cipher suites listed by
      * getSupportedCipherSuites().
      *
@@ -165,6 +180,7 @@
         OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters,
                                                          enabledProtocols.clone(),
                                                          enabledCipherSuites.clone());
+        socket.setChannelIdEnabled(channelIdEnabled);
         implAccept(socket);
         return socket;
     }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
index 003122f..1465027 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
@@ -44,7 +44,7 @@
     private boolean isValid = true;
     private final Map<String, Object> values = new HashMap<String, Object>();
     private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
-    protected int sslSessionNativePointer;
+    protected long sslSessionNativePointer;
     private String peerHost;
     private int peerPort = -1;
     private String cipherSuite;
@@ -56,7 +56,7 @@
      * Class constructor creates an SSL session context given the appropriate
      * SSL parameters.
      */
-    protected OpenSSLSessionImpl(int sslSessionNativePointer, X509Certificate[] localCertificates,
+    protected OpenSSLSessionImpl(long sslSessionNativePointer, X509Certificate[] localCertificates,
             X509Certificate[] peerCertificates, String peerHost, int peerPort,
             AbstractSessionContext sessionContext) {
         this.sslSessionNativePointer = sslSessionNativePointer;
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java
index d9746c6..34115ea 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java
@@ -25,6 +25,8 @@
 import java.security.SignatureException;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
@@ -35,13 +37,13 @@
  */
 public class OpenSSLSignature extends Signature {
     private static enum EngineType {
-        RSA, DSA,
+        RSA, DSA, EC,
     };
 
     /**
      * Holds a pointer to the native message digest context.
      */
-    private int ctx;
+    private long ctx;
 
     /**
      * The current OpenSSL key we're operating on.
@@ -117,31 +119,38 @@
         return null;
     }
 
+    private void checkEngineType(OpenSSLKey pkey) throws InvalidKeyException {
+        final int pkeyType = NativeCrypto.EVP_PKEY_type(pkey.getPkeyContext());
+
+        switch (engineType) {
+            case RSA:
+                if (pkeyType != NativeCrypto.EVP_PKEY_RSA) {
+                    throw new InvalidKeyException("Signature not initialized as RSA");
+                }
+                break;
+            case DSA:
+                if (pkeyType != NativeCrypto.EVP_PKEY_DSA) {
+                    throw new InvalidKeyException("Signature not initialized as DSA");
+                }
+                break;
+            case EC:
+                if (pkeyType != NativeCrypto.EVP_PKEY_EC) {
+                    throw new InvalidKeyException("Signature not initialized as EC");
+                }
+                break;
+            default:
+                throw new InvalidKeyException("Need DSA or RSA or EC private key");
+        }
+    }
+
     @Override
     protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
         destroyContextIfExists();
 
-        if (privateKey instanceof OpenSSLDSAPrivateKey) {
-            if (engineType != EngineType.DSA) {
-                throw new InvalidKeyException("Signature not initialized as DSA");
-            }
-
-            OpenSSLDSAPrivateKey dsaPrivateKey = (OpenSSLDSAPrivateKey) privateKey;
-            key = dsaPrivateKey.getOpenSSLKey();
-        } else if (privateKey instanceof DSAPrivateKey) {
-            if (engineType != EngineType.DSA) {
-                throw new InvalidKeyException("Signature not initialized as DSA");
-            }
-
-            DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privateKey;
-            key = OpenSSLDSAPrivateKey.getInstance(dsaPrivateKey);
-        } else if (privateKey instanceof OpenSSLRSAPrivateKey) {
-            if (engineType != EngineType.RSA) {
-                throw new InvalidKeyException("Signature not initialized as RSA");
-            }
-
-            OpenSSLRSAPrivateKey rsaPrivateKey = (OpenSSLRSAPrivateKey) privateKey;
-            key = rsaPrivateKey.getOpenSSLKey();
+        if (privateKey instanceof OpenSSLKeyHolder) {
+            OpenSSLKey pkey = ((OpenSSLKeyHolder) privateKey).getOpenSSLKey();
+            checkEngineType(pkey);
+            key = pkey;
         } else if (privateKey instanceof RSAPrivateCrtKey) {
             if (engineType != EngineType.RSA) {
                 throw new InvalidKeyException("Signature not initialized as RSA");
@@ -156,8 +165,22 @@
 
             RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
             key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey);
+        } else if (privateKey instanceof DSAPrivateKey) {
+            if (engineType != EngineType.DSA) {
+                throw new InvalidKeyException("Signature not initialized as DSA");
+            }
+
+            DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privateKey;
+            key = OpenSSLDSAPrivateKey.getInstance(dsaPrivateKey);
+        } else if (privateKey instanceof ECPrivateKey) {
+            if (engineType != EngineType.EC) {
+                throw new InvalidKeyException("Signature not initialized as EC");
+            }
+
+            ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey;
+            key = OpenSSLECPrivateKey.getInstance(ecPrivateKey);
         } else {
-            throw new InvalidKeyException("Need DSA or RSA private key");
+            throw new InvalidKeyException("Need DSA or RSA or EC private key");
         }
     }
 
@@ -166,27 +189,10 @@
         // If we had an existing context, destroy it first.
         destroyContextIfExists();
 
-        if (publicKey instanceof OpenSSLDSAPublicKey) {
-            if (engineType != EngineType.DSA) {
-                throw new InvalidKeyException("Signature not initialized as DSA");
-            }
-
-            OpenSSLDSAPublicKey dsaPublicKey = (OpenSSLDSAPublicKey) publicKey;
-            key = dsaPublicKey.getOpenSSLKey();
-        } else if (publicKey instanceof DSAPublicKey) {
-            if (engineType != EngineType.DSA) {
-                throw new InvalidKeyException("Signature not initialized as DSA");
-            }
-
-            DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
-            key = OpenSSLDSAPublicKey.getInstance(dsaPublicKey);
-        } else if (publicKey instanceof OpenSSLRSAPublicKey) {
-            if (engineType != EngineType.RSA) {
-                throw new InvalidKeyException("Signature not initialized as RSA");
-            }
-
-            OpenSSLRSAPublicKey rsaPublicKey = (OpenSSLRSAPublicKey) publicKey;
-            key = rsaPublicKey.getOpenSSLKey();
+        if (publicKey instanceof OpenSSLKeyHolder) {
+            OpenSSLKey pkey = ((OpenSSLKeyHolder) publicKey).getOpenSSLKey();
+            checkEngineType(pkey);
+            key = pkey;
         } else if (publicKey instanceof RSAPublicKey) {
             if (engineType != EngineType.RSA) {
                 throw new InvalidKeyException("Signature not initialized as RSA");
@@ -194,8 +200,22 @@
 
             RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
             key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey);
+        } else if (publicKey instanceof DSAPublicKey) {
+            if (engineType != EngineType.DSA) {
+                throw new InvalidKeyException("Signature not initialized as DSA");
+            }
+
+            DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
+            key = OpenSSLDSAPublicKey.getInstance(dsaPublicKey);
+        } else if (publicKey instanceof ECPublicKey) {
+            if (engineType != EngineType.EC) {
+                throw new InvalidKeyException("Signature not initialized as EC");
+            }
+
+            ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
+            key = OpenSSLECPublicKey.getInstance(ecPublicKey);
         } else {
-            throw new InvalidKeyException("Need DSA or RSA public key");
+            throw new InvalidKeyException("Need DSA or RSA or EC public key");
         }
     }
 
@@ -207,7 +227,7 @@
     protected byte[] engineSign() throws SignatureException {
         if (key == null) {
             // This can't actually happen, but you never know...
-            throw new SignatureException("Need DSA or RSA private key");
+            throw new SignatureException("Need DSA or RSA or EC private key");
         }
 
         try {
@@ -299,5 +319,25 @@
             super("DSA-SHA1", EngineType.DSA);
         }
     }
+    public static final class SHA1ECDSA extends OpenSSLSignature {
+        public SHA1ECDSA() throws NoSuchAlgorithmException {
+            super("SHA1", EngineType.EC);
+        }
+    }
+    public static final class SHA256ECDSA extends OpenSSLSignature {
+        public SHA256ECDSA() throws NoSuchAlgorithmException {
+            super("SHA256", EngineType.EC);
+        }
+    }
+    public static final class SHA384ECDSA extends OpenSSLSignature {
+        public SHA384ECDSA() throws NoSuchAlgorithmException {
+            super("SHA384", EngineType.EC);
+        }
+    }
+    public static final class SHA512ECDSA extends OpenSSLSignature {
+        public SHA512ECDSA() throws NoSuchAlgorithmException {
+            super("SHA512", EngineType.EC);
+        }
+    }
 }
 
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignatureRawRSA.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignatureRawRSA.java
index 289af30..e5d33b3 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignatureRawRSA.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignatureRawRSA.java
@@ -102,7 +102,7 @@
             RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
             key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey);
         } else {
-            throw new InvalidKeyException("Need DSA or RSA private key");
+            throw new InvalidKeyException("Need RSA private key");
         }
 
         // Allocate buffer according to RSA modulus size.
@@ -120,7 +120,7 @@
             RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
             key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey);
         } else {
-            throw new InvalidKeyException("Need DSA or RSA public key");
+            throw new InvalidKeyException("Need RSA public key");
         }
 
         // Allocate buffer according to RSA modulus size.
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 ed5c814..d9941aa 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
@@ -63,7 +63,7 @@
         extends javax.net.ssl.SSLSocket
         implements NativeCrypto.SSLHandshakeCallbacks {
 
-    private int sslNativePointer;
+    private long sslNativePointer;
     private InputStream is;
     private OutputStream os;
     private final Object handshakeLock = new Object();
@@ -75,6 +75,10 @@
     private String[] enabledCipherSuites;
     private boolean useSessionTickets;
     private String hostname;
+    /** Whether the TLS Channel ID extension is enabled. This field is server-side only. */
+    private boolean channelIdEnabled;
+    /** Private key for the TLS Channel ID extension. This field is client-side only. */
+    private PrivateKey channelIdPrivateKey;
     private OpenSSLSessionImpl sslSession;
     private final Socket socket;
     private boolean autoClose;
@@ -261,7 +265,7 @@
 
         final boolean client = sslParameters.getUseClientMode();
 
-        final int sslCtxNativePointer = (client) ?
+        final long sslCtxNativePointer = (client) ?
             sslParameters.getClientSessionContext().sslCtxNativePointer :
             sslParameters.getServerSessionContext().sslCtxNativePointer;
 
@@ -373,6 +377,19 @@
                 setSoWriteTimeout(handshakeTimeoutMilliseconds);
             }
 
+            // TLS Channel ID
+            if (client) {
+                // Client-side TLS Channel ID
+                if (channelIdPrivateKey != null) {
+                    NativeCrypto.SSL_set1_tls_channel_id(sslNativePointer, channelIdPrivateKey);
+                }
+            } else {
+                // Server-side TLS Channel ID
+                if (channelIdEnabled) {
+                    NativeCrypto.SSL_enable_tls_channel_id(sslNativePointer);
+                }
+            }
+
             int sslSessionNativePointer;
             try {
                 sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer,
@@ -473,13 +490,8 @@
             return;
         }
 
-        if (privateKey instanceof OpenSSLRSAPrivateKey) {
-            OpenSSLRSAPrivateKey rsaKey = (OpenSSLRSAPrivateKey) privateKey;
-            OpenSSLKey key = rsaKey.getOpenSSLKey();
-            NativeCrypto.SSL_use_OpenSSL_PrivateKey(sslNativePointer, key.getPkeyContext());
-        } else if (privateKey instanceof OpenSSLDSAPrivateKey) {
-            OpenSSLDSAPrivateKey dsaKey = (OpenSSLDSAPrivateKey) privateKey;
-            OpenSSLKey key = dsaKey.getOpenSSLKey();
+        if (privateKey instanceof OpenSSLKeyHolder) {
+            OpenSSLKey key = ((OpenSSLKeyHolder) privateKey).getOpenSSLKey();
             NativeCrypto.SSL_use_OpenSSL_PrivateKey(sslNativePointer, key.getPkeyContext());
         } else if ("PKCS#8".equals(privateKey.getFormat())) {
             byte[] privateKeyBytes = privateKey.getEncoded();
@@ -593,10 +605,8 @@
 
         } catch (CertificateException e) {
             throw e;
-        } catch (RuntimeException e) {
-            throw e;
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            throw new CertificateException(e);
         }
     }
 
@@ -799,6 +809,72 @@
         this.hostname = hostname;
     }
 
+    /**
+     * Enables/disables TLS Channel ID for this server socket.
+     *
+     * <p>This method needs to be invoked before the handshake starts.
+     *
+     * @throws IllegalStateException if this is a client socket or if the handshake has already
+     *         started.
+
+     */
+    public void setChannelIdEnabled(boolean enabled) {
+        if (getUseClientMode()) {
+            throw new IllegalStateException("Client mode");
+        }
+        if (handshakeStarted) {
+            throw new IllegalStateException(
+                    "Could not enable/disable Channel ID after the initial handshake has"
+                    + " begun.");
+        }
+        this.channelIdEnabled = enabled;
+    }
+
+    /**
+     * Gets the TLS Channel ID for this server socket. Channel ID is only available once the
+     * handshake completes.
+     *
+     * @return channel ID or {@code null} if not available.
+     *
+     * @throws IllegalStateException if this is a client socket or if the handshake has not yet
+     *         completed.
+     * @throws SSLException if channel ID is available but could not be obtained.
+     */
+    public byte[] getChannelId() throws SSLException {
+        if (getUseClientMode()) {
+            throw new IllegalStateException("Client mode");
+        }
+        if (!handshakeCompleted) {
+            throw new IllegalStateException(
+                    "Channel ID is only available after handshake completes");
+        }
+        return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer);
+    }
+
+    /**
+     * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
+     *
+     * <p>This method needs to be invoked before the handshake starts.
+     *
+     * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
+     *        TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST
+     *        P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
+     *
+     * @throws IllegalStateException if this is a server socket or if the handshake has already
+     *         started.
+     */
+    public void setChannelIdPrivateKey(PrivateKey privateKey) {
+        if (!getUseClientMode()) {
+            throw new IllegalStateException("Server mode");
+        }
+        if (handshakeStarted) {
+            throw new IllegalStateException(
+                    "Could not change Channel ID private key after the initial handshake has"
+                    + " begun.");
+        }
+        this.channelIdPrivateKey = privateKey;
+    }
+
     @Override public boolean getUseClientMode() {
         return sslParameters.getUseClientMode();
     }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CRL.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CRL.java
new file mode 100644
index 0000000..9d6b6b8
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CRL.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2013 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 org.apache.harmony.security.utils.AlgNameMapper;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLX509CertificateFactory.ParsingException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.Certificate;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.security.auth.x500.X500Principal;
+
+public class OpenSSLX509CRL extends X509CRL {
+    private final long mContext;
+
+    private OpenSSLX509CRL(long ctx) {
+        mContext = ctx;
+    }
+
+    public static OpenSSLX509CRL fromX509DerInputStream(InputStream is) throws ParsingException {
+        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        try {
+            final long crlCtx = NativeCrypto.d2i_X509_CRL_bio(bis.getBioContext());
+            if (crlCtx == 0) {
+                return null;
+            }
+            return new OpenSSLX509CRL(crlCtx);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+    }
+
+    public static List<OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is)
+            throws ParsingException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        final long[] certRefs;
+        try {
+            certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CRLS);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+
+        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
+        for (int i = 0; i < certRefs.length; i++) {
+            if (certRefs[i] == 0) {
+                continue;
+            }
+            certs.add(new OpenSSLX509CRL(certRefs[i]));
+        }
+        return certs;
+    }
+
+    public static OpenSSLX509CRL fromX509PemInputStream(InputStream is) throws ParsingException {
+        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        try {
+            final long crlCtx = NativeCrypto.PEM_read_bio_X509_CRL(bis.getBioContext());
+            if (crlCtx == 0) {
+                return null;
+            }
+            return new OpenSSLX509CRL(crlCtx);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+    }
+
+    public static List<OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is)
+            throws ParsingException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        final long[] certRefs;
+        try {
+            certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(),
+                    NativeCrypto.PKCS7_CRLS);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+
+        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
+        for (int i = 0; i < certRefs.length; i++) {
+            if (certRefs[i] == 0) {
+                continue;
+            }
+            certs.add(new OpenSSLX509CRL(certRefs[i]));
+        }
+        return certs;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        String[] critOids =
+                NativeCrypto.get_X509_CRL_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
+
+        /*
+         * This API has a special case that if there are no extensions, we
+         * should return null. So if we have no critical extensions, we'll check
+         * non-critical extensions.
+         */
+        if ((critOids.length == 0)
+                && (NativeCrypto.get_X509_CRL_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) {
+            return null;
+        }
+
+        return new HashSet<String>(Arrays.asList(critOids));
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return NativeCrypto.X509_CRL_get_ext_oid(mContext, oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        String[] nonCritOids =
+                NativeCrypto.get_X509_CRL_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL);
+
+        /*
+         * This API has a special case that if there are no extensions, we
+         * should return null. So if we have no non-critical extensions, we'll
+         * check critical extensions.
+         */
+        if ((nonCritOids.length == 0)
+                && (NativeCrypto.get_X509_CRL_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) {
+            return null;
+        }
+
+        return new HashSet<String>(Arrays.asList(nonCritOids));
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        final String[] criticalOids =
+                NativeCrypto.get_X509_CRL_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
+        for (String oid : criticalOids) {
+            final long extensionRef = NativeCrypto.X509_CRL_get_ext(mContext, oid);
+            if (NativeCrypto.X509_supported_extension(extensionRef) != 1) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public byte[] getEncoded() throws CRLException {
+        return NativeCrypto.i2d_X509_CRL(mContext);
+    }
+
+    private void verifyOpenSSL(OpenSSLKey pkey) throws CRLException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        NativeCrypto.X509_CRL_verify(mContext, pkey.getPkeyContext());
+    }
+
+    private void verifyInternal(PublicKey key, String sigProvider) throws CRLException,
+            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+            SignatureException {
+        String sigAlg = getSigAlgName();
+        if (sigAlg == null) {
+            sigAlg = getSigAlgOID();
+        }
+
+        final Signature sig;
+        if (sigProvider == null) {
+            sig = Signature.getInstance(sigAlg);
+        } else {
+            sig = Signature.getInstance(sigAlg, sigProvider);
+        }
+
+        sig.initVerify(key);
+        sig.update(getTBSCertList());
+        if (!sig.verify(getSignature())) {
+            throw new SignatureException("signature did not verify");
+        }
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CRLException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        if (key instanceof OpenSSLKeyHolder) {
+            OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey();
+            verifyOpenSSL(pkey);
+            return;
+        }
+
+        verifyInternal(key, null);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider) throws CRLException,
+            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+            SignatureException {
+        verifyInternal(key, sigProvider);
+    }
+
+    @Override
+    public int getVersion() {
+        return (int) NativeCrypto.X509_CRL_get_version(mContext) + 1;
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return getIssuerX500Principal();
+    }
+
+    @Override
+    public X500Principal getIssuerX500Principal() {
+        final byte[] issuer = NativeCrypto.X509_CRL_get_issuer_name(mContext);
+        return new X500Principal(issuer);
+    }
+
+    @Override
+    public Date getThisUpdate() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_CRL_get_lastUpdate(mContext),
+                calendar);
+        return calendar.getTime();
+    }
+
+    @Override
+    public Date getNextUpdate() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_CRL_get_nextUpdate(mContext),
+                calendar);
+        return calendar.getTime();
+    }
+
+    @Override
+    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber) {
+        final long revokedRef = NativeCrypto.X509_CRL_get0_by_serial(mContext,
+                serialNumber.toByteArray());
+        if (revokedRef == 0) {
+            return null;
+        }
+
+        return new OpenSSLX509CRLEntry(NativeCrypto.X509_REVOKED_dup(revokedRef));
+    }
+
+    @Override
+    public X509CRLEntry getRevokedCertificate(X509Certificate certificate) {
+        if (certificate instanceof OpenSSLX509Certificate) {
+            OpenSSLX509Certificate osslCert = (OpenSSLX509Certificate) certificate;
+            final long x509RevokedRef = NativeCrypto.X509_CRL_get0_by_cert(mContext,
+                    osslCert.getContext());
+
+            if (x509RevokedRef == 0) {
+                return null;
+            }
+
+            return new OpenSSLX509CRLEntry(NativeCrypto.X509_REVOKED_dup(x509RevokedRef));
+        }
+
+        return getRevokedCertificate(certificate.getSerialNumber());
+    }
+
+    @Override
+    public Set<? extends X509CRLEntry> getRevokedCertificates() {
+        final long[] entryRefs = NativeCrypto.X509_CRL_get_REVOKED(mContext);
+        if (entryRefs == null || entryRefs.length == 0) {
+            return null;
+        }
+
+        final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<OpenSSLX509CRLEntry>();
+        for (long entryRef : entryRefs) {
+            crlSet.add(new OpenSSLX509CRLEntry(entryRef));
+        }
+
+        return crlSet;
+    }
+
+    @Override
+    public byte[] getTBSCertList() throws CRLException {
+        return NativeCrypto.get_X509_CRL_crl_enc(mContext);
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return NativeCrypto.get_X509_CRL_signature(mContext);
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return AlgNameMapper.map2AlgName(getSigAlgOID());
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return NativeCrypto.get_X509_CRL_sig_alg_oid(mContext);
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return NativeCrypto.get_X509_CRL_sig_alg_parameter(mContext);
+    }
+
+    @Override
+    public boolean isRevoked(Certificate cert) {
+        if (!(cert instanceof X509Certificate)) {
+            return false;
+        }
+
+        final OpenSSLX509Certificate osslCert;
+        if (cert instanceof OpenSSLX509Certificate) {
+            osslCert = (OpenSSLX509Certificate) cert;
+        } else {
+            try {
+                osslCert = OpenSSLX509Certificate.fromX509DerInputStream(new ByteArrayInputStream(
+                        cert.getEncoded()));
+            } catch (Exception e) {
+                throw new RuntimeException("cannot convert certificate", e);
+            }
+        }
+
+        final long x509RevokedRef = NativeCrypto.X509_CRL_get0_by_cert(mContext,
+                osslCert.getContext());
+
+        return x509RevokedRef != 0;
+    }
+
+    @Override
+    public String toString() {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        final long bioCtx = NativeCrypto.create_BIO_OutputStream(os);
+        try {
+            NativeCrypto.X509_CRL_print(bioCtx, mContext);
+            return os.toString();
+        } finally {
+            NativeCrypto.BIO_free(bioCtx);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mContext != 0) {
+                NativeCrypto.X509_CRL_free(mContext);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CRLEntry.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CRLEntry.java
new file mode 100644
index 0000000..3655338
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CRLEntry.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 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.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRLEntry;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TimeZone;
+
+public class OpenSSLX509CRLEntry extends X509CRLEntry {
+    private final long mContext;
+
+    OpenSSLX509CRLEntry(long ctx) {
+        mContext = ctx;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        String[] critOids =
+                NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_CRITICAL);
+
+        /*
+         * This API has a special case that if there are no extensions, we
+         * should return null. So if we have no critical extensions, we'll check
+         * non-critical extensions.
+         */
+        if ((critOids.length == 0)
+                && (NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) {
+            return null;
+        }
+
+        return new HashSet<String>(Arrays.asList(critOids));
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return NativeCrypto.X509_REVOKED_get_ext_oid(mContext, oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        String[] critOids =
+                NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL);
+
+        /*
+         * This API has a special case that if there are no extensions, we
+         * should return null. So if we have no non-critical extensions, we'll
+         * check critical extensions.
+         */
+        if ((critOids.length == 0)
+                && (NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) {
+            return null;
+        }
+
+        return new HashSet<String>(Arrays.asList(critOids));
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        final String[] criticalOids =
+                NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_CRITICAL);
+        for (String oid : criticalOids) {
+            final long extensionRef = NativeCrypto.X509_REVOKED_get_ext(mContext, oid);
+            if (NativeCrypto.X509_supported_extension(extensionRef) != 1) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public byte[] getEncoded() throws CRLException {
+        return NativeCrypto.i2d_X509_REVOKED(mContext);
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return new BigInteger(NativeCrypto.X509_REVOKED_get_serialNumber(mContext));
+    }
+
+    @Override
+    public Date getRevocationDate() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.get_X509_REVOKED_revocationDate(mContext),
+                calendar);
+        return calendar.getTime();
+    }
+
+    @Override
+    public boolean hasExtensions() {
+        return (NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length != 0)
+                || (NativeCrypto.get_X509_REVOKED_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_CRITICAL).length != 0);
+    }
+
+    @Override
+    public String toString() {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        long bioCtx = NativeCrypto.create_BIO_OutputStream(os);
+        try {
+            NativeCrypto.X509_REVOKED_print(bioCtx, mContext);
+            return os.toString();
+        } finally {
+            NativeCrypto.BIO_free(bioCtx);
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
new file mode 100644
index 0000000..4639dfd
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2013 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 org.apache.harmony.xnet.provider.jsse.OpenSSLX509CertificateFactory.ParsingException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class OpenSSLX509CertPath extends CertPath {
+    private static final byte[] PKCS7_MARKER = "-----BEGIN PKCS7".getBytes();
+
+    private static final int PUSHBACK_SIZE = 64;
+
+    /**
+     * Supported encoding types for CerthPath. Used by the various APIs that
+     * encode this into bytes such as {@link #getEncoded()}.
+     */
+    private enum Encoding {
+        PKI_PATH("PkiPath"),
+        PKCS7("PKCS7");
+
+        private final String apiName;
+
+        Encoding(String apiName) {
+            this.apiName = apiName;
+        }
+
+        static Encoding findByApiName(String apiName) throws CertificateEncodingException {
+            for (Encoding element : values()) {
+                if (element.apiName.equals(apiName)) {
+                    return element;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    /** Unmodifiable list of encodings for the API. */
+    private static final List<String> ALL_ENCODINGS = Collections.unmodifiableList(Arrays
+            .asList(new String[] {
+                    Encoding.PKI_PATH.apiName,
+                    Encoding.PKCS7.apiName,
+            }));
+
+    private static final Encoding DEFAULT_ENCODING = Encoding.PKI_PATH;
+
+    private final List<? extends X509Certificate> mCertificates;
+
+    static Iterator<String> getEncodingsIterator() {
+        return ALL_ENCODINGS.iterator();
+    }
+
+    protected OpenSSLX509CertPath(List<? extends X509Certificate> certificates) {
+        super("X.509");
+
+        mCertificates = certificates;
+    }
+
+    @Override
+    public List<? extends Certificate> getCertificates() {
+        return Collections.unmodifiableList(mCertificates);
+    }
+
+    private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
+        final OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[mCertificates.size()];
+        final long[] certRefs = new long[certs.length];
+
+        for (int i = 0, j = certs.length - 1; j >= 0; i++, j--) {
+            final X509Certificate cert = mCertificates.get(i);
+
+            if (cert instanceof OpenSSLX509Certificate) {
+                certs[j] = (OpenSSLX509Certificate) cert;
+            } else {
+                certs[j] = OpenSSLX509Certificate.fromX509Der(cert.getEncoded());
+            }
+
+            certRefs[j] = certs[j].getContext();
+        }
+
+        switch (encoding) {
+            case PKI_PATH:
+                return NativeCrypto.ASN1_seq_pack_X509(certRefs);
+            case PKCS7:
+                return NativeCrypto.i2d_PKCS7(certRefs);
+            default:
+                throw new CertificateEncodingException("Unknown encoding");
+        }
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return getEncoded(DEFAULT_ENCODING);
+    }
+
+    @Override
+    public byte[] getEncoded(String encoding) throws CertificateEncodingException {
+        Encoding enc = Encoding.findByApiName(encoding);
+        if (enc == null) {
+            throw new CertificateEncodingException("Invalid encoding: " + encoding);
+        }
+
+        return getEncoded(enc);
+    }
+
+    @Override
+    public Iterator<String> getEncodings() {
+        return getEncodingsIterator();
+    }
+
+    private static CertPath fromPkiPathEncoding(InputStream inStream) throws CertificateException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(inStream);
+
+        final long[] certRefs;
+        try {
+            certRefs = NativeCrypto.ASN1_seq_unpack_X509_bio(bis.getBioContext());
+        } catch (Exception e) {
+            throw new CertificateException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+
+        if (certRefs == null) {
+            return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
+        }
+
+        final List<OpenSSLX509Certificate> certs =
+                new ArrayList<OpenSSLX509Certificate>(certRefs.length);
+        for (int i = certRefs.length - 1; i >= 0; i--) {
+            if (certRefs[i] == 0) {
+                continue;
+            }
+            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+        }
+
+        return new OpenSSLX509CertPath(certs);
+    }
+
+    private static CertPath fromPkcs7Encoding(InputStream inStream) throws CertificateException {
+        try {
+            if (inStream == null || inStream.available() == 0) {
+                return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
+            }
+        } catch (IOException e) {
+            throw new CertificateException("Problem reading input stream", e);
+        }
+
+        final boolean markable = inStream.markSupported();
+        if (markable) {
+            inStream.mark(PUSHBACK_SIZE);
+        }
+
+        /* Attempt to see if this is a PKCS#7 bag. */
+        final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
+        try {
+            final byte[] buffer = new byte[PKCS7_MARKER.length];
+
+            final int len = pbis.read(buffer);
+            if (len < 0) {
+                /* No need to reset here. The stream was empty or EOF. */
+                throw new ParsingException("inStream is empty");
+            }
+            pbis.unread(buffer, 0, len);
+
+            if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
+                return new OpenSSLX509CertPath(OpenSSLX509Certificate.fromPkcs7PemInputStream(pbis));
+            }
+
+            return new OpenSSLX509CertPath(OpenSSLX509Certificate.fromPkcs7DerInputStream(pbis));
+        } catch (Exception e) {
+            if (markable) {
+                try {
+                    inStream.reset();
+                } catch (IOException ignored) {
+                }
+            }
+            throw new CertificateException(e);
+        }
+    }
+
+    private static CertPath fromEncoding(InputStream inStream, Encoding encoding)
+            throws CertificateException {
+        switch (encoding) {
+            case PKI_PATH:
+                return fromPkiPathEncoding(inStream);
+            case PKCS7:
+                return fromPkcs7Encoding(inStream);
+            default:
+                throw new CertificateEncodingException("Unknown encoding");
+        }
+    }
+
+    public static CertPath fromEncoding(InputStream inStream, String encoding)
+            throws CertificateException {
+        Encoding enc = Encoding.findByApiName(encoding);
+        if (enc == null) {
+            throw new CertificateException("Invalid encoding: " + encoding);
+        }
+
+        return fromEncoding(inStream, enc);
+    }
+
+    public static CertPath fromEncoding(InputStream inStream) throws CertificateException {
+        return fromEncoding(inStream, DEFAULT_ENCODING);
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java
new file mode 100644
index 0000000..af960d5
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java
@@ -0,0 +1,492 @@
+/*
+ * 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 org.apache.harmony.security.utils.AlgNameMapper;
+import org.apache.harmony.security.x509.X509PublicKey;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLX509CertificateFactory.ParsingException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.security.auth.x500.X500Principal;
+
+public class OpenSSLX509Certificate extends X509Certificate {
+    private final long mContext;
+
+    OpenSSLX509Certificate(long ctx) {
+        mContext = ctx;
+    }
+
+    public static OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
+            throws ParsingException {
+        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        try {
+            final long certCtx = NativeCrypto.d2i_X509_bio(bis.getBioContext());
+            if (certCtx == 0) {
+                return null;
+            }
+            return new OpenSSLX509Certificate(certCtx);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+    }
+
+    public static OpenSSLX509Certificate fromX509Der(byte[] encoded) {
+        final long certCtx = NativeCrypto.d2i_X509(encoded);
+        if (certCtx == 0) {
+            return null;
+        }
+        return new OpenSSLX509Certificate(certCtx);
+    }
+
+    public static List<OpenSSLX509Certificate> fromPkcs7DerInputStream(InputStream is)
+            throws ParsingException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        final long[] certRefs;
+        try {
+            certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CERTS);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+
+        if (certRefs == null) {
+            return Collections.emptyList();
+        }
+
+        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
+                certRefs.length);
+        for (int i = 0; i < certRefs.length; i++) {
+            if (certRefs[i] == 0) {
+                continue;
+            }
+            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+        }
+        return certs;
+    }
+
+    public static OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
+            throws ParsingException {
+        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        try {
+            final long certCtx = NativeCrypto.PEM_read_bio_X509(bis.getBioContext());
+            if (certCtx == 0L) {
+                return null;
+            }
+            return new OpenSSLX509Certificate(certCtx);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+    }
+
+    public static List<OpenSSLX509Certificate> fromPkcs7PemInputStream(InputStream is)
+            throws ParsingException {
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
+
+        final long[] certRefs;
+        try {
+            certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(),
+                    NativeCrypto.PKCS7_CERTS);
+        } catch (Exception e) {
+            throw new ParsingException(e);
+        } finally {
+            NativeCrypto.BIO_free(bis.getBioContext());
+        }
+
+        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
+                certRefs.length);
+        for (int i = 0; i < certRefs.length; i++) {
+            if (certRefs[i] == 0) {
+                continue;
+            }
+            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+        }
+        return certs;
+    }
+
+    @Override
+    public Set<String> getCriticalExtensionOIDs() {
+        String[] critOids =
+                NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
+
+        /*
+         * This API has a special case that if there are no extensions, we
+         * should return null. So if we have no critical extensions, we'll check
+         * non-critical extensions.
+         */
+        if ((critOids.length == 0)
+                && (NativeCrypto.get_X509_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) {
+            return null;
+        }
+
+        return new HashSet<String>(Arrays.asList(critOids));
+    }
+
+    @Override
+    public byte[] getExtensionValue(String oid) {
+        return NativeCrypto.X509_get_ext_oid(mContext, oid);
+    }
+
+    @Override
+    public Set<String> getNonCriticalExtensionOIDs() {
+        String[] nonCritOids =
+                NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_NON_CRITICAL);
+
+        /*
+         * This API has a special case that if there are no extensions, we
+         * should return null. So if we have no non-critical extensions, we'll
+         * check critical extensions.
+         */
+        if ((nonCritOids.length == 0)
+                && (NativeCrypto.get_X509_ext_oids(mContext,
+                        NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) {
+            return null;
+        }
+
+        return new HashSet<String>(Arrays.asList(nonCritOids));
+    }
+
+    @Override
+    public boolean hasUnsupportedCriticalExtension() {
+        return (NativeCrypto.get_X509_ex_flags(mContext) & NativeCrypto.EXFLAG_CRITICAL) != 0;
+    }
+
+    @Override
+    public void checkValidity() throws CertificateExpiredException,
+            CertificateNotYetValidException {
+        checkValidity(new Date());
+    }
+
+    @Override
+    public void checkValidity(Date date) throws CertificateExpiredException,
+            CertificateNotYetValidException {
+        if (getNotBefore().compareTo(date) > 0) {
+            throw new CertificateNotYetValidException();
+        }
+
+        if (getNotAfter().compareTo(date) < 0) {
+            throw new CertificateExpiredException();
+        }
+    }
+
+    @Override
+    public int getVersion() {
+        return (int) NativeCrypto.X509_get_version(mContext) + 1;
+    }
+
+    @Override
+    public BigInteger getSerialNumber() {
+        return new BigInteger(NativeCrypto.X509_get_serialNumber(mContext));
+    }
+
+    @Override
+    public Principal getIssuerDN() {
+        return getIssuerX500Principal();
+    }
+
+    @Override
+    public Principal getSubjectDN() {
+        return getSubjectX500Principal();
+    }
+
+    @Override
+    public Date getNotBefore() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notBefore(mContext), calendar);
+        return calendar.getTime();
+    }
+
+    @Override
+    public Date getNotAfter() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notAfter(mContext), calendar);
+        return calendar.getTime();
+    }
+
+    @Override
+    public byte[] getTBSCertificate() throws CertificateEncodingException {
+        return NativeCrypto.get_X509_cert_info_enc(mContext);
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return NativeCrypto.get_X509_signature(mContext);
+    }
+
+    @Override
+    public String getSigAlgName() {
+        return AlgNameMapper.map2AlgName(getSigAlgOID());
+    }
+
+    @Override
+    public String getSigAlgOID() {
+        return NativeCrypto.get_X509_sig_alg_oid(mContext);
+    }
+
+    @Override
+    public byte[] getSigAlgParams() {
+        return NativeCrypto.get_X509_sig_alg_parameter(mContext);
+    }
+
+    @Override
+    public boolean[] getIssuerUniqueID() {
+        return NativeCrypto.get_X509_issuerUID(mContext);
+    }
+
+    @Override
+    public boolean[] getSubjectUniqueID() {
+        return NativeCrypto.get_X509_subjectUID(mContext);
+    }
+
+    @Override
+    public boolean[] getKeyUsage() {
+        final boolean[] kusage = NativeCrypto.get_X509_ex_kusage(mContext);
+        if (kusage == null) {
+            return null;
+        }
+
+        if (kusage.length >= 9) {
+            return kusage;
+        }
+
+        final boolean resized[] = new boolean[9];
+        System.arraycopy(kusage, 0, resized, 0, kusage.length);
+        return resized;
+    }
+
+    @Override
+    public int getBasicConstraints() {
+        if (NativeCrypto.X509_check_ca(mContext) != 1) {
+            return -1;
+        }
+
+        final int pathLen = NativeCrypto.get_X509_ex_pathlen(mContext);
+        if (pathLen == -1) {
+            return Integer.MAX_VALUE;
+        }
+
+        return pathLen;
+    }
+
+    @Override
+    public byte[] getEncoded() throws CertificateEncodingException {
+        return NativeCrypto.i2d_X509(mContext);
+    }
+
+    private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException,
+            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+            SignatureException {
+        NativeCrypto.X509_verify(mContext, pkey.getPkeyContext());
+    }
+
+    private void verifyInternal(PublicKey key, String sigProvider) throws CertificateException,
+            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+            SignatureException {
+        String sigAlg = getSigAlgName();
+        if (sigAlg == null) {
+            sigAlg = getSigAlgOID();
+        }
+
+        final Signature sig;
+        if (sigProvider == null) {
+            sig = Signature.getInstance(sigAlg);
+        } else {
+            sig = Signature.getInstance(sigAlg, sigProvider);
+        }
+
+        sig.initVerify(key);
+        sig.update(getTBSCertificate());
+        if (!sig.verify(getSignature())) {
+            throw new SignatureException("signature did not verify");
+        }
+    }
+
+    @Override
+    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
+        if (key instanceof OpenSSLKeyHolder) {
+            OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey();
+            verifyOpenSSL(pkey);
+            return;
+        }
+
+        verifyInternal(key, null);
+    }
+
+    @Override
+    public void verify(PublicKey key, String sigProvider) throws CertificateException,
+            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+            SignatureException {
+        verifyInternal(key, sigProvider);
+    }
+
+    @Override
+    public String toString() {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        long bioCtx = NativeCrypto.create_BIO_OutputStream(os);
+        try {
+            NativeCrypto.X509_print_ex(bioCtx, mContext, 0, 0);
+            return os.toString();
+        } finally {
+            NativeCrypto.BIO_free(bioCtx);
+        }
+    }
+
+    @Override
+    public PublicKey getPublicKey() {
+        /* First try to generate the key from supported OpenSSL key types. */
+        try {
+            OpenSSLKey pkey = new OpenSSLKey(NativeCrypto.X509_get_pubkey(mContext));
+            return pkey.getPublicKey();
+        } catch (NoSuchAlgorithmException ignored) {
+        }
+
+        /* Try generating the key using other Java providers. */
+        String oid = NativeCrypto.get_X509_pubkey_oid(mContext);
+        byte[] encoded = NativeCrypto.i2d_X509_PUBKEY(mContext);
+        try {
+            KeyFactory kf = KeyFactory.getInstance(oid);
+            return kf.generatePublic(new X509EncodedKeySpec(encoded));
+        } catch (NoSuchAlgorithmException ignored) {
+        } catch (InvalidKeySpecException ignored) {
+        }
+
+        /*
+         * We couldn't find anything else, so just return a nearly-unusable
+         * X.509-encoded key.
+         */
+        return new X509PublicKey(oid, encoded, null);
+    }
+
+    @Override
+    public X500Principal getIssuerX500Principal() {
+        final byte[] issuer = NativeCrypto.X509_get_issuer_name(mContext);
+        return new X500Principal(issuer);
+    }
+
+    @Override
+    public X500Principal getSubjectX500Principal() {
+        final byte[] subject = NativeCrypto.X509_get_subject_name(mContext);
+        return new X500Principal(subject);
+    }
+
+    @Override
+    public List<String> getExtendedKeyUsage() throws CertificateParsingException {
+        String[] extUsage = NativeCrypto.get_X509_ex_xkusage(mContext);
+        if (extUsage == null) {
+            return null;
+        }
+
+        return Arrays.asList(extUsage);
+    }
+
+    private static Collection<List<?>> alternativeNameArrayToList(Object[][] altNameArray) {
+        if (altNameArray == null) {
+            return null;
+        }
+
+        Collection<List<?>> coll = new ArrayList<List<?>>(altNameArray.length);
+        for (int i = 0; i < altNameArray.length; i++) {
+            coll.add(Collections.unmodifiableList(Arrays.asList(altNameArray[i])));
+        }
+
+        return Collections.unmodifiableCollection(coll);
+    }
+
+    @Override
+    public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
+        return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext,
+                NativeCrypto.GN_STACK_SUBJECT_ALT_NAME));
+    }
+
+    @Override
+    public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
+        return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext,
+                NativeCrypto.GN_STACK_ISSUER_ALT_NAME));
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof OpenSSLX509Certificate) {
+            OpenSSLX509Certificate o = (OpenSSLX509Certificate) other;
+
+            return NativeCrypto.X509_cmp(mContext, o.mContext) == 0;
+        }
+
+        return super.equals(other);
+    }
+
+    @Override
+    public int hashCode() {
+        /* Make this faster since we might be in hash-based structures. */
+        return NativeCrypto.get_X509_hashCode(mContext);
+    }
+
+    long getContext() {
+        return mContext;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mContext != 0) {
+                NativeCrypto.X509_free(mContext);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java
new file mode 100644
index 0000000..09e2507
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java
@@ -0,0 +1,333 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactorySpi;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class OpenSSLX509CertificateFactory extends CertificateFactorySpi {
+    private static final byte[] PKCS7_MARKER = "-----BEGIN PKCS7".getBytes();
+
+    private static final int PUSHBACK_SIZE = 64;
+
+    static class ParsingException extends Exception {
+        private static final long serialVersionUID = 8390802697728301325L;
+
+        public ParsingException(String message) {
+            super(message);
+        }
+
+        public ParsingException(Exception cause) {
+            super(cause);
+        }
+
+        public ParsingException(String message, Exception cause) {
+            super(message, cause);
+        }
+    }
+
+    /**
+     * The code for X509 Certificates and CRL is pretty much the same. We use
+     * this abstract class to share the code between them. This makes it ugly,
+     * but it's already written in this language anyway.
+     */
+    private static abstract class Parser<T> {
+        public T generateItem(InputStream inStream) throws ParsingException {
+            if (inStream == null) {
+                throw new ParsingException("inStream == null");
+            }
+
+            final boolean markable = inStream.markSupported();
+            if (markable) {
+                inStream.mark(PKCS7_MARKER.length);
+            }
+
+            final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
+            try {
+                final byte[] buffer = new byte[PKCS7_MARKER.length];
+
+                final int len = pbis.read(buffer);
+                if (len < 0) {
+                    /* No need to reset here. The stream was empty or EOF. */
+                    throw new ParsingException("inStream is empty");
+                }
+                pbis.unread(buffer, 0, len);
+
+                if (buffer[0] == '-') {
+                    if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
+                        List<? extends T> items = fromPkcs7PemInputStream(pbis);
+                        if (items.size() == 0) {
+                            return null;
+                        }
+                        items.get(0);
+                    } else {
+                        return fromX509PemInputStream(pbis);
+                    }
+                }
+
+                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
+                if (buffer[4] == 0x06) {
+                    List<? extends T> certs = fromPkcs7DerInputStream(pbis);
+                    if (certs.size() == 0) {
+                        return null;
+                    }
+                    return certs.get(0);
+                } else {
+                    return fromX509DerInputStream(pbis);
+                }
+            } catch (Exception e) {
+                if (markable) {
+                    try {
+                        inStream.reset();
+                    } catch (IOException ignored) {
+                    }
+                }
+                throw new ParsingException(e);
+            }
+        }
+
+        public Collection<? extends T> generateItems(InputStream inStream)
+                throws ParsingException {
+            try {
+                if (inStream == null || inStream.available() == 0) {
+                    return Collections.emptyList();
+                }
+            } catch (IOException e) {
+                throw new ParsingException("Problem reading input stream", e);
+            }
+
+            final boolean markable = inStream.markSupported();
+            if (markable) {
+                inStream.mark(PUSHBACK_SIZE);
+            }
+
+            /* Attempt to see if this is a PKCS#7 bag. */
+            final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
+            try {
+                final byte[] buffer = new byte[PKCS7_MARKER.length];
+
+                final int len = pbis.read(buffer);
+                if (len < 0) {
+                    /* No need to reset here. The stream was empty or EOF. */
+                    throw new ParsingException("inStream is empty");
+                }
+                pbis.unread(buffer, 0, len);
+
+                if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
+                    return fromPkcs7PemInputStream(pbis);
+                }
+
+                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
+                if (buffer[4] == 0x06) {
+                    return fromPkcs7DerInputStream(pbis);
+                }
+            } catch (Exception e) {
+                if (markable) {
+                    try {
+                        inStream.reset();
+                    } catch (IOException ignored) {
+                    }
+                }
+                throw new ParsingException(e);
+            }
+
+            /*
+             * It wasn't, so just try to keep grabbing certificates until we
+             * can't anymore.
+             */
+            final List<T> coll = new ArrayList<T>();
+            T c = null;
+            do {
+                /*
+                 * If this stream supports marking, try to mark here in case
+                 * there is an error during certificate generation.
+                 */
+                if (markable) {
+                    inStream.mark(PUSHBACK_SIZE);
+                }
+
+                try {
+                    c = generateItem(pbis);
+                    coll.add(c);
+                } catch (ParsingException e) {
+                    /*
+                     * If this stream supports marking, attempt to reset it to
+                     * the mark before the failure.
+                     */
+                    if (markable) {
+                        try {
+                            inStream.reset();
+                        } catch (IOException ignored) {
+                        }
+                    }
+
+                    c = null;
+                }
+            } while (c != null);
+
+            return coll;
+        }
+
+        protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException;
+
+        protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException;
+
+        protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is)
+                throws ParsingException;
+
+        protected abstract List<? extends T> fromPkcs7DerInputStream(InputStream is)
+                throws ParsingException;
+    }
+
+    private Parser<OpenSSLX509Certificate> certificateParser =
+            new Parser<OpenSSLX509Certificate>() {
+                @Override
+                public OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
+                        throws ParsingException {
+                    return OpenSSLX509Certificate.fromX509PemInputStream(is);
+                }
+
+                @Override
+                public OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
+                        throws ParsingException {
+                    return OpenSSLX509Certificate.fromX509DerInputStream(is);
+                }
+
+                @Override
+                public List<? extends OpenSSLX509Certificate>
+                        fromPkcs7PemInputStream(InputStream is) throws ParsingException {
+                    return OpenSSLX509Certificate.fromPkcs7PemInputStream(is);
+                }
+
+                @Override
+                public List<? extends OpenSSLX509Certificate>
+                        fromPkcs7DerInputStream(InputStream is) throws ParsingException {
+                    return OpenSSLX509Certificate.fromPkcs7DerInputStream(is);
+                }
+            };
+
+    private Parser<OpenSSLX509CRL> crlParser =
+            new Parser<OpenSSLX509CRL>() {
+                @Override
+                public OpenSSLX509CRL fromX509PemInputStream(InputStream is)
+                        throws ParsingException {
+                    return OpenSSLX509CRL.fromX509PemInputStream(is);
+                }
+
+                @Override
+                public OpenSSLX509CRL fromX509DerInputStream(InputStream is)
+                        throws ParsingException {
+                    return OpenSSLX509CRL.fromX509DerInputStream(is);
+                }
+
+                @Override
+                public List<? extends OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is)
+                        throws ParsingException {
+                    return OpenSSLX509CRL.fromPkcs7PemInputStream(is);
+                }
+
+                @Override
+                public List<? extends OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is)
+                        throws ParsingException {
+                    return OpenSSLX509CRL.fromPkcs7DerInputStream(is);
+                }
+            };
+
+    @Override
+    public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException {
+        try {
+            return certificateParser.generateItem(inStream);
+        } catch (ParsingException e) {
+            throw new CertificateException(e);
+        }
+    }
+
+    @Override
+    public Collection<? extends Certificate> engineGenerateCertificates(
+            InputStream inStream) throws CertificateException {
+        try {
+            return certificateParser.generateItems(inStream);
+        } catch (ParsingException e) {
+            throw new CertificateException(e);
+        }
+    }
+
+    @Override
+    public CRL engineGenerateCRL(InputStream inStream) throws CRLException {
+        try {
+            return crlParser.generateItem(inStream);
+        } catch (ParsingException e) {
+            throw new CRLException(e);
+        }
+    }
+
+    @Override
+    public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException {
+        try {
+            return crlParser.generateItems(inStream);
+        } catch (ParsingException e) {
+            throw new CRLException(e);
+        }
+    }
+
+    @Override
+    public Iterator<String> engineGetCertPathEncodings() {
+        return OpenSSLX509CertPath.getEncodingsIterator();
+    }
+
+    @Override
+    public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException {
+        return OpenSSLX509CertPath.fromEncoding(inStream);
+    }
+
+    @Override
+    public CertPath engineGenerateCertPath(InputStream inStream, String encoding)
+            throws CertificateException {
+        return OpenSSLX509CertPath.fromEncoding(inStream, encoding);
+    }
+
+    @Override
+    public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)
+            throws CertificateException {
+        final List<X509Certificate> filtered = new ArrayList<X509Certificate>(certificates.size());
+        for (int i = 0; i < certificates.size(); i++) {
+            final Certificate c = certificates.get(i);
+
+            if (!(c instanceof X509Certificate)) {
+                throw new CertificateException("Certificate not X.509 type at index " + i);
+            }
+
+            filtered.add((X509Certificate) c);
+        }
+
+        return new OpenSSLX509CertPath(filtered);
+    }
+}
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 0218249..1682df7 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
@@ -277,6 +277,10 @@
                     "Trust anchor for certification path not found.", null, certPath, -1));
         }
 
+        // There's no point in checking trust anchors here, and it will throw off the MD5 check,
+        // so we just hand it the chain without anchors
+        ChainStrengthAnalyzer.check(newChain);
+
         try {
             PKIXParameters params = new PKIXParameters(trustAnchor);
             params.setRevocationEnabled(false);
diff --git a/luni/src/main/java/org/w3c/dom/package.html b/luni/src/main/java/org/w3c/dom/package.html
deleted file mode 100644
index 15f8ff6..0000000
--- a/luni/src/main/java/org/w3c/dom/package.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-  <body>
-    <p>
-      Provides the official W3C Java bindings for the Document Object Model,
-      level 2 core. XML documents returned by
-      {@link javax.xml.parsers.DocumentBuilder} are accessed and manipulated
-      through these interfaces.
-    </p>
-  </body>
-</html>
diff --git a/luni/src/main/java/org/xml/sax/ext/package.html b/luni/src/main/java/org/xml/sax/ext/package.html
deleted file mode 100644
index e443df4..0000000
--- a/luni/src/main/java/org/xml/sax/ext/package.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<HTML><HEAD>
-<!-- $Id: package.html,v 1.8 2002/01/30 21:00:44 dbrownell Exp $ -->
-</HEAD><BODY>
-
-<p>
-This package contains interfaces to SAX2 facilities that
-conformant SAX drivers won't necessarily support.
-
-<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
-for more information about SAX.</p>
-
-<p> This package is independent of the SAX2 core, though the functionality
-exposed generally needs to be implemented within a parser core.
-That independence has several consequences:</p>
-
-<ul>
-
-<li>SAX2 drivers are <em>not</em> required to recognize these handlers.
-</li>
-
-<li>You cannot assume that the class files will be present in every SAX2
-installation.</li>
-
-<li>This package may be updated independently of SAX2 (i.e. new
-handlers and classes may be added without updating SAX2 itself).</li>
-
-<li>The new handlers are not implemented by the SAX2
-<code>org.xml.sax.helpers.DefaultHandler</code> or
-<code>org.xml.sax.helpers.XMLFilterImpl</code> classes.
-You can subclass these if you need such behavior, or
-use the helper classes found here.</li>
-
-<li>The handlers need to be registered differently than core SAX2
-handlers.</li>
-
-</ul>
-
-<p>This package, SAX2-ext, is a standardized extension to SAX2.  It is
-designed both to allow SAX parsers to pass certain types of information
-to applications, and to serve as a simple model for other SAX2 parser
-extension packages.  Not all such extension packages should need to
-be recognized directly by parsers, however.
-As an example, most validation systems can be cleanly layered on top
-of parsers supporting the standardized SAX2 interfaces.  </p>
-
-</BODY></HTML>
diff --git a/luni/src/main/java/org/xml/sax/helpers/package.html b/luni/src/main/java/org/xml/sax/helpers/package.html
deleted file mode 100644
index 8f323c0..0000000
--- a/luni/src/main/java/org/xml/sax/helpers/package.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<HTML><HEAD>
-<!-- $Id: package.html,v 1.6 2002/01/30 20:52:39 dbrownell Exp $ -->
-</HEAD><BODY>
-
-<p>This package contains "helper" classes, including
-support for bootstrapping SAX-based applications.
-
-<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
-for more information about SAX.</p>
-
-</BODY></HTML>
diff --git a/luni/src/main/java/org/xml/sax/package.html b/luni/src/main/java/org/xml/sax/package.html
deleted file mode 100644
index 7d7f257..0000000
--- a/luni/src/main/java/org/xml/sax/package.html
+++ /dev/null
@@ -1,297 +0,0 @@
-<html><head>
-<!-- $Id: package.html,v 1.18 2004/04/21 13:06:01 dmegginson Exp $ -->
-</head><body>
-
-<p> This package provides the core SAX APIs.
-Some SAX1 APIs are deprecated to encourage integration of
-namespace-awareness into designs of new applications
-and into maintenance of existing infrastructure. </p>
-
-<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
-for more information about SAX.</p>
-
-
-<h2> SAX2 Standard Feature Flags </h2>
-
-<p> One of the essential characteristics of SAX2 is that it added
-feature flags which can be used to examine and perhaps modify
-parser modes, in particular modes such as validation.
-Since features are identified by (absolute) URIs, anyone
-can define such features.   
-Currently defined standard feature URIs have the prefix
-<code>http://xml.org/sax/features/</code> before an identifier such as
-<code>validation</code>.  Turn features on or off using
-<em>setFeature</em>.  Those standard identifiers are: </p>
-
-
-<table border="1" cellpadding="3" cellspacing="0" width="100%">
-    <tr align="center" bgcolor="#ccccff">
-	<th>Feature ID</th>
-	<th>Access</th>
-	<th>Default</th>
-	<th>Description</th>
-	</tr>
-
-    <tr>
-	<td>external-general-entities</td>
-	<td><em>read/write</em></td>
-	<td><em>unspecified</em></td>
-	<td> Reports whether this parser processes external
-	    general entities; always true if validating.
-		</td>
-	</tr>
-
-    <tr>
-	<td>external-parameter-entities</td>
-	<td><em>read/write</em></td>
-	<td><em>unspecified</em></td>
-	<td> Reports whether this parser processes external
-	    parameter entities; always true if validating.
-		</td>
-	</tr>
-
-    <tr>
-	<td>is-standalone</td>
-	<td>(parsing) <em>read-only</em>, (not parsing) <em>none</em></td>
-	<td>not applicable</td>
-	<td> May be examined only during a parse, after the
-	    <em>startDocument()</em> callback has been completed; read-only.
-	    The value is true if the document specified standalone="yes" in 
-	    its XML declaration, and otherwise is false.
-		</td>
-	</tr>
-
-    <tr>
-	<td>lexical-handler/parameter-entities</td>
-	<td><em>read/write</em></td>
-	<td><em>unspecified</em></td>
-	<td> A value of "true" indicates that the LexicalHandler will report
-	    the beginning and end of parameter entities.
-		</td>
-	</tr>
-
-    <tr>
-	<td>namespaces</td>
-	<td><em>read/write</em></td>
-	<td>true</td>
-	<td> A value of "true" indicates namespace URIs and unprefixed local names
-	    for element and attribute names will be available.
-		</td>
-	</tr>
-
-    <tr>
-	<td>namespace-prefixes</td>
-	<td><em>read/write</em></td>
-	<td>false</td>
-	<td> A value of "true" indicates that XML qualified names (with prefixes) and
-	    attributes (including <em>xmlns*</em> attributes) will be available.
-		</td>
-	</tr>
-
-    <tr>
-	<td>resolve-dtd-uris</td>
-	<td><em>read/write</em></td>
-	<td><em>true</em></td>
-	<td> A value of "true" indicates that system IDs in declarations will
-	    be absolutized (relative to their base URIs) before reporting.
-	    (That is the default behavior for all SAX2 XML parsers.)
-	    A value of "false" indicates those IDs will not be absolutized;
-	    parsers will provide the base URI from
-	    <em>Locator.getSystemId()</em>.
-	    This applies to system IDs passed in <ul>
-		<li><em>DTDHandler.notationDecl()</em>,
-		<li><em>DTDHandler.unparsedEntityDecl()</em>, and
-		<li><em>DeclHandler.externalEntityDecl()</em>.
-	    </ul>
-	    It does not apply to <em>EntityResolver.resolveEntity()</em>,
-	    which is not used to report declarations, or to
-	    <em>LexicalHandler.startDTD()</em>, which already provides
-	    the non-absolutized URI.
-	    </td>
-	</tr>
-
-    <tr>
-	<td>string-interning</td>
-	<td><em>read/write</em></td>
-	<td><em>unspecified</em></td>
-	<td> Has a value of "true" if all XML names (for elements, prefixes,
-	    attributes, entities, notations, and local names),
-	    as well as Namespace URIs, will have been interned
-	    using <em>java.lang.String.intern</em>. This supports fast
-	    testing of equality/inequality against string constants,
-	    rather than forcing slower calls to <em>String.equals()</em>.
-	    </td>
-	</tr>
-
-    <tr>
-    <td>unicode-normalization-checking</td>
-    <td><em>read/write</em></td>
-    <td><em>false</em></td>
-    <td> Controls whether the parser reports Unicode normalization 
-        errors as described in section 2.13 and Appendix B of the 
-        XML 1.1 Recommendation. If true, Unicode normalization
-        errors are reported using the ErrorHandler.error() callback.
-        Such errors are not fatal in themselves (though, obviously,
-        other Unicode-related encoding errors may be).
-		</td>
-    </tr>
-    
-    <tr>
-	<td>use-attributes2</td>
-	<td><em>read-only</em></td>
-	<td>not applicable</td>
-	<td> Returns "true" if the <em>Attributes</em> objects passed by
-	    this parser in <em>ContentHandler.startElement()</em>
-	    implement the <a href="ext/Attributes2.html"
-	    ><em>org.xml.sax.ext.Attributes2</em></a> interface.
-	    That interface exposes additional DTD-related information,
-	    such as whether the attribute was specified in the
-	    source text rather than defaulted.
-		</td>
-	</tr>
-
-    <tr>
-	<td>use-locator2</td>
-	<td><em>read-only</em></td>
-	<td>not applicable</td>
-	<td> Returns "true" if the <em>Locator</em> objects passed by
-	    this parser in <em>ContentHandler.setDocumentLocator()</em>
-	    implement the <a href="ext/Locator2.html"
-	    ><em>org.xml.sax.ext.Locator2</em></a> interface.
-	    That interface exposes additional entity information,
-	    such as the character encoding and XML version used.
-		</td>
-	</tr>
-
-    <tr>
-	<td>use-entity-resolver2</td>
-	<td><em>read/write</em></td>
-	<td><em>true</em></td>
-	<td> Returns "true" if, when <em>setEntityResolver</em> is given
-	    an object implementing the <a href="ext/EntityResolver2.html"
-	    ><em>org.xml.sax.ext.EntityResolver2</em></a> interface,
-	    those new methods will be used.
-	    Returns "false" to indicate that those methods will not be used.
-		</td>
-	</tr>
-
-    <tr>
-	<td>validation</td>
-	<td><em>read/write</em></td>
-	<td><em>unspecified</em></td>
-	<td> Controls whether the parser is reporting all validity
-	    errors; if true, all external entities will be read.
-		</td>
-	</tr>
-
-    <tr>
-	<td>xmlns-uris</td>
-	<td><em>read/write</em></td>
-	<td><em>false</em></td>
-	<td> Controls whether, when the <em>namespace-prefixes</em> feature
-	    is set, the parser treats namespace declaration attributes as
-	    being in the <em>http://www.w3.org/2000/xmlns/</em> namespace.
-	    By default, SAX2 conforms to the original "Namespaces in XML"
-	    Recommendation, which explicitly states that such attributes are
-	    not in any namespace.
-	    Setting this optional flag to "true" makes the SAX2 events conform to
-	    a later backwards-incompatible revision of that recommendation,
-	    placing those attributes in a namespace.
-		</td>
-	</tr>
-
-    <tr>
-    <td>xml-1.1</td>
-    <td><em>read-only</em></td>
-    <td>not applicable</td>
-    <td> Returns "true" if the parser supports both XML 1.1 and XML 1.0.
-        Returns "false" if the parser supports only XML 1.0.
-		</td>
-    </tr>
-
-</table>
-
-<p> Support for the default values of the
-<em>namespaces</em> and <em>namespace-prefixes</em>
-properties is required.
-Support for any other feature flags is entirely optional.
-</p>
-
-<p> For default values not specified by SAX2,
-each XMLReader implementation specifies its default,
-or may choose not to expose the feature flag.
-Unless otherwise specified here,
-implementations may support changing current values
-of these standard feature flags, but not while parsing.
-</p>
-
-<h2> SAX2 Standard Handler and Property IDs </h2>
-
-<p> For parser interface characteristics that are described
-as objects, a separate namespace is defined.  The
-objects in this namespace are again identified by URI, and
-the standard property URIs have the prefix
-<code>http://xml.org/sax/properties/</code> before an identifier such as
-<code>lexical-handler</code> or
-<code>dom-node</code>.  Manage those properties using
-<em>setProperty()</em>.  Those identifiers are: </p>
-
-<table border="1" cellpadding="3" cellspacing="0" width="100%">
-    <tr align="center" bgcolor="#ccccff">
-	<th>Property ID</th>
-	<th>Description</th>
-	</tr>
-
-    <tr>
-	<td>declaration-handler</td>
-	<td> Used to see most DTD declarations except those treated
-	    as lexical ("document element name is ...") or which are
-	    mandatory for all SAX parsers (<em>DTDHandler</em>).
-	    The Object must implement <a href="ext/DeclHandler.html"
-	    ><em>org.xml.sax.ext.DeclHandler</em></a>.
-	    </td>
-	</tr>
-
-    <tr>
-        <td>document-xml-version</td>
-        <td> May be examined only during a parse, after the startDocument()
-            callback has been completed; read-only. This property is a 
-            literal string describing the actual XML version of the document, 
-            such as "1.0" or "1.1".
-            </td>
-        </tr>
-    
-    <tr>
-	<td>dom-node</td>
-	<td> For "DOM Walker" style parsers, which ignore their
-	    <em>parser.parse()</em> parameters, this is used to
-	    specify the DOM (sub)tree being walked by the parser.
-	    The Object must implement the
-	    <em>org.w3c.dom.Node</em> interface.
-	    </td>
-	</tr>
-
-    <tr>
-	<td>lexical-handler</td>
-	<td> Used to see some syntax events that are essential in some
-	    applications:  comments, CDATA delimiters, selected general
-	    entity inclusions, and the start and end of the DTD
-	    (and declaration of document element name).
-	    The Object must implement <a href="ext/LexicalHandler.html"
-	    ><em>org.xml.sax.ext.LexicalHandler</em></a>.
-	    </td>
-	</tr>
-
-    <tr>
-	<td>xml-string</td>
-	<td> Readable only during a parser callback, this exposes a <b>TBS</b>
-	    chunk of characters responsible for the current event. </td>
-	</tr>
-
-</table>
-
-<p> All of these standard properties are optional;
-XMLReader implementations need not support them.
-</p>
-
-</body></html>
diff --git a/luni/src/main/java/sun/misc/Unsafe.java b/luni/src/main/java/sun/misc/Unsafe.java
index 33bb9f1..884340b 100644
--- a/luni/src/main/java/sun/misc/Unsafe.java
+++ b/luni/src/main/java/sun/misc/Unsafe.java
@@ -27,8 +27,10 @@
  * of Java.
  */
 public final class Unsafe {
-    /** non-null; unique instance of this class */
+    /** Traditional dalvik name. */
     private static final Unsafe THE_ONE = new Unsafe();
+    /** Traditional RI name. */
+    private static final Unsafe theUnsafe = THE_ONE;
 
     /**
      * This class is only privately instantiable.
@@ -339,4 +341,10 @@
             throw new IllegalArgumentException("valid for Threads only");
         }
     }
+
+    /**
+     * Allocates an instance of the given class without running the constructor.
+     * The class' <clinit> will be run, if necessary.
+     */
+    public native Object allocateInstance(Class<?> c);
 }
diff --git a/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp b/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp
index 7b0f602..9617e9d 100644
--- a/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp
+++ b/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp
@@ -37,7 +37,11 @@
 /**
  * The specific signal chosen here is arbitrary.
  */
+#if defined(__APPLE__)
+static const int BLOCKED_THREAD_SIGNAL = SIGUSR2;
+#else
 static const int BLOCKED_THREAD_SIGNAL = SIGRTMIN + 2;
+#endif
 
 static void blockedThreadSignalHandler(int /*signal*/) {
     // Do nothing. We only sent this signal for its side-effect of interrupting syscalls.
diff --git a/luni/src/main/native/IcuUtilities.cpp b/luni/src/main/native/IcuUtilities.cpp
new file mode 100644
index 0000000..ef9909b
--- /dev/null
+++ b/luni/src/main/native/IcuUtilities.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IcuUtilities"
+
+#include "IcuUtilities.h"
+
+#include "JniConstants.h"
+#include "JniException.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "UniquePtr.h"
+#include "cutils/log.h"
+#include "unicode/strenum.h"
+#include "unicode/uloc.h"
+#include "unicode/ustring.h"
+
+Locale getLocale(JNIEnv* env, jstring localeName) {
+  return Locale::createFromName(ScopedUtfChars(env, localeName).c_str());
+}
+
+jobjectArray fromStringEnumeration(JNIEnv* env, StringEnumeration* se) {
+  UniquePtr<StringEnumeration> deleter(se);
+  if (se == NULL) {
+    return NULL;
+  }
+
+  UErrorCode status = U_ZERO_ERROR;
+  int32_t count = se->count(status);
+  if (maybeThrowIcuException(env, "StringEnumeration::count", status)) {
+    return NULL;
+  }
+
+  jobjectArray result = env->NewObjectArray(count, JniConstants::stringClass, NULL);
+  for (int32_t i = 0; i < count; ++i) {
+    const UnicodeString* string = se->snext(status);
+    if (maybeThrowIcuException(env, "StringEnumeration::snext", status)) {
+      return NULL;
+    }
+    ScopedLocalRef<jstring> javaString(env, env->NewString(string->getBuffer(), string->length()));
+    env->SetObjectArrayElement(result, i, javaString.get());
+  }
+  return result;
+}
+
+bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error) {
+  if (U_SUCCESS(error)) {
+    return false;
+  }
+  const char* exceptionClass = "java/lang/RuntimeException";
+  if (error == U_ILLEGAL_ARGUMENT_ERROR) {
+    exceptionClass = "java/lang/IllegalArgumentException";
+  } else if (error == U_INDEX_OUTOFBOUNDS_ERROR || error == U_BUFFER_OVERFLOW_ERROR) {
+    exceptionClass = "java/lang/ArrayIndexOutOfBoundsException";
+  } else if (error == U_UNSUPPORTED_ERROR) {
+    exceptionClass = "java/lang/UnsupportedOperationException";
+  }
+  jniThrowExceptionFmt(env, exceptionClass, "%s failed: %s", function, u_errorName(error));
+  return true;
+}
diff --git a/luni/src/main/native/IcuUtilities.h b/luni/src/main/native/IcuUtilities.h
new file mode 100644
index 0000000..219d663
--- /dev/null
+++ b/luni/src/main/native/IcuUtilities.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ICU_UTILITIES_H_included
+#define ICU_UTILITIES_H_included
+
+#include "jni.h"
+#include "unicode/utypes.h" // For UErrorCode.
+#include "unicode/locid.h" // For Locale.
+
+extern Locale getLocale(JNIEnv* env, jstring localeName);
+extern jobjectArray fromStringEnumeration(JNIEnv* env, StringEnumeration*);
+bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error);
+
+#endif  // ICU_UTILITIES_H_included
diff --git a/luni/src/main/native/JniConstants.cpp b/luni/src/main/native/JniConstants.cpp
index 6815734..8693cf5 100644
--- a/luni/src/main/native/JniConstants.cpp
+++ b/luni/src/main/native/JniConstants.cpp
@@ -24,6 +24,7 @@
 jclass JniConstants::booleanClass;
 jclass JniConstants::byteArrayClass;
 jclass JniConstants::byteClass;
+jclass JniConstants::calendarClass;
 jclass JniConstants::charsetICUClass;
 jclass JniConstants::constructorClass;
 jclass JniConstants::deflaterClass;
@@ -36,19 +37,23 @@
 jclass JniConstants::inet6AddressClass;
 jclass JniConstants::inetAddressClass;
 jclass JniConstants::inetSocketAddressClass;
+jclass JniConstants::inetUnixAddressClass;
 jclass JniConstants::inflaterClass;
+jclass JniConstants::inputStreamClass;
 jclass JniConstants::integerClass;
 jclass JniConstants::localeDataClass;
 jclass JniConstants::longClass;
 jclass JniConstants::methodClass;
 jclass JniConstants::mutableIntClass;
 jclass JniConstants::mutableLongClass;
+jclass JniConstants::objectClass;
+jclass JniConstants::objectArrayClass;
+jclass JniConstants::outputStreamClass;
 jclass JniConstants::parsePositionClass;
 jclass JniConstants::patternSyntaxExceptionClass;
 jclass JniConstants::realToStringClass;
 jclass JniConstants::socketClass;
 jclass JniConstants::socketImplClass;
-jclass JniConstants::stringArrayClass;
 jclass JniConstants::stringClass;
 jclass JniConstants::structAddrinfoClass;
 jclass JniConstants::structFlockClass;
@@ -59,6 +64,7 @@
 jclass JniConstants::structStatClass;
 jclass JniConstants::structStatFsClass;
 jclass JniConstants::structTimevalClass;
+jclass JniConstants::structUcredClass;
 jclass JniConstants::structUtsnameClass;
 
 static jclass findClass(JNIEnv* env, const char* name) {
@@ -77,6 +83,7 @@
     booleanClass = findClass(env, "java/lang/Boolean");
     byteClass = findClass(env, "java/lang/Byte");
     byteArrayClass = findClass(env, "[B");
+    calendarClass = findClass(env, "java/util/Calendar");
     charsetICUClass = findClass(env, "java/nio/charset/CharsetICU");
     constructorClass = findClass(env, "java/lang/reflect/Constructor");
     deflaterClass = findClass(env, "java/util/zip/Deflater");
@@ -89,19 +96,23 @@
     inet6AddressClass = findClass(env, "java/net/Inet6Address");
     inetAddressClass = findClass(env, "java/net/InetAddress");
     inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress");
+    inetUnixAddressClass = findClass(env, "java/net/InetUnixAddress");
     inflaterClass = findClass(env, "java/util/zip/Inflater");
+    inputStreamClass = findClass(env, "java/io/InputStream");
     integerClass = findClass(env, "java/lang/Integer");
     localeDataClass = findClass(env, "libcore/icu/LocaleData");
     longClass = findClass(env, "java/lang/Long");
     methodClass = findClass(env, "java/lang/reflect/Method");
     mutableIntClass = findClass(env, "libcore/util/MutableInt");
     mutableLongClass = findClass(env, "libcore/util/MutableLong");
+    objectClass = findClass(env, "java/lang/Object");
+    objectArrayClass = findClass(env, "[Ljava/lang/Object;");
+    outputStreamClass = findClass(env, "java/io/OutputStream");
     parsePositionClass = findClass(env, "java/text/ParsePosition");
     patternSyntaxExceptionClass = findClass(env, "java/util/regex/PatternSyntaxException");
     realToStringClass = findClass(env, "java/lang/RealToString");
     socketClass = findClass(env, "java/net/Socket");
     socketImplClass = findClass(env, "java/net/SocketImpl");
-    stringArrayClass = findClass(env, "[Ljava/lang/String;");
     stringClass = findClass(env, "java/lang/String");
     structAddrinfoClass = findClass(env, "libcore/io/StructAddrinfo");
     structFlockClass = findClass(env, "libcore/io/StructFlock");
@@ -112,5 +123,6 @@
     structStatClass = findClass(env, "libcore/io/StructStat");
     structStatFsClass = findClass(env, "libcore/io/StructStatFs");
     structTimevalClass = findClass(env, "libcore/io/StructTimeval");
+    structUcredClass = findClass(env, "libcore/io/StructUcred");
     structUtsnameClass = findClass(env, "libcore/io/StructUtsname");
 }
diff --git a/luni/src/main/native/JniConstants.h b/luni/src/main/native/JniConstants.h
index db52775..8fd7bc8 100644
--- a/luni/src/main/native/JniConstants.h
+++ b/luni/src/main/native/JniConstants.h
@@ -45,6 +45,7 @@
     static jclass booleanClass;
     static jclass byteArrayClass;
     static jclass byteClass;
+    static jclass calendarClass;
     static jclass charsetICUClass;
     static jclass constructorClass;
     static jclass deflaterClass;
@@ -57,19 +58,23 @@
     static jclass inet6AddressClass;
     static jclass inetAddressClass;
     static jclass inetSocketAddressClass;
+    static jclass inetUnixAddressClass;
     static jclass inflaterClass;
+    static jclass inputStreamClass;
     static jclass integerClass;
     static jclass localeDataClass;
     static jclass longClass;
     static jclass methodClass;
     static jclass mutableIntClass;
     static jclass mutableLongClass;
+    static jclass objectClass;
+    static jclass objectArrayClass;
+    static jclass outputStreamClass;
     static jclass parsePositionClass;
     static jclass patternSyntaxExceptionClass;
     static jclass realToStringClass;
     static jclass socketClass;
     static jclass socketImplClass;
-    static jclass stringArrayClass;
     static jclass stringClass;
     static jclass structAddrinfoClass;
     static jclass structFlockClass;
@@ -80,6 +85,7 @@
     static jclass structStatClass;
     static jclass structStatFsClass;
     static jclass structTimevalClass;
+    static jclass structUcredClass;
     static jclass structUtsnameClass;
 };
 
diff --git a/luni/src/main/native/JniException.cpp b/luni/src/main/native/JniException.cpp
index c733db4..8f97891 100644
--- a/luni/src/main/native/JniException.cpp
+++ b/luni/src/main/native/JniException.cpp
@@ -17,29 +17,6 @@
 #include "JniException.h"
 #include "JNIHelp.h"
 
-bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error) {
-    if (U_SUCCESS(error)) {
-        return false;
-    }
-    const char* exceptionClass;
-    switch (error) {
-    case U_ILLEGAL_ARGUMENT_ERROR:
-        exceptionClass = "java/lang/IllegalArgumentException";
-        break;
-    case U_INDEX_OUTOFBOUNDS_ERROR:
-    case U_BUFFER_OVERFLOW_ERROR:
-        exceptionClass = "java/lang/ArrayIndexOutOfBoundsException";
-        break;
-    case U_UNSUPPORTED_ERROR:
-        exceptionClass = "java/lang/UnsupportedOperationException";
-        break;
-    default:
-        exceptionClass = "java/lang/RuntimeException";
-        break;
-    }
-    return jniThrowExceptionFmt(env, exceptionClass, "%s failed: %s", function, u_errorName(error));
-}
-
 void jniThrowExceptionWithErrno(JNIEnv* env, const char* exceptionClassName, int error) {
     char buf[BUFSIZ];
     jniThrowException(env, exceptionClassName, jniStrError(error, buf, sizeof(buf)));
diff --git a/luni/src/main/native/JniException.h b/luni/src/main/native/JniException.h
index cece2aa..2d4a097 100644
--- a/luni/src/main/native/JniException.h
+++ b/luni/src/main/native/JniException.h
@@ -18,13 +18,10 @@
 #define JNI_EXCEPTION_H_included
 
 #include "jni.h"
-#include "unicode/utypes.h" // For UErrorCode.
 
 void jniThrowExceptionWithErrno(JNIEnv* env, const char* exceptionClassName, int error);
 
 void jniThrowOutOfMemoryError(JNIEnv* env, const char* message);
 void jniThrowSocketException(JNIEnv* env, int error);
 
-bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error);
-
 #endif  // JNI_EXCEPTION_H_included
diff --git a/luni/src/main/native/NetworkUtilities.cpp b/luni/src/main/native/NetworkUtilities.cpp
index 9f4d770..7215306 100644
--- a/luni/src/main/native/NetworkUtilities.cpp
+++ b/luni/src/main/native/NetworkUtilities.cpp
@@ -26,45 +26,50 @@
 #include <stdio.h>
 #include <string.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 
-jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage* ss, jint* port) {
+jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage& ss, jint* port) {
     // Convert IPv4-mapped IPv6 addresses to IPv4 addresses.
     // The RI states "Java will never return an IPv4-mapped address".
-    sockaddr_storage tmp;
-    memset(&tmp, 0, sizeof(tmp));
-    const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(ss);
-    if (ss->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+    const sockaddr_in6& sin6 = reinterpret_cast<const sockaddr_in6&>(ss);
+    if (ss.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) {
         // Copy the IPv6 address into the temporary sockaddr_storage.
-        memcpy(&tmp, ss, sizeof(sockaddr_in6));
+        sockaddr_storage tmp;
+        memset(&tmp, 0, sizeof(tmp));
+        memcpy(&tmp, &ss, sizeof(sockaddr_in6));
         // Unmap it into an IPv4 address.
-        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&tmp);
-        sin->sin_family = AF_INET;
-        sin->sin_port = sin6->sin6_port;
-        memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
-        // Fall through into the regular conversion using the unmapped address.
-        ss = &tmp;
+        sockaddr_in& sin = reinterpret_cast<sockaddr_in&>(tmp);
+        sin.sin_family = AF_INET;
+        sin.sin_port = sin6.sin6_port;
+        memcpy(&sin.sin_addr.s_addr, &sin6.sin6_addr.s6_addr[12], 4);
+        // Do the regular conversion using the unmapped address.
+        return sockaddrToInetAddress(env, tmp, port);
     }
 
     const void* rawAddress;
     size_t addressLength;
-    int sin_port;
+    int sin_port = 0;
     int scope_id = 0;
-    if (ss->ss_family == AF_INET) {
-        const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(ss);
-        rawAddress = &sin->sin_addr.s_addr;
+    if (ss.ss_family == AF_INET) {
+        const sockaddr_in& sin = reinterpret_cast<const sockaddr_in&>(ss);
+        rawAddress = &sin.sin_addr.s_addr;
         addressLength = 4;
-        sin_port = ntohs(sin->sin_port);
-    } else if (ss->ss_family == AF_INET6) {
-        const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(ss);
-        rawAddress = &sin6->sin6_addr.s6_addr;
+        sin_port = ntohs(sin.sin_port);
+    } else if (ss.ss_family == AF_INET6) {
+        const sockaddr_in6& sin6 = reinterpret_cast<const sockaddr_in6&>(ss);
+        rawAddress = &sin6.sin6_addr.s6_addr;
         addressLength = 16;
-        sin_port = ntohs(sin6->sin6_port);
-        scope_id = sin6->sin6_scope_id;
+        sin_port = ntohs(sin6.sin6_port);
+        scope_id = sin6.sin6_scope_id;
+    } else if (ss.ss_family == AF_UNIX) {
+        const sockaddr_un& sun = reinterpret_cast<const sockaddr_un&>(ss);
+        rawAddress = &sun.sun_path;
+        addressLength = strlen(sun.sun_path);
     } else {
         // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one
         // really does imply an internal error.
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "sockaddrToInetAddress bad ss_family: %i", ss->ss_family);
+                             "sockaddrToInetAddress unsupported ss_family: %i", ss.ss_family);
         return NULL;
     }
     if (port != NULL) {
@@ -78,6 +83,14 @@
     env->SetByteArrayRegion(byteArray.get(), 0, addressLength,
             reinterpret_cast<const jbyte*>(rawAddress));
 
+    if (ss.ss_family == AF_UNIX) {
+        // Note that we get here for AF_UNIX sockets on accept(2). The unix(7) man page claims
+        // that the peer's sun_path will contain the path, but in practice it doesn't, and the
+        // peer length is returned as 2 (meaning only the sun_family field was set).
+        static jmethodID ctor = env->GetMethodID(JniConstants::inetUnixAddressClass, "<init>", "([B)V");
+        return env->NewObject(JniConstants::inetUnixAddressClass, ctor, byteArray.get());
+    }
+
     static jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inetAddressClass,
             "getByAddress", "(Ljava/lang/String;[BI)Ljava/net/InetAddress;");
     if (getByAddressMethod == NULL) {
@@ -87,8 +100,9 @@
             NULL, byteArray.get(), scope_id);
 }
 
-static bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss, bool map) {
-    memset(ss, 0, sizeof(*ss));
+static bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage& ss, socklen_t& sa_len, bool map) {
+    memset(&ss, 0, sizeof(ss));
+    sa_len = 0;
 
     if (inetAddress == NULL) {
         jniThrowNullPointerException(env, NULL);
@@ -97,15 +111,16 @@
 
     // Get the address family.
     static jfieldID familyFid = env->GetFieldID(JniConstants::inetAddressClass, "family", "I");
-    ss->ss_family = env->GetIntField(inetAddress, familyFid);
-    if (ss->ss_family == AF_UNSPEC) {
+    ss.ss_family = env->GetIntField(inetAddress, familyFid);
+    if (ss.ss_family == AF_UNSPEC) {
+        sa_len = sizeof(ss.ss_family);
         return true; // Job done!
     }
 
     // Check this is an address family we support.
-    if (ss->ss_family != AF_INET && ss->ss_family != AF_INET6) {
+    if (ss.ss_family != AF_INET && ss.ss_family != AF_INET6 && ss.ss_family != AF_UNIX) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "inetAddressToSockaddr bad family: %i", ss->ss_family);
+                "inetAddressToSockaddr bad family: %i", ss.ss_family);
         return false;
     }
 
@@ -117,16 +132,42 @@
         return false;
     }
 
+    // Handle the AF_UNIX special case.
+    if (ss.ss_family == AF_UNIX) {
+        sockaddr_un& sun = reinterpret_cast<sockaddr_un&>(ss);
+
+        size_t path_length = env->GetArrayLength(addressBytes.get());
+        if (path_length >= sizeof(sun.sun_path)) {
+            jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                                 "inetAddressToSockaddr path too long for AF_UNIX: %i", path_length);
+            return false;
+        }
+
+        // Copy the bytes...
+        jbyte* dst = reinterpret_cast<jbyte*>(&sun.sun_path);
+        memset(dst, 0, sizeof(sun.sun_path));
+        env->GetByteArrayRegion(addressBytes.get(), 0, path_length, dst);
+        sa_len = sizeof(sun.sun_path);
+        return true;
+    }
+
+    // TODO: bionic's getnameinfo(3) seems to want its length parameter to be exactly
+    // sizeof(sockaddr_in) for an IPv4 address and sizeof (sockaddr_in6) for an
+    // IPv6 address. Fix getnameinfo so it accepts sizeof(sockaddr_storage), and
+    // then unconditionally set sa_len to sizeof(sockaddr_storage) instead of having
+    // to deal with this case by case.
+
     // We use AF_INET6 sockets, so we want an IPv6 address (which may be a IPv4-mapped address).
-    sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss);
-    sin6->sin6_port = htons(port);
-    if (ss->ss_family == AF_INET6) {
+    sockaddr_in6& sin6 = reinterpret_cast<sockaddr_in6&>(ss);
+    sin6.sin6_port = htons(port);
+    if (ss.ss_family == AF_INET6) {
         // IPv6 address. Copy the bytes...
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin6.sin6_addr.s6_addr);
         env->GetByteArrayRegion(addressBytes.get(), 0, 16, dst);
         // ...and set the scope id...
         static jfieldID scopeFid = env->GetFieldID(JniConstants::inet6AddressClass, "scope_id", "I");
-        sin6->sin6_scope_id = env->GetIntField(inetAddress, scopeFid);
+        sin6.sin6_scope_id = env->GetIntField(inetAddress, scopeFid);
+        sa_len = sizeof(sockaddr_in6);
         return true;
     }
 
@@ -134,31 +175,33 @@
     if (map) {
         // We should represent this Inet4Address as an IPv4-mapped IPv6 sockaddr_in6.
         // Change the family...
-        sin6->sin6_family = AF_INET6;
+        sin6.sin6_family = AF_INET6;
         // Copy the bytes...
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr[12]);
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin6.sin6_addr.s6_addr[12]);
         env->GetByteArrayRegion(addressBytes.get(), 0, 4, dst);
         // INADDR_ANY and in6addr_any are both all-zeros...
-        if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+        if (!IN6_IS_ADDR_UNSPECIFIED(&sin6.sin6_addr)) {
             // ...but all other IPv4-mapped addresses are ::ffff:a.b.c.d, so insert the ffff...
-            memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
+            memset(&(sin6.sin6_addr.s6_addr[10]), 0xff, 2);
         }
+        sa_len = sizeof(sockaddr_in6);
     } else {
         // We should represent this Inet4Address as an IPv4 sockaddr_in.
-        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss);
-        sin->sin_port = htons(port);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
+        sockaddr_in& sin = reinterpret_cast<sockaddr_in&>(ss);
+        sin.sin_port = htons(port);
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin.sin_addr.s_addr);
         env->GetByteArrayRegion(addressBytes.get(), 0, 4, dst);
+        sa_len = sizeof(sockaddr_in);
     }
     return true;
 }
 
-bool inetAddressToSockaddrVerbatim(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) {
-    return inetAddressToSockaddr(env, inetAddress, port, ss, false);
+bool inetAddressToSockaddrVerbatim(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage& ss, socklen_t& sa_len) {
+    return inetAddressToSockaddr(env, inetAddress, port, ss, sa_len, false);
 }
 
-bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) {
-    return inetAddressToSockaddr(env, inetAddress, port, ss, true);
+bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage& ss, socklen_t& sa_len) {
+    return inetAddressToSockaddr(env, inetAddress, port, ss, sa_len, true);
 }
 
 bool setBlocking(int fd, bool blocking) {
diff --git a/luni/src/main/native/NetworkUtilities.h b/luni/src/main/native/NetworkUtilities.h
index 1ae6c53..28e9fa5 100644
--- a/luni/src/main/native/NetworkUtilities.h
+++ b/luni/src/main/native/NetworkUtilities.h
@@ -17,20 +17,27 @@
 #include "jni.h"
 #include <sys/socket.h>
 
-// Convert from sockaddr_storage to InetAddress and an optional int port.
-jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage* ss, int* port);
+// Convert from sockaddr_storage to Inet4Address (AF_INET), Inet6Address (AF_INET6), or
+// InetUnixAddress (AF_UNIX). If 'port' is non-NULL and the address family includes a notion
+// of port number, *port will be set to the port number.
+jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage& ss, int* port);
 
-// Convert from InetAddress to sockaddr_storage. An Inet4Address will be converted to
-// an IPv4-mapped AF_INET6 sockaddr_in6. This is what you want if you're about to perform an
-// operation on a socket, since all our sockets are AF_INET6.
-bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
+// Convert from InetAddress to sockaddr_storage. An InetUnixAddress will be converted to
+// an AF_UNIX sockaddr_un. An Inet6Address will be converted to an AF_INET6 sockaddr_in6.
+// An Inet4Address will be converted to an IPv4-mapped AF_INET6 sockaddr_in6. This is what
+// you want if you're about to perform an operation on a socket, since all our sockets
+// are AF_INET6.
+bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port,
+                           sockaddr_storage& ss, socklen_t& sa_len);
 
-// Convert from InetAddress to sockaddr_storage. An Inet6Address will be converted to
-// a sockaddr_in6 while an Inet4Address will be converted to a sockaddr_in. This is
-// probably only useful for getnameinfo(2), where we'll be presenting the result to
-// the user and the user may actually care whether the original address was pure IPv4
-// or an IPv4-mapped IPv6 address, and for the MCAST_JOIN_GROUP socket option.
-bool inetAddressToSockaddrVerbatim(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
+// Convert from InetAddress to sockaddr_storage. An InetUnixAddress will be converted to
+// an AF_UNIX sockaddr_un. An Inet6Address will be converted to an AF_INET6 sockaddr_in6.
+// An Inet4Address will be converted to a sockaddr_in. This is probably only useful for
+// getnameinfo(2), where we'll be presenting the result to the user and the user may actually
+// care whether the original address was pure IPv4 or an IPv4-mapped IPv6 address, and
+// for the MCAST_JOIN_GROUP socket option.
+bool inetAddressToSockaddrVerbatim(JNIEnv* env, jobject inetAddress, int port,
+                                   sockaddr_storage& ss, socklen_t& sa_len);
 
 
 
diff --git a/luni/src/main/native/Portability.h b/luni/src/main/native/Portability.h
new file mode 100644
index 0000000..833d5bb
--- /dev/null
+++ b/luni/src/main/native/Portability.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef PORTABILITY_H_included
+#define PORTABILITY_H_included
+
+#if defined(__APPLE__)
+
+// Mac OS.
+#include <AvailabilityMacros.h> // For MAC_OS_X_VERSION_MAX_ALLOWED
+
+#include <libkern/OSByteOrder.h>
+#define bswap_16 OSSwapInt16
+#define bswap_32 OSSwapInt32
+#define bswap_64 OSSwapInt64
+
+#include <crt_externs.h>
+#define environ (*_NSGetEnviron())
+
+// Mac OS has a 64-bit off_t and no 32-bit compatibility cruft.
+#define flock64 flock
+#define ftruncate64 ftruncate
+#define isnanf __inline_isnanf
+#define lseek64 lseek
+#define pread64 pread
+#define pwrite64 pwrite
+
+// TODO: Darwin appears to have an fdatasync syscall.
+static inline int fdatasync(int fd) { return fsync(fd); }
+
+// For Linux-compatible sendfile(3).
+#include <sys/socket.h>
+#include <sys/types.h>
+static inline ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count) {
+  off_t in_out_count = count;
+  int result = sendfile(in_fd, out_fd, *offset, &in_out_count, NULL, 0);
+  if (result == -1) {
+    return -1;
+  }
+  return in_out_count;
+}
+
+// For mincore(3).
+#define _DARWIN_C_SOURCE
+#include <sys/mman.h>
+#undef _DARWIN_C_SOURCE
+static inline int mincore(void* addr, size_t length, unsigned char* vec) {
+  return mincore(addr, length, reinterpret_cast<char*>(vec));
+}
+
+// For statfs(3).
+#include <sys/param.h>
+#include <sys/mount.h>
+#define f_frsize f_bsize // TODO: close enough?
+
+#else
+
+// Bionic or glibc.
+
+#include <byteswap.h>
+#include <sys/sendfile.h>
+
+// For statfs(3).
+#include <sys/vfs.h> // Bionic doesn't have <sys/statvfs.h>
+
+#endif
+
+#endif  // PORTABILITY_H_included
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index 36abd7b..c94cd5d 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -54,6 +54,7 @@
     REGISTER(register_java_util_zip_CRC32);
     REGISTER(register_java_util_zip_Deflater);
     REGISTER(register_java_util_zip_Inflater);
+    REGISTER(register_libcore_icu_AlphabeticIndex);
     REGISTER(register_libcore_icu_ICU);
     REGISTER(register_libcore_icu_NativeBreakIterator);
     REGISTER(register_libcore_icu_NativeCollation);
@@ -62,7 +63,8 @@
     REGISTER(register_libcore_icu_NativeIDN);
     REGISTER(register_libcore_icu_NativeNormalizer);
     REGISTER(register_libcore_icu_NativePluralRules);
-    REGISTER(register_libcore_icu_TimeZones);
+    REGISTER(register_libcore_icu_TimeZoneNames);
+    REGISTER(register_libcore_icu_Transliterator);
     REGISTER(register_libcore_io_AsynchronousCloseMonitor);
     REGISTER(register_libcore_io_Memory);
     REGISTER(register_libcore_io_OsConstants);
@@ -71,6 +73,7 @@
     REGISTER(register_org_apache_harmony_dalvik_NativeTestTarget);
     REGISTER(register_org_apache_harmony_xml_ExpatParser);
     REGISTER(register_org_apache_harmony_xnet_provider_jsse_NativeCrypto);
+    REGISTER(register_sun_misc_Unsafe);
 #undef REGISTER
     return JNI_VERSION_1_6;
 }
diff --git a/luni/src/main/native/zip.h b/luni/src/main/native/ZipUtilities.h
similarity index 96%
rename from luni/src/main/native/zip.h
rename to luni/src/main/native/ZipUtilities.h
index 303c940..4c86f32 100644
--- a/luni/src/main/native/zip.h
+++ b/luni/src/main/native/ZipUtilities.h
@@ -15,8 +15,8 @@
  * limitations under the License.
  */
 
-#if !defined(zip_h)
-#define zip_h
+#ifndef ZIP_UTILITIES_H_included
+#define ZIP_UTILITIES_H_included
 
 #include "JNIHelp.h"
 #include "JniException.h"
@@ -97,4 +97,4 @@
     return reinterpret_cast<NativeZipStream*>(static_cast<uintptr_t>(address));
 }
 
-#endif /* zip_h */
+#endif  // ZIP_UTILITIES_H_included
diff --git a/luni/src/main/native/cbigint.cpp b/luni/src/main/native/cbigint.cpp
index da15fcb..6943ca2 100644
--- a/luni/src/main/native/cbigint.cpp
+++ b/luni/src/main/native/cbigint.cpp
@@ -18,7 +18,7 @@
 #include <string.h>
 #include "cbigint.h"
 
-#if defined(__linux__) || defined(FREEBSD) || defined(ZOS) || defined(MACOSX) || defined(AIX)
+#if defined(__linux__) || defined(__APPLE__)
 #define USE_LL
 #endif
 
@@ -121,7 +121,8 @@
   else if (length == 1)
     return 1;
 
-  while (++arg1[index] == 0 && ++index < length);
+  while (++arg1[index] == 0 && ++index < length) {
+  }
 
   return index == length;
 }
@@ -166,7 +167,8 @@
   else if (index == length1)
     return 1;
 
-  while (++arg1[index] == 0 && ++index < length1);
+  while (++arg1[index] == 0 && ++index < length1) {
+  }
 
   return index == length1;
 }
@@ -539,8 +541,10 @@
 int32_t
 compareHighPrecision (uint64_t * arg1, int32_t length1, uint64_t * arg2, int32_t length2)
 {
-  while (--length1 >= 0 && arg1[length1] == 0);
-  while (--length2 >= 0 && arg2[length2] == 0);
+  while (--length1 >= 0 && arg1[length1] == 0) {
+  }
+  while (--length2 >= 0 && arg2[length2] == 0) {
+  }
 
   if (length1 > length2)
     return 1;
diff --git a/luni/src/main/native/java_io_File.cpp b/luni/src/main/native/java_io_File.cpp
index ba1f55a..c217ea2 100644
--- a/luni/src/main/native/java_io_File.cpp
+++ b/luni/src/main/native/java_io_File.cpp
@@ -35,7 +35,6 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <sys/vfs.h>
 #include <time.h>
 #include <unistd.h>
 #include <utime.h>
@@ -107,13 +106,15 @@
         if (mIsBad) {
             return NULL;
         }
-        dirent* result = NULL;
-        int rc = readdir_r(mDirStream, &mEntry, &result);
-        if (rc != 0) {
-            mIsBad = true;
-            return NULL;
+        errno = 0;
+        dirent* result = readdir(mDirStream);
+        if (result != NULL) {
+            return result->d_name;
         }
-        return (result != NULL) ? result->d_name : NULL;
+        if (errno != 0) {
+            mIsBad = true;
+        }
+        return NULL;
     }
 
     // Has an error occurred on this stream?
@@ -123,7 +124,6 @@
 
 private:
     DIR* mDirStream;
-    dirent mEntry;
     bool mIsBad;
 
     // Disallow copy and assignment.
diff --git a/luni/src/main/native/java_io_ObjectStreamClass.cpp b/luni/src/main/native/java_io_ObjectStreamClass.cpp
index f6bd560..9a1b632 100644
--- a/luni/src/main/native/java_io_ObjectStreamClass.cpp
+++ b/luni/src/main/native/java_io_ObjectStreamClass.cpp
@@ -47,21 +47,21 @@
     return (mid != 0);
 }
 
-static jint ObjectStreamClass_getConstructorId(JNIEnv* env, jclass, jclass constructorClass) {
-    return reinterpret_cast<jint>(env->GetMethodID(constructorClass, "<init>", "()V"));
+static jlong ObjectStreamClass_getConstructorId(JNIEnv* env, jclass, jclass constructorClass) {
+    return reinterpret_cast<jlong>(env->GetMethodID(constructorClass, "<init>", "()V"));
 }
 
-static jobject ObjectStreamClass_newInstance(JNIEnv* env, jclass, jclass instantiationClass, jint methodId) {
+static jobject ObjectStreamClass_newInstance(JNIEnv* env, jclass, jclass instantiationClass, jlong methodId) {
     return env->NewObject(instantiationClass, reinterpret_cast<jmethodID>(methodId));
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(ObjectStreamClass, getConstructorId, "(Ljava/lang/Class;)I"),
+    NATIVE_METHOD(ObjectStreamClass, getConstructorId, "(Ljava/lang/Class;)J"),
     NATIVE_METHOD(ObjectStreamClass, getConstructorSignature, "(Ljava/lang/reflect/Constructor;)Ljava/lang/String;"),
     NATIVE_METHOD(ObjectStreamClass, getFieldSignature, "(Ljava/lang/reflect/Field;)Ljava/lang/String;"),
     NATIVE_METHOD(ObjectStreamClass, getMethodSignature, "(Ljava/lang/reflect/Method;)Ljava/lang/String;"),
     NATIVE_METHOD(ObjectStreamClass, hasClinit, "(Ljava/lang/Class;)Z"),
-    NATIVE_METHOD(ObjectStreamClass, newInstance, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
+    NATIVE_METHOD(ObjectStreamClass, newInstance, "(Ljava/lang/Class;J)Ljava/lang/Object;"),
 };
 void register_java_io_ObjectStreamClass(JNIEnv* env) {
     jniRegisterNativeMethods(env, "java/io/ObjectStreamClass", gMethods, NELEM(gMethods));
diff --git a/luni/src/main/native/java_lang_ProcessManager.cpp b/luni/src/main/native/java_lang_ProcessManager.cpp
index 6302d13..e3f0f45 100644
--- a/luni/src/main/native/java_lang_ProcessManager.cpp
+++ b/luni/src/main/native/java_lang_ProcessManager.cpp
@@ -27,8 +27,10 @@
 #include "jni.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "Portability.h"
 #include "ScopedLocalRef.h"
 #include "cutils/log.h"
+#include "toStringArray.h"
 
 /** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */
 static void closeNonStandardFds(int skipFd1, int skipFd2) {
@@ -101,9 +103,9 @@
     // If this is the child process...
     if (childPid == 0) {
         /*
-         * Note: We cannot malloc() or free() after this point!
-         * A no-longer-running thread may be holding on to the heap lock, and
-         * an attempt to malloc() or free() would result in deadlock.
+         * Note: We cannot malloc(3) or free(3) after this point!
+         * A thread in the parent that no longer exists in the child may have held the heap lock
+         * when we forked, so an attempt to malloc(3) or free(3) would result in deadlock.
          */
 
         // Replace stdin, out, and err with pipes.
@@ -143,11 +145,11 @@
         execvp(commands[0], commands);
 
         // If we got here, execvp() failed or the working dir was invalid.
-        execFailed:
-            int error = errno;
-            write(statusOut, &error, sizeof(int));
-            close(statusOut);
-            exit(error);
+execFailed:
+        int error = errno;
+        write(statusOut, &error, sizeof(int));
+        close(statusOut);
+        exit(error);
     }
 
     // This is the parent process.
@@ -182,40 +184,6 @@
     return childPid;
 }
 
-/** Converts a Java String[] to a 0-terminated char**. */
-static char** convertStrings(JNIEnv* env, jobjectArray javaArray) {
-    if (javaArray == NULL) {
-        return NULL;
-    }
-
-    jsize length = env->GetArrayLength(javaArray);
-    char** array = new char*[length + 1];
-    array[length] = 0;
-    for (jsize i = 0; i < length; ++i) {
-        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
-        // We need to pass these strings to const-unfriendly code.
-        char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry.get(), NULL));
-        array[i] = entry;
-    }
-
-    return array;
-}
-
-/** Frees a char** which was converted from a Java String[]. */
-static void freeStrings(JNIEnv* env, jobjectArray javaArray, char** array) {
-    if (javaArray == NULL) {
-        return;
-    }
-
-    jsize length = env->GetArrayLength(javaArray);
-    for (jsize i = 0; i < length; ++i) {
-        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
-        env->ReleaseStringUTFChars(javaEntry.get(), array[i]);
-    }
-
-    delete[] array;
-}
-
 /**
  * Converts Java String[] to char** and delegates to executeProcess().
  */
diff --git a/luni/src/main/native/java_lang_StrictMath.cpp b/luni/src/main/native/java_lang_StrictMath.cpp
index 959ddc6..cfe375e 100644
--- a/luni/src/main/native/java_lang_StrictMath.cpp
+++ b/luni/src/main/native/java_lang_StrictMath.cpp
@@ -144,7 +144,6 @@
     NATIVE_METHOD(StrictMath, tan, "!(D)D"),
     NATIVE_METHOD(StrictMath, tanh, "!(D)D"),
 };
-
 void register_java_lang_StrictMath(JNIEnv* env) {
     jniRegisterNativeMethods(env, "java/lang/StrictMath", gMethods, NELEM(gMethods));
 }
diff --git a/luni/src/main/native/java_lang_StringToReal.cpp b/luni/src/main/native/java_lang_StringToReal.cpp
index d401e65..108f939 100644
--- a/luni/src/main/native/java_lang_StringToReal.cpp
+++ b/luni/src/main/native/java_lang_StringToReal.cpp
@@ -25,7 +25,7 @@
 #include "cbigint.h"
 
 /* ************************* Defines ************************* */
-#if defined(__linux__) || defined(FREEBSD)
+#if defined(__linux__) || defined(__APPLE__)
 #define USE_LL
 #endif
 
diff --git a/luni/src/main/native/java_text_Bidi.cpp b/luni/src/main/native/java_text_Bidi.cpp
index 01bca09..d9ef35d 100644
--- a/luni/src/main/native/java_text_Bidi.cpp
+++ b/luni/src/main/native/java_text_Bidi.cpp
@@ -17,6 +17,7 @@
 
 #define LOG_TAG "Bidi"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
@@ -144,7 +145,8 @@
     if (maybeThrowIcuException(env, "ubidi_countRuns", status)) {
         return NULL;
     }
-    jmethodID bidiRunConstructor = env->GetMethodID(JniConstants::bidiRunClass, "<init>", "(III)V");
+    static jmethodID bidiRunConstructor =
+            env->GetMethodID(JniConstants::bidiRunClass, "<init>", "(III)V");
     jobjectArray runs = env->NewObjectArray(runCount, JniConstants::bidiRunClass, NULL);
     UBiDiLevel level = 0;
     int start = 0;
diff --git a/luni/src/main/native/java_util_regex_Matcher.cpp b/luni/src/main/native/java_util_regex_Matcher.cpp
index 6116d2b..2e5259e 100644
--- a/luni/src/main/native/java_util_regex_Matcher.cpp
+++ b/luni/src/main/native/java_util_regex_Matcher.cpp
@@ -18,6 +18,7 @@
 
 #include <stdlib.h>
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
@@ -29,8 +30,8 @@
 
 // ICU documentation: http://icu-project.org/apiref/icu4c/classRegexMatcher.html
 
-static RegexMatcher* toRegexMatcher(jint addr) {
-    return reinterpret_cast<RegexMatcher*>(static_cast<uintptr_t>(addr));
+static RegexMatcher* toRegexMatcher(jlong address) {
+    return reinterpret_cast<RegexMatcher*>(static_cast<uintptr_t>(address));
 }
 
 /**
@@ -41,8 +42,8 @@
  */
 class MatcherAccessor {
 public:
-    MatcherAccessor(JNIEnv* env, jint addr, jstring javaInput, bool reset) {
-        init(env, addr);
+    MatcherAccessor(JNIEnv* env, jlong address, jstring javaInput, bool reset) {
+        init(env, address);
 
         mJavaInput = javaInput;
         mChars = env->GetStringChars(mJavaInput, NULL);
@@ -62,8 +63,8 @@
         }
     }
 
-    MatcherAccessor(JNIEnv* env, jint addr) {
-        init(env, addr);
+    MatcherAccessor(JNIEnv* env, jlong address) {
+        init(env, address);
     }
 
     ~MatcherAccessor() {
@@ -95,10 +96,10 @@
     }
 
 private:
-    void init(JNIEnv* env, jint addr) {
+    void init(JNIEnv* env, jlong address) {
         mEnv = env;
         mJavaInput = NULL;
-        mMatcher = toRegexMatcher(addr);
+        mMatcher = toRegexMatcher(address);
         mChars = NULL;
         mStatus = U_ZERO_ERROR;
         mUText = NULL;
@@ -116,11 +117,11 @@
     void operator=(const MatcherAccessor&);
 };
 
-static void Matcher_closeImpl(JNIEnv*, jclass, jint addr) {
-    delete toRegexMatcher(addr);
+static void Matcher_closeImpl(JNIEnv*, jclass, jlong address) {
+    delete toRegexMatcher(address);
 }
 
-static jint Matcher_findImpl(JNIEnv* env, jclass, jint addr, jstring javaText, jint startIndex, jintArray offsets) {
+static jint Matcher_findImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint startIndex, jintArray offsets) {
     MatcherAccessor matcher(env, addr, javaText, false);
     UBool result = matcher->find(startIndex, matcher.status());
     if (result) {
@@ -129,7 +130,7 @@
     return result;
 }
 
-static jint Matcher_findNextImpl(JNIEnv* env, jclass, jint addr, jstring javaText, jintArray offsets) {
+static jint Matcher_findNextImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
     MatcherAccessor matcher(env, addr, javaText, false);
     if (matcher.status() != U_ZERO_ERROR) {
         return -1;
@@ -141,17 +142,17 @@
     return result;
 }
 
-static jint Matcher_groupCountImpl(JNIEnv* env, jclass, jint addr) {
+static jint Matcher_groupCountImpl(JNIEnv* env, jclass, jlong addr) {
     MatcherAccessor matcher(env, addr);
     return matcher->groupCount();
 }
 
-static jint Matcher_hitEndImpl(JNIEnv* env, jclass, jint addr) {
+static jint Matcher_hitEndImpl(JNIEnv* env, jclass, jlong addr) {
     MatcherAccessor matcher(env, addr);
     return matcher->hitEnd();
 }
 
-static jint Matcher_lookingAtImpl(JNIEnv* env, jclass, jint addr, jstring javaText, jintArray offsets) {
+static jint Matcher_lookingAtImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
     MatcherAccessor matcher(env, addr, javaText, false);
     UBool result = matcher->lookingAt(matcher.status());
     if (result) {
@@ -160,7 +161,7 @@
     return result;
 }
 
-static jint Matcher_matchesImpl(JNIEnv* env, jclass, jint addr, jstring javaText, jintArray offsets) {
+static jint Matcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
     MatcherAccessor matcher(env, addr, javaText, false);
     UBool result = matcher->matches(matcher.status());
     if (result) {
@@ -169,47 +170,47 @@
     return result;
 }
 
-static jint Matcher_openImpl(JNIEnv* env, jclass, jint patternAddr) {
+static jlong Matcher_openImpl(JNIEnv* env, jclass, jlong patternAddr) {
     RegexPattern* pattern = reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(patternAddr));
     UErrorCode status = U_ZERO_ERROR;
     RegexMatcher* result = pattern->matcher(status);
     maybeThrowIcuException(env, "RegexPattern::matcher", status);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(result));
+    return reinterpret_cast<uintptr_t>(result);
 }
 
-static jint Matcher_requireEndImpl(JNIEnv* env, jclass, jint addr) {
+static jint Matcher_requireEndImpl(JNIEnv* env, jclass, jlong addr) {
     MatcherAccessor matcher(env, addr);
     return matcher->requireEnd();
 }
 
-static void Matcher_setInputImpl(JNIEnv* env, jclass, jint addr, jstring javaText, jint start, jint end) {
+static void Matcher_setInputImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint start, jint end) {
     MatcherAccessor matcher(env, addr, javaText, true);
     matcher->region(start, end, matcher.status());
 }
 
-static void Matcher_useAnchoringBoundsImpl(JNIEnv* env, jclass, jint addr, jboolean value) {
+static void Matcher_useAnchoringBoundsImpl(JNIEnv* env, jclass, jlong addr, jboolean value) {
     MatcherAccessor matcher(env, addr);
     matcher->useAnchoringBounds(value);
 }
 
-static void Matcher_useTransparentBoundsImpl(JNIEnv* env, jclass, jint addr, jboolean value) {
+static void Matcher_useTransparentBoundsImpl(JNIEnv* env, jclass, jlong addr, jboolean value) {
     MatcherAccessor matcher(env, addr);
     matcher->useTransparentBounds(value);
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(Matcher, closeImpl, "(I)V"),
-    NATIVE_METHOD(Matcher, findImpl, "(ILjava/lang/String;I[I)Z"),
-    NATIVE_METHOD(Matcher, findNextImpl, "(ILjava/lang/String;[I)Z"),
-    NATIVE_METHOD(Matcher, groupCountImpl, "(I)I"),
-    NATIVE_METHOD(Matcher, hitEndImpl, "(I)Z"),
-    NATIVE_METHOD(Matcher, lookingAtImpl, "(ILjava/lang/String;[I)Z"),
-    NATIVE_METHOD(Matcher, matchesImpl, "(ILjava/lang/String;[I)Z"),
-    NATIVE_METHOD(Matcher, openImpl, "(I)I"),
-    NATIVE_METHOD(Matcher, requireEndImpl, "(I)Z"),
-    NATIVE_METHOD(Matcher, setInputImpl, "(ILjava/lang/String;II)V"),
-    NATIVE_METHOD(Matcher, useAnchoringBoundsImpl, "(IZ)V"),
-    NATIVE_METHOD(Matcher, useTransparentBoundsImpl, "(IZ)V"),
+    NATIVE_METHOD(Matcher, closeImpl, "(J)V"),
+    NATIVE_METHOD(Matcher, findImpl, "(JLjava/lang/String;I[I)Z"),
+    NATIVE_METHOD(Matcher, findNextImpl, "(JLjava/lang/String;[I)Z"),
+    NATIVE_METHOD(Matcher, groupCountImpl, "(J)I"),
+    NATIVE_METHOD(Matcher, hitEndImpl, "(J)Z"),
+    NATIVE_METHOD(Matcher, lookingAtImpl, "(JLjava/lang/String;[I)Z"),
+    NATIVE_METHOD(Matcher, matchesImpl, "(JLjava/lang/String;[I)Z"),
+    NATIVE_METHOD(Matcher, openImpl, "(J)J"),
+    NATIVE_METHOD(Matcher, requireEndImpl, "(J)Z"),
+    NATIVE_METHOD(Matcher, setInputImpl, "(JLjava/lang/String;II)V"),
+    NATIVE_METHOD(Matcher, useAnchoringBoundsImpl, "(JZ)V"),
+    NATIVE_METHOD(Matcher, useTransparentBoundsImpl, "(JZ)V"),
 };
 void register_java_util_regex_Matcher(JNIEnv* env) {
     jniRegisterNativeMethods(env, "java/util/regex/Matcher", gMethods, NELEM(gMethods));
diff --git a/luni/src/main/native/java_util_regex_Pattern.cpp b/luni/src/main/native/java_util_regex_Pattern.cpp
index cad154f..1a99d0a 100644
--- a/luni/src/main/native/java_util_regex_Pattern.cpp
+++ b/luni/src/main/native/java_util_regex_Pattern.cpp
@@ -27,7 +27,7 @@
 
 // ICU documentation: http://icu-project.org/apiref/icu4c/classRegexPattern.html
 
-static RegexPattern* toRegexPattern(jint addr) {
+static RegexPattern* toRegexPattern(jlong addr) {
     return reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(addr));
 }
 
@@ -71,11 +71,11 @@
     env->Throw(reinterpret_cast<jthrowable>(exception));
 }
 
-static void Pattern_closeImpl(JNIEnv*, jclass, jint addr) {
+static void Pattern_closeImpl(JNIEnv*, jclass, jlong addr) {
     delete toRegexPattern(addr);
 }
 
-static jint Pattern_compileImpl(JNIEnv* env, jclass, jstring javaRegex, jint flags) {
+static jlong Pattern_compileImpl(JNIEnv* env, jclass, jstring javaRegex, jint flags) {
     flags |= UREGEX_ERROR_ON_UNKNOWN_ESCAPES;
 
     UErrorCode status = U_ZERO_ERROR;
@@ -83,17 +83,20 @@
     error.offset = -1;
 
     ScopedJavaUnicodeString regex(env, javaRegex);
+    if (!regex.valid()) {
+        return 0;
+    }
     UnicodeString& regexString(regex.unicodeString());
     RegexPattern* result = RegexPattern::compile(regexString, flags, error, status);
     if (!U_SUCCESS(status)) {
         throwPatternSyntaxException(env, status, javaRegex, error);
     }
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(result));
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(result));
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(Pattern, closeImpl, "(I)V"),
-    NATIVE_METHOD(Pattern, compileImpl, "(Ljava/lang/String;I)I"),
+    NATIVE_METHOD(Pattern, closeImpl, "(J)V"),
+    NATIVE_METHOD(Pattern, compileImpl, "(Ljava/lang/String;I)J"),
 };
 void register_java_util_regex_Pattern(JNIEnv* env) {
     jniRegisterNativeMethods(env, "java/util/regex/Pattern", gMethods, NELEM(gMethods));
diff --git a/luni/src/main/native/java_util_zip_Deflater.cpp b/luni/src/main/native/java_util_zip_Deflater.cpp
index e129134..ed7d754 100644
--- a/luni/src/main/native/java_util_zip_Deflater.cpp
+++ b/luni/src/main/native/java_util_zip_Deflater.cpp
@@ -19,7 +19,7 @@
 
 #include "JniConstants.h"
 #include "ScopedPrimitiveArray.h"
-#include "zip.h"
+#include "ZipUtilities.h"
 
 static void Deflater_setDictionaryImpl(JNIEnv* env, jobject, jbyteArray dict, int off, int len, jlong handle) {
     toNativeZipStream(handle)->setDictionary(env, dict, off, len, false);
diff --git a/luni/src/main/native/java_util_zip_Inflater.cpp b/luni/src/main/native/java_util_zip_Inflater.cpp
index 890c6dc..6aa3e24 100644
--- a/luni/src/main/native/java_util_zip_Inflater.cpp
+++ b/luni/src/main/native/java_util_zip_Inflater.cpp
@@ -19,7 +19,7 @@
 
 #include "JniConstants.h"
 #include "ScopedPrimitiveArray.h"
-#include "zip.h"
+#include "ZipUtilities.h"
 #include <errno.h>
 
 static jlong Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) {
diff --git a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp
new file mode 100644
index 0000000..b815853
--- /dev/null
+++ b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AlphabeticIndex"
+
+#include "IcuUtilities.h"
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "JniException.h"
+#include "ScopedJavaUnicodeString.h"
+#include "ScopedStringChars.h"
+#include "unicode/alphaindex.h"
+#include "unicode/uniset.h"
+
+static AlphabeticIndex* fromPeer(jlong peer) {
+  return reinterpret_cast<AlphabeticIndex*>(static_cast<uintptr_t>(peer));
+}
+
+static jlong AlphabeticIndex_create(JNIEnv* env, jclass, jstring javaLocale) {
+  UErrorCode status = U_ZERO_ERROR;
+  AlphabeticIndex* ai = new AlphabeticIndex(getLocale(env, javaLocale), status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex", status)) {
+    return 0;
+  }
+  return reinterpret_cast<uintptr_t>(ai);
+}
+
+static void AlphabeticIndex_destroy(JNIEnv*, jclass, jlong peer) {
+  delete fromPeer(peer);
+}
+
+static void AlphabeticIndex_addLabels(JNIEnv* env, jclass, jlong peer, jstring javaLocale) {
+  AlphabeticIndex* ai = fromPeer(peer);
+  UErrorCode status = U_ZERO_ERROR;
+  ai->addLabels(getLocale(env, javaLocale), status);
+  maybeThrowIcuException(env, "AlphabeticIndex::addLabels", status);
+}
+
+static void AlphabeticIndex_addLabelRange(JNIEnv* env, jclass, jlong peer,
+                                          jint codePointStart, jint codePointEnd) {
+  AlphabeticIndex* ai = fromPeer(peer);
+  UErrorCode status = U_ZERO_ERROR;
+  ai->addLabels(UnicodeSet(codePointStart, codePointEnd), status);
+  maybeThrowIcuException(env, "AlphabeticIndex::addLabels", status);
+}
+
+static jint AlphabeticIndex_getBucketCount(JNIEnv* env, jclass, jlong peer) {
+  AlphabeticIndex* ai = fromPeer(peer);
+  UErrorCode status = U_ZERO_ERROR;
+  jint result = ai->getBucketCount(status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::getBucketCount", status)) {
+    return -1;
+  }
+  return result;
+}
+
+static jint AlphabeticIndex_getBucketIndex(JNIEnv* env, jclass, jlong peer, jstring javaString) {
+  AlphabeticIndex* ai = fromPeer(peer);
+  ScopedJavaUnicodeString string(env, javaString);
+  if (!string.valid()) {
+    return -1;
+  }
+  UErrorCode status = U_ZERO_ERROR;
+  jint result = ai->getBucketIndex(string.unicodeString(), status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::getBucketIndex", status)) {
+    return -1;
+  }
+  return result;
+}
+
+static jstring AlphabeticIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, jint index) {
+  if (index < 0) {
+    jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid index: %d", index);
+    return NULL;
+  }
+
+  // Iterate to the nth bucket.
+  AlphabeticIndex* ai = fromPeer(peer);
+  UErrorCode status = U_ZERO_ERROR;
+  ai->resetBucketIterator(status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::resetBucketIterator", status)) {
+    return NULL;
+  }
+  for (jint i = 0; i <= index; ++i) {
+    if (!ai->nextBucket(status)) {
+      jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid index: %d", index);
+      return NULL;
+    }
+    if (maybeThrowIcuException(env, "AlphabeticIndex::nextBucket", status)) {
+      return NULL;
+    }
+  }
+
+  // Return "" for the underflow/inflow/overflow buckets.
+  if (ai->getBucketLabelType() != U_ALPHAINDEX_NORMAL) {
+    return env->NewStringUTF("");
+  }
+
+  const UnicodeString& label(ai->getBucketLabel());
+  return env->NewString(label.getBuffer(), label.length());
+}
+
+static jlong AlphabeticIndex_buildImmutableIndex(JNIEnv* env, jclass, jlong peer) {
+  AlphabeticIndex* ai = fromPeer(peer);
+  UErrorCode status = U_ZERO_ERROR;
+  AlphabeticIndex::ImmutableIndex* ii = ai->buildImmutableIndex(status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::buildImmutableIndex", status)) {
+    return 0;
+  }
+  return reinterpret_cast<uintptr_t>(ii);
+}
+
+static AlphabeticIndex::ImmutableIndex* immutableIndexFromPeer(jlong peer) {
+  return reinterpret_cast<AlphabeticIndex::ImmutableIndex*>(static_cast<uintptr_t>(peer));
+}
+
+static jint ImmutableIndex_getBucketCount(JNIEnv*, jclass, jlong peer) {
+  AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer);
+  return ii->getBucketCount();
+}
+
+static jint ImmutableIndex_getBucketIndex(JNIEnv* env, jclass, jlong peer, jstring javaString) {
+  AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer);
+  ScopedJavaUnicodeString string(env, javaString);
+  if (!string.valid()) {
+    return -1;
+  }
+  UErrorCode status = U_ZERO_ERROR;
+  jint result = ii->getBucketIndex(string.unicodeString(), status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::ImmutableIndex::getBucketIndex", status)) {
+    return -1;
+  }
+  return result;
+}
+
+static jstring ImmutableIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, jint index) {
+  AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer);
+  const AlphabeticIndex::Bucket* bucket = ii->getBucket(index);
+  if (bucket == NULL) {
+    jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid index: %d", index);
+    return NULL;
+  }
+
+  // Return "" for the underflow/inflow/overflow buckets.
+  if (bucket->getLabelType() != U_ALPHAINDEX_NORMAL) {
+    return env->NewStringUTF("");
+  }
+
+  const UnicodeString& label(bucket->getLabel());
+  return env->NewString(label.getBuffer(), label.length());
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(AlphabeticIndex, create, "(Ljava/lang/String;)J"),
+  NATIVE_METHOD(AlphabeticIndex, destroy, "(J)V"),
+  NATIVE_METHOD(AlphabeticIndex, addLabels, "(JLjava/lang/String;)V"),
+  NATIVE_METHOD(AlphabeticIndex, addLabelRange, "(JII)V"),
+  NATIVE_METHOD(AlphabeticIndex, getBucketCount, "(J)I"),
+  NATIVE_METHOD(AlphabeticIndex, getBucketIndex, "(JLjava/lang/String;)I"),
+  NATIVE_METHOD(AlphabeticIndex, getBucketLabel, "(JI)Ljava/lang/String;"),
+  NATIVE_METHOD(AlphabeticIndex, buildImmutableIndex, "(J)J"),
+};
+static JNINativeMethod gImmutableIndexMethods[] = {
+  NATIVE_METHOD(ImmutableIndex, getBucketCount, "(J)I"),
+  NATIVE_METHOD(ImmutableIndex, getBucketIndex, "(JLjava/lang/String;)I"),
+  NATIVE_METHOD(ImmutableIndex, getBucketLabel, "(JI)Ljava/lang/String;"),
+};
+void register_libcore_icu_AlphabeticIndex(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "libcore/icu/AlphabeticIndex", gMethods, NELEM(gMethods));
+  jniRegisterNativeMethods(env, "libcore/icu/AlphabeticIndex$ImmutableIndex", gImmutableIndexMethods, NELEM(gImmutableIndexMethods));
+}
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index 5224420..53a1241 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -16,13 +16,13 @@
 
 #define LOG_TAG "ICU"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
 #include "ScopedFd.h"
 #include "ScopedJavaUnicodeString.h"
 #include "ScopedLocalRef.h"
-#include "ScopedStringChars.h"
 #include "ScopedUtfChars.h"
 #include "UniquePtr.h"
 #include "cutils/log.h"
@@ -32,6 +32,7 @@
 #include "unicode/dcfmtsym.h"
 #include "unicode/decimfmt.h"
 #include "unicode/dtfmtsym.h"
+#include "unicode/dtptngen.h"
 #include "unicode/gregocal.h"
 #include "unicode/locid.h"
 #include "unicode/numfmt.h"
@@ -43,6 +44,7 @@
 #include "unicode/ucurr.h"
 #include "unicode/udat.h"
 #include "unicode/uloc.h"
+#include "unicode/ulocdata.h"
 #include "unicode/ustring.h"
 #include "ureslocs.h"
 #include "valueOf.h"
@@ -92,10 +94,6 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedResourceBundle);
 };
 
-Locale getLocale(JNIEnv* env, jstring localeName) {
-    return Locale::createFromName(ScopedUtfChars(env, localeName).c_str());
-}
-
 static jstring ICU_addLikelySubtags(JNIEnv* env, jclass, jstring javaLocale) {
     UErrorCode status = U_ZERO_ERROR;
     ScopedUtfChars localeID(env, javaLocale);
@@ -119,12 +117,16 @@
 }
 
 static jint ICU_getCurrencyFractionDigits(JNIEnv* env, jclass, jstring javaCurrencyCode) {
-    ScopedJavaUnicodeString currencyCode(env, javaCurrencyCode);
-    UnicodeString icuCurrencyCode(currencyCode.unicodeString());
-    UErrorCode status = U_ZERO_ERROR;
-    return ucurr_getDefaultFractionDigits(icuCurrencyCode.getTerminatedBuffer(), &status);
+  ScopedJavaUnicodeString currencyCode(env, javaCurrencyCode);
+  if (!currencyCode.valid()) {
+    return 0;
+  }
+  UnicodeString icuCurrencyCode(currencyCode.unicodeString());
+  UErrorCode status = U_ZERO_ERROR;
+  return ucurr_getDefaultFractionDigits(icuCurrencyCode.getTerminatedBuffer(), &status);
 }
 
+// TODO: rewrite this with int32_t ucurr_forLocale(const char* locale, UChar* buff, int32_t buffCapacity, UErrorCode* ec)...
 static jstring ICU_getCurrencyCode(JNIEnv* env, jclass, jstring javaCountryCode) {
     UErrorCode status = U_ZERO_ERROR;
     ScopedResourceBundle supplData(ures_openDirect(U_ICUDATA_CURR, "supplementalData", &status));
@@ -167,50 +169,44 @@
     return (charCount == 0) ? env->NewStringUTF("XXX") : env->NewString(chars, charCount);
 }
 
-static jstring ICU_getCurrencyDisplayName(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaCurrencyCode) {
-    ScopedUtfChars localeName(env, javaLocaleName);
-    ScopedJavaUnicodeString currencyCode(env, javaCurrencyCode);
-    UnicodeString icuCurrencyCode(currencyCode.unicodeString());
-    UErrorCode status = U_ZERO_ERROR;
-    UBool isChoiceFormat;
-    int32_t charCount;
-    const UChar* chars = ucurr_getName(icuCurrencyCode.getTerminatedBuffer(), localeName.c_str(),
-            UCURR_LONG_NAME, &isChoiceFormat, &charCount, &status);
-    if (status == U_USING_DEFAULT_WARNING) {
-        // ICU's default is English. We want the ISO 4217 currency code instead.
-        chars = icuCurrencyCode.getBuffer();
-        charCount = icuCurrencyCode.length();
+static jstring getCurrencyName(JNIEnv* env, jstring javaLocaleName, jstring javaCurrencyCode, UCurrNameStyle nameStyle) {
+  ScopedUtfChars localeName(env, javaLocaleName);
+  if (localeName.c_str() == NULL) {
+    return NULL;
+  }
+  ScopedJavaUnicodeString currencyCode(env, javaCurrencyCode);
+  if (!currencyCode.valid()) {
+    return NULL;
+  }
+  UnicodeString icuCurrencyCode(currencyCode.unicodeString());
+  UErrorCode status = U_ZERO_ERROR;
+  UBool isChoiceFormat = false;
+  int32_t charCount;
+  const UChar* chars = ucurr_getName(icuCurrencyCode.getTerminatedBuffer(), localeName.c_str(),
+                                     nameStyle, &isChoiceFormat, &charCount, &status);
+  if (status == U_USING_DEFAULT_WARNING) {
+    if (nameStyle == UCURR_SYMBOL_NAME) {
+      // ICU doesn't distinguish between falling back to the root locale and meeting a genuinely
+      // unknown currency. The Currency class does.
+      if (!ucurr_isAvailable(icuCurrencyCode.getTerminatedBuffer(), U_DATE_MIN, U_DATE_MAX, &status)) {
+        return NULL;
+      }
     }
-    return (charCount == 0) ? NULL : env->NewString(chars, charCount);
+    if (nameStyle == UCURR_LONG_NAME) {
+      // ICU's default is English. We want the ISO 4217 currency code instead.
+      chars = icuCurrencyCode.getBuffer();
+      charCount = icuCurrencyCode.length();
+    }
+  }
+  return (charCount == 0) ? NULL : env->NewString(chars, charCount);
 }
 
-static jstring ICU_getCurrencySymbol(JNIEnv* env, jclass, jstring locale, jstring currencyCode) {
-    // We can't use ucurr_getName because it doesn't distinguish between using data root from
-    // the root locale and parroting back the input because it's never heard of the currency code.
-    ScopedUtfChars localeName(env, locale);
-    UErrorCode status = U_ZERO_ERROR;
-    ScopedResourceBundle currLoc(ures_open(U_ICUDATA_CURR, localeName.c_str(), &status));
-    if (U_FAILURE(status)) {
-        return NULL;
-    }
+static jstring ICU_getCurrencyDisplayName(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaCurrencyCode) {
+  return getCurrencyName(env, javaLocaleName, javaCurrencyCode, UCURR_LONG_NAME);
+}
 
-    ScopedResourceBundle currencies(ures_getByKey(currLoc.get(), "Currencies", NULL, &status));
-    if (U_FAILURE(status)) {
-        return NULL;
-    }
-
-    ScopedUtfChars currency(env, currencyCode);
-    ScopedResourceBundle currencyElems(ures_getByKey(currencies.get(), currency.c_str(), NULL, &status));
-    if (U_FAILURE(status)) {
-        return NULL;
-    }
-
-    int32_t charCount;
-    const jchar* chars = ures_getStringByIndex(currencyElems.get(), 0, &charCount, &status);
-    if (U_FAILURE(status)) {
-        return NULL;
-    }
-    return (charCount == 0) ? NULL : env->NewString(chars, charCount);
+static jstring ICU_getCurrencySymbol(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaCurrencyCode) {
+  return getCurrencyName(env, javaLocaleName, javaCurrencyCode, UCURR_SYMBOL_NAME);
 }
 
 static jstring ICU_getDisplayCountryNative(JNIEnv* env, jclass, jstring targetLocale, jstring locale) {
@@ -346,28 +342,26 @@
     setStringField(env, obj, fieldName, env->NewString(chars, value.length()));
 }
 
-static void setNumberPatterns(JNIEnv* env, jobject obj, jstring locale) {
+static void setNumberPatterns(JNIEnv* env, jobject obj, Locale& locale) {
     UErrorCode status = U_ZERO_ERROR;
-    Locale localeObj = getLocale(env, locale);
 
     UnicodeString pattern;
-    UniquePtr<DecimalFormat> fmt(static_cast<DecimalFormat*>(NumberFormat::createInstance(localeObj, UNUM_CURRENCY, status)));
+    UniquePtr<DecimalFormat> fmt(static_cast<DecimalFormat*>(NumberFormat::createInstance(locale, UNUM_CURRENCY, status)));
     pattern = fmt->toPattern(pattern.remove());
     setStringField(env, obj, "currencyPattern", pattern);
 
-    fmt.reset(static_cast<DecimalFormat*>(NumberFormat::createInstance(localeObj, UNUM_DECIMAL, status)));
+    fmt.reset(static_cast<DecimalFormat*>(NumberFormat::createInstance(locale, UNUM_DECIMAL, status)));
     pattern = fmt->toPattern(pattern.remove());
     setStringField(env, obj, "numberPattern", pattern);
 
-    fmt.reset(static_cast<DecimalFormat*>(NumberFormat::createInstance(localeObj, UNUM_PERCENT, status)));
+    fmt.reset(static_cast<DecimalFormat*>(NumberFormat::createInstance(locale, UNUM_PERCENT, status)));
     pattern = fmt->toPattern(pattern.remove());
     setStringField(env, obj, "percentPattern", pattern);
 }
 
-static void setDecimalFormatSymbolsData(JNIEnv* env, jobject obj, jstring locale) {
+static void setDecimalFormatSymbolsData(JNIEnv* env, jobject obj, Locale& locale) {
     UErrorCode status = U_ZERO_ERROR;
-    Locale localeObj = getLocale(env, locale);
-    DecimalFormatSymbols dfs(localeObj, status);
+    DecimalFormatSymbols dfs(locale, status);
 
     setCharField(env, obj, "decimalSeparator", dfs.getSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol));
     setCharField(env, obj, "groupingSeparator", dfs.getSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol));
@@ -444,6 +438,28 @@
   return true;
 }
 
+static bool getTimeFormats12And24(JNIEnv* env, jobject localeData, Locale& locale) {
+  UErrorCode status = U_ZERO_ERROR;
+  DateTimePatternGenerator* generator = DateTimePatternGenerator::createInstance(locale, status);
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  UnicodeString pattern_Hm(generator->getBestPattern(UnicodeString("Hm", 2, US_INV), status));
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  UnicodeString pattern_hm(generator->getBestPattern(UnicodeString("hm", 2, US_INV), status));
+  if (U_FAILURE(status)) {
+    return false;
+  }
+
+  setStringField(env, localeData, "timeFormat12", pattern_hm);
+  setStringField(env, localeData, "timeFormat24", pattern_Hm);
+  return true;
+}
+
 static bool getYesterdayTodayAndTomorrow(JNIEnv* env, jobject localeData, const char* locale_name) {
   UErrorCode status = U_ZERO_ERROR;
   ScopedResourceBundle root(ures_open(NULL, locale_name, &status));
@@ -480,8 +496,8 @@
   return false;
 }
 
-static jboolean ICU_initLocaleDataImpl(JNIEnv* env, jclass, jstring locale, jobject localeData) {
-    ScopedUtfChars localeName(env, locale);
+static jboolean ICU_initLocaleDataImpl(JNIEnv* env, jclass, jstring javaLocaleName, jobject localeData) {
+    ScopedUtfChars localeName(env, javaLocaleName);
     if (localeName.c_str() == NULL) {
         return JNI_FALSE;
     }
@@ -503,6 +519,13 @@
         return JNI_FALSE;
     }
 
+    // Get the "h:mm a" and "HH:mm" 12- and 24-hour time format strings.
+    Locale locale = getLocale(env, javaLocaleName);
+    if (!getTimeFormats12And24(env, localeData, locale)) {
+        ALOGE("Couldn't find ICU 12- and 24-hour time formats for %s", localeName.c_str());
+        return JNI_FALSE;
+    }
+
     // Get the "Yesterday", "Today", and "Tomorrow" strings.
     bool foundYesterdayTodayAndTomorrow = false;
     for (LocaleNameIterator it(localeName.c_str(), status); it.HasNext(); it.Up()) {
@@ -517,8 +540,7 @@
     }
 
     status = U_ZERO_ERROR;
-    Locale localeObj = getLocale(env, locale);
-    UniquePtr<Calendar> cal(Calendar::createInstance(localeObj, status));
+    UniquePtr<Calendar> cal(Calendar::createInstance(locale, status));
     if (U_FAILURE(status)) {
         return JNI_FALSE;
     }
@@ -528,7 +550,7 @@
 
     // Get DateFormatSymbols.
     status = U_ZERO_ERROR;
-    DateFormatSymbols dateFormatSym(localeObj, status);
+    DateFormatSymbols dateFormatSym(locale, status);
     if (U_FAILURE(status)) {
         return JNI_FALSE;
     }
@@ -591,7 +613,7 @@
 
     jstring currencySymbol = NULL;
     if (internationalCurrencySymbol != NULL) {
-        currencySymbol = ICU_getCurrencySymbol(env, NULL, locale, internationalCurrencySymbol);
+        currencySymbol = ICU_getCurrencySymbol(env, NULL, javaLocaleName, internationalCurrencySymbol);
     } else {
         internationalCurrencySymbol = env->NewStringUTF("XXX");
     }
@@ -606,19 +628,25 @@
 }
 
 static jstring ICU_toLowerCase(JNIEnv* env, jclass, jstring javaString, jstring localeName) {
-    ScopedJavaUnicodeString scopedString(env, javaString);
-    UnicodeString& s(scopedString.unicodeString());
-    UnicodeString original(s);
-    s.toLower(Locale::createFromName(ScopedUtfChars(env, localeName).c_str()));
-    return s == original ? javaString : env->NewString(s.getBuffer(), s.length());
+  ScopedJavaUnicodeString scopedString(env, javaString);
+  if (!scopedString.valid()) {
+    return NULL;
+  }
+  UnicodeString& s(scopedString.unicodeString());
+  UnicodeString original(s);
+  s.toLower(Locale::createFromName(ScopedUtfChars(env, localeName).c_str()));
+  return s == original ? javaString : env->NewString(s.getBuffer(), s.length());
 }
 
 static jstring ICU_toUpperCase(JNIEnv* env, jclass, jstring javaString, jstring localeName) {
-    ScopedJavaUnicodeString scopedString(env, javaString);
-    UnicodeString& s(scopedString.unicodeString());
-    UnicodeString original(s);
-    s.toUpper(Locale::createFromName(ScopedUtfChars(env, localeName).c_str()));
-    return s == original ? javaString : env->NewString(s.getBuffer(), s.length());
+  ScopedJavaUnicodeString scopedString(env, javaString);
+  if (!scopedString.valid()) {
+    return NULL;
+  }
+  UnicodeString& s(scopedString.unicodeString());
+  UnicodeString original(s);
+  s.toUpper(Locale::createFromName(ScopedUtfChars(env, localeName).c_str()));
+  return s == original ? javaString : env->NewString(s.getBuffer(), s.length());
 }
 
 static jstring versionString(JNIEnv* env, const UVersionInfo& version) {
@@ -627,6 +655,13 @@
     return env->NewStringUTF(versionString);
 }
 
+static jstring ICU_getCldrVersion(JNIEnv* env, jclass) {
+  UErrorCode status = U_ZERO_ERROR;
+  UVersionInfo cldrVersion;
+  ulocdata_getCLDRVersion(cldrVersion, &status);
+  return versionString(env, cldrVersion);
+}
+
 static jstring ICU_getIcuVersion(JNIEnv* env, jclass) {
     UVersionInfo icuVersion;
     u_getVersion(icuVersion);
@@ -639,7 +674,6 @@
     return versionString(env, unicodeVersion);
 }
 
-
 struct EnumerationCounter {
     const size_t count;
     EnumerationCounter(size_t count) : count(count) {}
@@ -665,6 +699,26 @@
     return result;
 }
 
+static jstring ICU_getBestDateTimePattern(JNIEnv* env, jclass, jstring javaPattern, jstring javaLocaleName) {
+  Locale locale = getLocale(env, javaLocaleName);
+  UErrorCode status = U_ZERO_ERROR;
+  DateTimePatternGenerator* generator = DateTimePatternGenerator::createInstance(locale, status);
+  if (maybeThrowIcuException(env, "DateTimePatternGenerator::createInstance", status)) {
+    return NULL;
+  }
+
+  ScopedJavaUnicodeString patternHolder(env, javaPattern);
+  if (!patternHolder.valid()) {
+    return NULL;
+  }
+  UnicodeString result(generator->getBestPattern(patternHolder.unicodeString(), status));
+  if (maybeThrowIcuException(env, "DateTimePatternGenerator::getBestPattern", status)) {
+    return NULL;
+  }
+
+  return env->NewString(result.getBuffer(), result.length());
+}
+
 static JNINativeMethod gMethods[] = {
     NATIVE_METHOD(ICU, addLikelySubtags, "(Ljava/lang/String;)Ljava/lang/String;"),
     NATIVE_METHOD(ICU, getAvailableBreakIteratorLocalesNative, "()[Ljava/lang/String;"),
@@ -674,6 +728,8 @@
     NATIVE_METHOD(ICU, getAvailableDateFormatLocalesNative, "()[Ljava/lang/String;"),
     NATIVE_METHOD(ICU, getAvailableLocalesNative, "()[Ljava/lang/String;"),
     NATIVE_METHOD(ICU, getAvailableNumberFormatLocalesNative, "()[Ljava/lang/String;"),
+    NATIVE_METHOD(ICU, getBestDateTimePattern, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(ICU, getCldrVersion, "()Ljava/lang/String;"),
     NATIVE_METHOD(ICU, getCurrencyCode, "(Ljava/lang/String;)Ljava/lang/String;"),
     NATIVE_METHOD(ICU, getCurrencyDisplayName, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
     NATIVE_METHOD(ICU, getCurrencyFractionDigits, "(Ljava/lang/String;)I"),
diff --git a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
index 5865081..c025a60 100644
--- a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
+++ b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
@@ -16,210 +16,208 @@
 
 #define LOG_TAG "NativeBreakIterator"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
 #include "ScopedUtfChars.h"
-#include "unicode/ubrk.h"
+#include "unicode/brkiter.h"
 #include "unicode/putil.h"
 #include <stdlib.h>
 
+// ICU documentation: http://icu-project.org/apiref/icu4c/classBreakIterator.html
+
+static BreakIterator* toBreakIterator(jint address) {
+  return reinterpret_cast<BreakIterator*>(static_cast<uintptr_t>(address));
+}
+
 /**
- * ICU4C 4.6 doesn't let us update the pointers inside a UBreakIterator to track our char[] as it
- * moves around the heap. This class pins the char[] for the lifetime of the
- * java.text.BreakIterator. It also holds a global reference to the java.lang.String that owns the
- * char[] so that the char[] can't be GCed.
+ * We use ICU4C's BreakIterator class, but our input is on the Java heap and potentially moving
+ * around between calls. This wrapper class ensures that our RegexMatcher is always pointing at
+ * the current location of the char[]. Earlier versions of Android simply copied the data to the
+ * native heap, but that's wasteful and hides allocations from the garbage collector.
  */
-class BreakIteratorPeer {
-public:
-    static BreakIteratorPeer* fromAddress(jint address) {
-        return reinterpret_cast<BreakIteratorPeer*>(static_cast<uintptr_t>(address));
+class BreakIteratorAccessor {
+ public:
+  BreakIteratorAccessor(JNIEnv* env, jint address, jstring javaInput, bool reset) {
+    init(env, address);
+    mJavaInput = javaInput;
+
+    if (mJavaInput == NULL) {
+      return;
     }
 
-    uintptr_t toAddress() {
-        return reinterpret_cast<uintptr_t>(this);
+    mChars = env->GetStringChars(mJavaInput, NULL);
+    if (mChars == NULL) {
+      return;
     }
 
-    BreakIteratorPeer(UBreakIterator* it) : mIt(it), mString(NULL), mChars(NULL) {
+    mUText = utext_openUChars(NULL, mChars, env->GetStringLength(mJavaInput), &mStatus);
+    if (mUText == NULL) {
+      return;
     }
 
-    void setText(JNIEnv* env, jstring s) {
-        releaseString(env);
-
-        mString = reinterpret_cast<jstring>(env->NewGlobalRef(s));
-        mChars = env->GetStringChars(mString, NULL);
-        if (mChars == NULL) {
-            return;
-        }
-
-        size_t charCount = env->GetStringLength(mString);
-        UErrorCode status = U_ZERO_ERROR;
-        ubrk_setText(mIt, mChars, charCount, &status);
-        maybeThrowIcuException(env, "ubrk_setText", status);
+    if (reset) {
+      mBreakIterator->setText(mUText, mStatus);
+    } else {
+      mBreakIterator->refreshInputText(mUText, mStatus);
     }
+  }
 
-    BreakIteratorPeer* clone(JNIEnv* env) {
-        UErrorCode status = U_ZERO_ERROR;
-        jint bufferSize = U_BRK_SAFECLONE_BUFFERSIZE;
-        UBreakIterator* it = ubrk_safeClone(mIt, NULL, &bufferSize, &status);
-        if (maybeThrowIcuException(env, "ubrk_safeClone", status)) {
-            return NULL;
-        }
-        BreakIteratorPeer* result = new BreakIteratorPeer(it);
-        if (mString != NULL) {
-            result->setText(env, mString);
-        }
-        return result;
+  BreakIteratorAccessor(JNIEnv* env, jint address) {
+    init(env, address);
+  }
+
+  ~BreakIteratorAccessor() {
+    utext_close(mUText);
+    if (mJavaInput) {
+      mEnv->ReleaseStringChars(mJavaInput, mChars);
     }
+    maybeThrowIcuException(mEnv, "utext_close", mStatus);
+  }
 
-    void close(JNIEnv* env) {
-        if (mIt != NULL) {
-            ubrk_close(mIt);
-            mIt = NULL;
-        }
-        releaseString(env);
-    }
+  BreakIterator* operator->() {
+    return mBreakIterator;
+  }
 
-    ~BreakIteratorPeer() {
-        if (mIt != NULL || mString != NULL) {
-            LOG_ALWAYS_FATAL("BreakIteratorPeer deleted but not closed");
-        }
-    }
+  UErrorCode& status() {
+    return mStatus;
+  }
 
-    UBreakIterator* breakIterator() {
-        return mIt;
-    }
+ private:
+  void init(JNIEnv* env, jint address) {
+    mEnv = env;
+    mJavaInput = NULL;
+    mBreakIterator = toBreakIterator(address);
+    mChars = NULL;
+    mStatus = U_ZERO_ERROR;
+    mUText = NULL;
+  }
 
-private:
-    UBreakIterator* mIt;
+  JNIEnv* mEnv;
+  jstring mJavaInput;
+  BreakIterator* mBreakIterator;
+  const jchar* mChars;
+  UErrorCode mStatus;
+  UText* mUText;
 
-    jstring mString;
-    const jchar* mChars;
-
-    void releaseString(JNIEnv* env) {
-        if (mString != NULL) {
-            env->ReleaseStringChars(mString, mChars);
-            env->DeleteGlobalRef(mString);
-            mString = NULL;
-        }
-    }
-
-    // Disallow copy and assignment.
-    BreakIteratorPeer(const BreakIteratorPeer&);
-    void operator=(const BreakIteratorPeer&);
+  // Disallow copy and assignment.
+  BreakIteratorAccessor(const BreakIteratorAccessor&);
+  void operator=(const BreakIteratorAccessor&);
 };
 
-static UBreakIterator* breakIterator(jint address) {
-    return BreakIteratorPeer::fromAddress(address)->breakIterator();
-}
-
-static jint makeIterator(JNIEnv* env, jstring locale, UBreakIteratorType type) {
-    UErrorCode status = U_ZERO_ERROR;
-    const ScopedUtfChars localeChars(env, locale);
-    if (localeChars.c_str() == NULL) {
-        return 0;
-    }
-    UBreakIterator* it = ubrk_open(type, localeChars.c_str(), NULL, 0, &status);
-    if (maybeThrowIcuException(env, "ubrk_open", status)) {
-        return 0;
-    }
-    return (new BreakIteratorPeer(it))->toAddress();
-}
-
-static jint NativeBreakIterator_getCharacterInstanceImpl(JNIEnv* env, jclass, jstring locale) {
-    return makeIterator(env, locale, UBRK_CHARACTER);
-}
-
-static jint NativeBreakIterator_getLineInstanceImpl(JNIEnv* env, jclass, jstring locale) {
-    return makeIterator(env, locale, UBRK_LINE);
-}
-
-static jint NativeBreakIterator_getSentenceInstanceImpl(JNIEnv* env, jclass, jstring locale) {
-    return makeIterator(env, locale, UBRK_SENTENCE);
-}
-
-static jint NativeBreakIterator_getWordInstanceImpl(JNIEnv* env, jclass, jstring locale) {
-    return makeIterator(env, locale, UBRK_WORD);
-}
-
-static void NativeBreakIterator_closeBreakIteratorImpl(JNIEnv* env, jclass, jint address) {
-    BreakIteratorPeer* peer = BreakIteratorPeer::fromAddress(address);
-    peer->close(env);
-    delete peer;
-}
+#define MAKE_BREAK_ITERATOR_INSTANCE(F) \
+  UErrorCode status = U_ZERO_ERROR; \
+  const ScopedUtfChars localeChars(env, javaLocale); \
+  if (localeChars.c_str() == NULL) { \
+    return 0; \
+  } \
+  Locale locale(Locale::createFromName(localeChars.c_str())); \
+  BreakIterator* it = F(locale, status); \
+  if (maybeThrowIcuException(env, "ubrk_open", status)) { \
+    return 0; \
+  } \
+  return reinterpret_cast<uintptr_t>(it)
 
 static jint NativeBreakIterator_cloneImpl(JNIEnv* env, jclass, jint address) {
-    return BreakIteratorPeer::fromAddress(address)->clone(env)->toAddress();
+  BreakIteratorAccessor it(env, address);
+  return reinterpret_cast<uintptr_t>(it->clone());
 }
 
-static void NativeBreakIterator_setTextImpl(JNIEnv* env, jclass, jint address, jstring javaText) {
-    BreakIteratorPeer* peer = BreakIteratorPeer::fromAddress(address);
-    peer->setText(env, javaText);
+static void NativeBreakIterator_closeImpl(JNIEnv*, jclass, jint address) {
+  delete toBreakIterator(address);
 }
 
-static jboolean NativeBreakIterator_isBoundaryImpl(JNIEnv*, jclass, jint address, jint offset) {
-    return ubrk_isBoundary(breakIterator(address), offset);
+static jint NativeBreakIterator_currentImpl(JNIEnv* env, jclass, jint address, jstring javaInput) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->current();
 }
 
-static jint NativeBreakIterator_nextImpl(JNIEnv*, jclass, jint address, jint n) {
-    UBreakIterator* bi = breakIterator(address);
-    if (n < 0) {
-        while (n++ < -1) {
-            ubrk_previous(bi);
-        }
-        return ubrk_previous(bi);
-    } else if (n == 0) {
-        return ubrk_current(bi);
-    } else {
-        while (n-- > 1) {
-            ubrk_next(bi);
-        }
-        return ubrk_next(bi);
+static jint NativeBreakIterator_firstImpl(JNIEnv* env, jclass, jint address, jstring javaInput) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->first();
+}
+
+static jint NativeBreakIterator_followingImpl(JNIEnv* env, jclass, jint address, jstring javaInput, jint offset) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->following(offset);
+}
+
+static jint NativeBreakIterator_getCharacterInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) {
+  MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createCharacterInstance);
+}
+
+static jint NativeBreakIterator_getLineInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) {
+  MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createLineInstance);
+}
+
+static jint NativeBreakIterator_getSentenceInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) {
+  MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createSentenceInstance);
+}
+
+static jint NativeBreakIterator_getWordInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) {
+  MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createWordInstance);
+}
+
+static jboolean NativeBreakIterator_isBoundaryImpl(JNIEnv* env, jclass, jint address, jstring javaInput, jint offset) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->isBoundary(offset);
+}
+
+static jint NativeBreakIterator_lastImpl(JNIEnv* env, jclass, jint address, jstring javaInput) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->last();
+}
+
+static jint NativeBreakIterator_nextImpl(JNIEnv* env, jclass, jint address, jstring javaInput, jint n) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  if (n < 0) {
+    while (n++ < -1) {
+      it->previous();
     }
-    return -1;
+    return it->previous();
+  } else if (n == 0) {
+    return it->current();
+  } else {
+    while (n-- > 1) {
+      it->next();
+    }
+    return it->next();
+  }
+  return -1;
 }
 
-static jint NativeBreakIterator_precedingImpl(JNIEnv*, jclass, jint address, jint offset) {
-    return ubrk_preceding(breakIterator(address), offset);
+static jint NativeBreakIterator_precedingImpl(JNIEnv* env, jclass, jint address, jstring javaInput, jint offset) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->preceding(offset);
 }
 
-static jint NativeBreakIterator_firstImpl(JNIEnv*, jclass, jint address) {
-    return ubrk_first(breakIterator(address));
+static jint NativeBreakIterator_previousImpl(JNIEnv* env, jclass, jint address, jstring javaInput) {
+  BreakIteratorAccessor it(env, address, javaInput, false);
+  return it->previous();
 }
 
-static jint NativeBreakIterator_followingImpl(JNIEnv*, jclass, jint address, jint offset) {
-    return ubrk_following(breakIterator(address), offset);
-}
-
-static jint NativeBreakIterator_currentImpl(JNIEnv*, jclass, jint address) {
-    return ubrk_current(breakIterator(address));
-}
-
-static jint NativeBreakIterator_previousImpl(JNIEnv*, jclass, jint address) {
-    return ubrk_previous(breakIterator(address));
-}
-
-static jint NativeBreakIterator_lastImpl(JNIEnv*, jclass, jint address) {
-    return ubrk_last(breakIterator(address));
+static void NativeBreakIterator_setTextImpl(JNIEnv* env, jclass, jint address, jstring javaInput) {
+  BreakIteratorAccessor it(env, address, javaInput, true);
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(NativeBreakIterator, cloneImpl, "(I)I"),
-    NATIVE_METHOD(NativeBreakIterator, closeBreakIteratorImpl, "(I)V"),
-    NATIVE_METHOD(NativeBreakIterator, currentImpl, "(I)I"),
-    NATIVE_METHOD(NativeBreakIterator, firstImpl, "(I)I"),
-    NATIVE_METHOD(NativeBreakIterator, followingImpl, "(II)I"),
-    NATIVE_METHOD(NativeBreakIterator, getCharacterInstanceImpl, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeBreakIterator, getLineInstanceImpl, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeBreakIterator, getSentenceInstanceImpl, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeBreakIterator, getWordInstanceImpl, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeBreakIterator, isBoundaryImpl, "(II)Z"),
-    NATIVE_METHOD(NativeBreakIterator, lastImpl, "(I)I"),
-    NATIVE_METHOD(NativeBreakIterator, nextImpl, "(II)I"),
-    NATIVE_METHOD(NativeBreakIterator, precedingImpl, "(II)I"),
-    NATIVE_METHOD(NativeBreakIterator, previousImpl, "(I)I"),
-    NATIVE_METHOD(NativeBreakIterator, setTextImpl, "(ILjava/lang/String;)V"),
+  NATIVE_METHOD(NativeBreakIterator, cloneImpl, "(I)I"),
+  NATIVE_METHOD(NativeBreakIterator, closeImpl, "(I)V"),
+  NATIVE_METHOD(NativeBreakIterator, currentImpl, "(ILjava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, firstImpl, "(ILjava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, followingImpl, "(ILjava/lang/String;I)I"),
+  NATIVE_METHOD(NativeBreakIterator, getCharacterInstanceImpl, "(Ljava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, getLineInstanceImpl, "(Ljava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, getSentenceInstanceImpl, "(Ljava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, getWordInstanceImpl, "(Ljava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, isBoundaryImpl, "(ILjava/lang/String;I)Z"),
+  NATIVE_METHOD(NativeBreakIterator, lastImpl, "(ILjava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, nextImpl, "(ILjava/lang/String;I)I"),
+  NATIVE_METHOD(NativeBreakIterator, precedingImpl, "(ILjava/lang/String;I)I"),
+  NATIVE_METHOD(NativeBreakIterator, previousImpl, "(ILjava/lang/String;)I"),
+  NATIVE_METHOD(NativeBreakIterator, setTextImpl, "(ILjava/lang/String;)V"),
 };
 void register_libcore_icu_NativeBreakIterator(JNIEnv* env) {
-    jniRegisterNativeMethods(env, "libcore/icu/NativeBreakIterator", gMethods, NELEM(gMethods));
+  jniRegisterNativeMethods(env, "libcore/icu/NativeBreakIterator", gMethods, NELEM(gMethods));
 }
diff --git a/luni/src/main/native/libcore_icu_NativeCollation.cpp b/luni/src/main/native/libcore_icu_NativeCollation.cpp
index 32b1cc4..98a78ea 100644
--- a/luni/src/main/native/libcore_icu_NativeCollation.cpp
+++ b/luni/src/main/native/libcore_icu_NativeCollation.cpp
@@ -9,6 +9,7 @@
 
 #define LOG_TAG "NativeCollation"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
diff --git a/luni/src/main/native/libcore_icu_NativeConverter.cpp b/luni/src/main/native/libcore_icu_NativeConverter.cpp
index 6b08ee0..24dfa6f 100644
--- a/luni/src/main/native/libcore_icu_NativeConverter.cpp
+++ b/luni/src/main/native/libcore_icu_NativeConverter.cpp
@@ -15,6 +15,7 @@
 
 #define LOG_TAG "NativeConverter"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
@@ -588,7 +589,7 @@
     }
     // Get Java's canonical name for this charset.
     jstring javaCanonicalName = getJavaCanonicalName(env, icuCanonicalName);
-    if (env->ExceptionOccurred()) {
+    if (env->ExceptionCheck()) {
         return NULL;
     }
 
@@ -604,14 +605,14 @@
 
     // Get the aliases for this charset.
     jobjectArray aliases = getAliases(env, icuCanonicalName);
-    if (env->ExceptionOccurred()) {
+    if (env->ExceptionCheck()) {
         return NULL;
     }
 
     // Construct the CharsetICU object.
-    jmethodID charsetConstructor = env->GetMethodID(JniConstants::charsetICUClass, "<init>",
+    static jmethodID charsetConstructor = env->GetMethodID(JniConstants::charsetICUClass, "<init>",
             "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
-    if (env->ExceptionOccurred()) {
+    if (env->ExceptionCheck()) {
         return NULL;
     }
     return env->NewObject(JniConstants::charsetICUClass, charsetConstructor,
diff --git a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
index 0406094..efed954 100644
--- a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
+++ b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "NativeDecimalFormat"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
@@ -35,11 +36,11 @@
 #include <stdlib.h>
 #include <string.h>
 
-static DecimalFormat* toDecimalFormat(jint addr) {
+static DecimalFormat* toDecimalFormat(jlong addr) {
     return reinterpret_cast<DecimalFormat*>(static_cast<uintptr_t>(addr));
 }
 
-static UNumberFormat* toUNumberFormat(jint addr) {
+static UNumberFormat* toUNumberFormat(jlong addr) {
     return reinterpret_cast<UNumberFormat*>(static_cast<uintptr_t>(addr));
 }
 
@@ -86,7 +87,7 @@
     return result;
 }
 
-static void NativeDecimalFormat_setDecimalFormatSymbols(JNIEnv* env, jclass, jint addr,
+static void NativeDecimalFormat_setDecimalFormatSymbols(JNIEnv* env, jclass, jlong addr,
         jstring currencySymbol, jchar decimalSeparator, jchar digit, jstring exponentSeparator,
         jchar groupingSeparator, jstring infinity,
         jstring internationalCurrencySymbol, jchar minusSign,
@@ -100,19 +101,18 @@
     toDecimalFormat(addr)->adoptDecimalFormatSymbols(symbols);
 }
 
-static jint NativeDecimalFormat_open(JNIEnv* env, jclass, jstring pattern0,
+static jlong NativeDecimalFormat_open(JNIEnv* env, jclass, jstring pattern0,
         jstring currencySymbol, jchar decimalSeparator, jchar digit, jstring exponentSeparator,
         jchar groupingSeparator, jstring infinity,
         jstring internationalCurrencySymbol, jchar minusSign,
         jchar monetaryDecimalSeparator, jstring nan, jchar patternSeparator,
         jchar percent, jchar perMill, jchar zeroDigit) {
-    if (pattern0 == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
-    }
     UErrorCode status = U_ZERO_ERROR;
     UParseError parseError;
     ScopedJavaUnicodeString pattern(env, pattern0);
+    if (!pattern.valid()) {
+      return 0;
+    }
     DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env,
             currencySymbol, decimalSeparator, digit, exponentSeparator, groupingSeparator,
             infinity, internationalCurrencySymbol, minusSign,
@@ -123,20 +123,20 @@
         delete symbols;
     }
     maybeThrowIcuException(env, "DecimalFormat::DecimalFormat", status);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(fmt));
+    return reinterpret_cast<uintptr_t>(fmt);
 }
 
-static void NativeDecimalFormat_close(JNIEnv*, jclass, jint addr) {
+static void NativeDecimalFormat_close(JNIEnv*, jclass, jlong addr) {
     delete toDecimalFormat(addr);
 }
 
-static void NativeDecimalFormat_setRoundingMode(JNIEnv*, jclass, jint addr, jint mode, jdouble increment) {
+static void NativeDecimalFormat_setRoundingMode(JNIEnv*, jclass, jlong addr, jint mode, jdouble increment) {
     DecimalFormat* fmt = toDecimalFormat(addr);
     fmt->setRoundingMode(static_cast<DecimalFormat::ERoundingMode>(mode));
     fmt->setRoundingIncrement(increment);
 }
 
-static void NativeDecimalFormat_setSymbol(JNIEnv* env, jclass, jint addr, jint javaSymbol, jstring javaValue) {
+static void NativeDecimalFormat_setSymbol(JNIEnv* env, jclass, jlong addr, jint javaSymbol, jstring javaValue) {
     ScopedStringChars value(env, javaValue);
     if (value.get() == NULL) {
         return;
@@ -147,17 +147,17 @@
     maybeThrowIcuException(env, "unum_setSymbol", status);
 }
 
-static void NativeDecimalFormat_setAttribute(JNIEnv*, jclass, jint addr, jint javaAttr, jint value) {
+static void NativeDecimalFormat_setAttribute(JNIEnv*, jclass, jlong addr, jint javaAttr, jint value) {
     UNumberFormatAttribute attr = static_cast<UNumberFormatAttribute>(javaAttr);
     unum_setAttribute(toUNumberFormat(addr), attr, value);
 }
 
-static jint NativeDecimalFormat_getAttribute(JNIEnv*, jclass, jint addr, jint javaAttr) {
+static jint NativeDecimalFormat_getAttribute(JNIEnv*, jclass, jlong addr, jint javaAttr) {
     UNumberFormatAttribute attr = static_cast<UNumberFormatAttribute>(javaAttr);
     return unum_getAttribute(toUNumberFormat(addr), attr);
 }
 
-static void NativeDecimalFormat_setTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr, jstring javaValue) {
+static void NativeDecimalFormat_setTextAttribute(JNIEnv* env, jclass, jlong addr, jint javaAttr, jstring javaValue) {
     ScopedStringChars value(env, javaValue);
     if (value.get() == NULL) {
         return;
@@ -168,7 +168,7 @@
     maybeThrowIcuException(env, "unum_setTextAttribute", status);
 }
 
-static jstring NativeDecimalFormat_getTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr) {
+static jstring NativeDecimalFormat_getTextAttribute(JNIEnv* env, jclass, jlong addr, jint javaAttr) {
     UErrorCode status = U_ZERO_ERROR;
     UNumberFormat* fmt = toUNumberFormat(addr);
     UNumberFormatTextAttribute attr = static_cast<UNumberFormatTextAttribute>(javaAttr);
@@ -187,12 +187,11 @@
     return maybeThrowIcuException(env, "unum_getTextAttribute", status) ? NULL : env->NewString(chars.get(), charCount);
 }
 
-static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized, jstring pattern0) {
-    if (pattern0 == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
+static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jlong addr, jboolean localized, jstring pattern0) {
     ScopedJavaUnicodeString pattern(env, pattern0);
+    if (!pattern.valid()) {
+      return;
+    }
     DecimalFormat* fmt = toDecimalFormat(addr);
     UErrorCode status = U_ZERO_ERROR;
     const char* function;
@@ -206,7 +205,7 @@
     maybeThrowIcuException(env, function, status);
 }
 
-static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized) {
+static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jlong addr, jboolean localized) {
     DecimalFormat* fmt = toDecimalFormat(addr);
     UnicodeString pattern;
     if (localized) {
@@ -242,7 +241,7 @@
 }
 
 template <typename T>
-static jcharArray format(JNIEnv* env, jint addr, jobject fpIter, T val) {
+static jcharArray format(JNIEnv* env, jlong addr, jobject fpIter, T val) {
     UErrorCode status = U_ZERO_ERROR;
     UnicodeString str;
     DecimalFormat* fmt = toDecimalFormat(addr);
@@ -252,15 +251,15 @@
     return formatResult(env, str, pfpi, fpIter);
 }
 
-static jcharArray NativeDecimalFormat_formatLong(JNIEnv* env, jclass, jint addr, jlong value, jobject fpIter) {
+static jcharArray NativeDecimalFormat_formatLong(JNIEnv* env, jclass, jlong addr, jlong value, jobject fpIter) {
+  return format(env, addr, fpIter, value);
+}
+
+static jcharArray NativeDecimalFormat_formatDouble(JNIEnv* env, jclass, jlong addr, jdouble value, jobject fpIter) {
     return format(env, addr, fpIter, value);
 }
 
-static jcharArray NativeDecimalFormat_formatDouble(JNIEnv* env, jclass, jint addr, jdouble value, jobject fpIter) {
-    return format(env, addr, fpIter, value);
-}
-
-static jcharArray NativeDecimalFormat_formatDigitList(JNIEnv* env, jclass, jint addr, jstring value, jobject fpIter) {
+static jcharArray NativeDecimalFormat_formatDigitList(JNIEnv* env, jclass, jlong addr, jstring value, jobject fpIter) {
     ScopedUtfChars chars(env, value);
     if (chars.c_str() == NULL) {
         return NULL;
@@ -281,7 +280,7 @@
     return env->NewObject(JniConstants::bigDecimalClass, gBigDecimal_init, str);
 }
 
-static jobject NativeDecimalFormat_parse(JNIEnv* env, jclass, jint addr, jstring text,
+static jobject NativeDecimalFormat_parse(JNIEnv* env, jclass, jlong addr, jstring text,
         jobject position, jboolean parseBigDecimal) {
 
     static jmethodID gPP_getIndex = env->GetMethodID(JniConstants::parsePositionClass, "getIndex", "()I");
@@ -299,6 +298,9 @@
     Formattable res;
     ParsePosition pp(parsePos);
     ScopedJavaUnicodeString src(env, text);
+    if (!src.valid()) {
+      return NULL;
+    }
     DecimalFormat* fmt = toDecimalFormat(addr);
     fmt->parse(src.unicodeString(), res, pp);
 
@@ -334,28 +336,28 @@
     }
 }
 
-static jint NativeDecimalFormat_cloneImpl(JNIEnv*, jclass, jint addr) {
+static jlong NativeDecimalFormat_cloneImpl(JNIEnv*, jclass, jlong addr) {
     DecimalFormat* fmt = toDecimalFormat(addr);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(fmt->clone()));
+    return reinterpret_cast<uintptr_t>(fmt->clone());
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(NativeDecimalFormat, applyPatternImpl, "(IZLjava/lang/String;)V"),
-    NATIVE_METHOD(NativeDecimalFormat, cloneImpl, "(I)I"),
-    NATIVE_METHOD(NativeDecimalFormat, close, "(I)V"),
-    NATIVE_METHOD(NativeDecimalFormat, formatDouble, "(IDLlibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
-    NATIVE_METHOD(NativeDecimalFormat, formatLong, "(IJLlibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
-    NATIVE_METHOD(NativeDecimalFormat, formatDigitList, "(ILjava/lang/String;Llibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
-    NATIVE_METHOD(NativeDecimalFormat, getAttribute, "(II)I"),
-    NATIVE_METHOD(NativeDecimalFormat, getTextAttribute, "(II)Ljava/lang/String;"),
-    NATIVE_METHOD(NativeDecimalFormat, open, "(Ljava/lang/String;Ljava/lang/String;CCLjava/lang/String;CLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)I"),
-    NATIVE_METHOD(NativeDecimalFormat, parse, "(ILjava/lang/String;Ljava/text/ParsePosition;Z)Ljava/lang/Number;"),
-    NATIVE_METHOD(NativeDecimalFormat, setAttribute, "(III)V"),
-    NATIVE_METHOD(NativeDecimalFormat, setDecimalFormatSymbols, "(ILjava/lang/String;CCLjava/lang/String;CLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)V"),
-    NATIVE_METHOD(NativeDecimalFormat, setRoundingMode, "(IID)V"),
-    NATIVE_METHOD(NativeDecimalFormat, setSymbol, "(IILjava/lang/String;)V"),
-    NATIVE_METHOD(NativeDecimalFormat, setTextAttribute, "(IILjava/lang/String;)V"),
-    NATIVE_METHOD(NativeDecimalFormat, toPatternImpl, "(IZ)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeDecimalFormat, applyPatternImpl, "(JZLjava/lang/String;)V"),
+    NATIVE_METHOD(NativeDecimalFormat, cloneImpl, "(J)I"),
+    NATIVE_METHOD(NativeDecimalFormat, close, "(J)V"),
+    NATIVE_METHOD(NativeDecimalFormat, formatDouble, "(JDLlibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
+    NATIVE_METHOD(NativeDecimalFormat, formatLong, "(JJLlibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
+    NATIVE_METHOD(NativeDecimalFormat, formatDigitList, "(JLjava/lang/String;Llibcore/icu/NativeDecimalFormat$FieldPositionIterator;)[C"),
+    NATIVE_METHOD(NativeDecimalFormat, getAttribute, "(JI)I"),
+    NATIVE_METHOD(NativeDecimalFormat, getTextAttribute, "(JI)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeDecimalFormat, open, "(Ljava/lang/String;Ljava/lang/String;CCLjava/lang/String;CLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)J"),
+    NATIVE_METHOD(NativeDecimalFormat, parse, "(JLjava/lang/String;Ljava/text/ParsePosition;Z)Ljava/lang/Number;"),
+    NATIVE_METHOD(NativeDecimalFormat, setAttribute, "(JII)V"),
+    NATIVE_METHOD(NativeDecimalFormat, setDecimalFormatSymbols, "(JLjava/lang/String;CCLjava/lang/String;CLjava/lang/String;Ljava/lang/String;CCLjava/lang/String;CCCC)V"),
+    NATIVE_METHOD(NativeDecimalFormat, setRoundingMode, "(JID)V"),
+    NATIVE_METHOD(NativeDecimalFormat, setSymbol, "(JILjava/lang/String;)V"),
+    NATIVE_METHOD(NativeDecimalFormat, setTextAttribute, "(JILjava/lang/String;)V"),
+    NATIVE_METHOD(NativeDecimalFormat, toPatternImpl, "(JZ)Ljava/lang/String;"),
 };
 void register_libcore_icu_NativeDecimalFormat(JNIEnv* env) {
     jniRegisterNativeMethods(env, "libcore/icu/NativeDecimalFormat", gMethods, NELEM(gMethods));
diff --git a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
index 693e944..8ae42d9 100644
--- a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
+++ b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "NativeNormalizer"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
@@ -23,28 +24,34 @@
 #include "unicode/normlzr.h"
 
 static jstring NativeNormalizer_normalizeImpl(JNIEnv* env, jclass, jstring s, jint intMode) {
-    ScopedJavaUnicodeString src(env, s);
-    UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
-    UErrorCode status = U_ZERO_ERROR;
-    UnicodeString dst;
-    Normalizer::normalize(src.unicodeString(), mode, 0, dst, status);
-    maybeThrowIcuException(env, "Normalizer::normalize", status);
-    return dst.isBogus() ? NULL : env->NewString(dst.getBuffer(), dst.length());
+  ScopedJavaUnicodeString src(env, s);
+  if (!src.valid()) {
+    return NULL;
+  }
+  UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
+  UErrorCode status = U_ZERO_ERROR;
+  UnicodeString dst;
+  Normalizer::normalize(src.unicodeString(), mode, 0, dst, status);
+  maybeThrowIcuException(env, "Normalizer::normalize", status);
+  return dst.isBogus() ? NULL : env->NewString(dst.getBuffer(), dst.length());
 }
 
 static jboolean NativeNormalizer_isNormalizedImpl(JNIEnv* env, jclass, jstring s, jint intMode) {
-    ScopedJavaUnicodeString src(env, s);
-    UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
-    UErrorCode status = U_ZERO_ERROR;
-    UBool result = Normalizer::isNormalized(src.unicodeString(), mode, status);
-    maybeThrowIcuException(env, "Normalizer::isNormalized", status);
-    return result;
+  ScopedJavaUnicodeString src(env, s);
+  if (!src.valid()) {
+    return JNI_FALSE;
+  }
+  UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
+  UErrorCode status = U_ZERO_ERROR;
+  UBool result = Normalizer::isNormalized(src.unicodeString(), mode, status);
+  maybeThrowIcuException(env, "Normalizer::isNormalized", status);
+  return result;
 }
 
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(NativeNormalizer, normalizeImpl, "(Ljava/lang/String;I)Ljava/lang/String;"),
-    NATIVE_METHOD(NativeNormalizer, isNormalizedImpl, "(Ljava/lang/String;I)Z"),
+  NATIVE_METHOD(NativeNormalizer, normalizeImpl, "(Ljava/lang/String;I)Ljava/lang/String;"),
+  NATIVE_METHOD(NativeNormalizer, isNormalizedImpl, "(Ljava/lang/String;I)Z"),
 };
 void register_libcore_icu_NativeNormalizer(JNIEnv* env) {
-    jniRegisterNativeMethods(env, "libcore/icu/NativeNormalizer", gMethods, NELEM(gMethods));
+  jniRegisterNativeMethods(env, "libcore/icu/NativeNormalizer", gMethods, NELEM(gMethods));
 }
diff --git a/luni/src/main/native/libcore_icu_NativePluralRules.cpp b/luni/src/main/native/libcore_icu_NativePluralRules.cpp
index df228ff..3253eb8 100644
--- a/luni/src/main/native/libcore_icu_NativePluralRules.cpp
+++ b/luni/src/main/native/libcore_icu_NativePluralRules.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "NativePluralRules"
 
+#include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
new file mode 100644
index 0000000..8c8682a
--- /dev/null
+++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TimeZoneNames"
+
+#include "IcuUtilities.h"
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "JniException.h"
+#include "ScopedJavaUnicodeString.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "UniquePtr.h"
+#include "unicode/calendar.h"
+#include "unicode/timezone.h"
+#include "unicode/tznames.h"
+
+static bool isUtc(const UnicodeString& id) {
+  static const UnicodeString kEtcUct("Etc/UCT", 7, US_INV);
+  static const UnicodeString kEtcUtc("Etc/UTC", 7, US_INV);
+  static const UnicodeString kEtcUniversal("Etc/Universal", 13, US_INV);
+  static const UnicodeString kEtcZulu("Etc/Zulu", 8, US_INV);
+
+  static const UnicodeString kUct("UCT", 3, US_INV);
+  static const UnicodeString kUtc("UTC", 3, US_INV);
+  static const UnicodeString kUniversal("Universal", 9, US_INV);
+  static const UnicodeString kZulu("Zulu", 4, US_INV);
+
+  return id == kEtcUct || id == kEtcUtc || id == kEtcUniversal || id == kEtcZulu ||
+      id == kUct || id == kUtc || id == kUniversal || id == kZulu;
+}
+
+static void setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const UnicodeString& s) {
+  // Fill in whatever we got. We don't use the display names if they're "GMT[+-]xx:xx"
+  // because icu4c doesn't use the up-to-date time zone transition data, so it gets these
+  // wrong. TimeZone.getDisplayName creates accurate names on demand.
+  // TODO: investigate whether it's worth doing that work once in the Java wrapper instead of on-demand.
+  static const UnicodeString kGmt("GMT", 3, US_INV);
+  if (!s.isBogus() && !s.startsWith(kGmt)) {
+    ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length()));
+    env->SetObjectArrayElement(array, i, javaString.get());
+  }
+}
+
+static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring localeName, jobjectArray result) {
+  Locale locale = getLocale(env, localeName);
+
+  UErrorCode status = U_ZERO_ERROR;
+  UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(locale, status));
+  if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) {
+    return;
+  }
+
+  const UDate now(Calendar::getNow());
+
+  static const UnicodeString kUtc("UTC", 3, US_INV);
+  static const UnicodeString pacific_apia("Pacific/Apia", 12, US_INV);
+
+  size_t id_count = env->GetArrayLength(result);
+  for (size_t i = 0; i < id_count; ++i) {
+    ScopedLocalRef<jobjectArray> java_row(env,
+                                          reinterpret_cast<jobjectArray>(env->GetObjectArrayElement(result, i)));
+    ScopedLocalRef<jstring> java_zone_id(env,
+                                         reinterpret_cast<jstring>(env->GetObjectArrayElement(java_row.get(), 0)));
+    ScopedJavaUnicodeString zone_id(env, java_zone_id.get());
+    if (!zone_id.valid()) {
+      return;
+    }
+
+    UnicodeString long_std;
+    names->getDisplayName(zone_id.unicodeString(), UTZNM_LONG_STANDARD, now, long_std);
+    UnicodeString short_std;
+    names->getDisplayName(zone_id.unicodeString(), UTZNM_SHORT_STANDARD, now, short_std);
+    UnicodeString long_dst;
+    names->getDisplayName(zone_id.unicodeString(), UTZNM_LONG_DAYLIGHT, now, long_dst);
+    UnicodeString short_dst;
+    names->getDisplayName(zone_id.unicodeString(), UTZNM_SHORT_DAYLIGHT, now, short_dst);
+
+    if (isUtc(zone_id.unicodeString())) {
+      // ICU doesn't have names for the UTC zones; it just says "GMT+00:00" for both
+      // long and short names. We don't want this. The best we can do is use "UTC"
+      // for everything (since we don't know how to say "Universal Coordinated Time" in
+      // every language).
+      // TODO: check CLDR doesn't actually have this somewhere.
+      long_std = short_std = long_dst = short_dst = kUtc;
+    } else if (zone_id.unicodeString() == pacific_apia) {
+      // icu4c 50 doesn't know Samoa has DST yet. http://b/7955614
+      if (long_dst.isBogus()) {
+        long_dst = "Samoa Daylight Time";
+      }
+    }
+
+    setStringArrayElement(env, java_row.get(), 1, long_std);
+    setStringArrayElement(env, java_row.get(), 2, short_std);
+    setStringArrayElement(env, java_row.get(), 3, long_dst);
+    setStringArrayElement(env, java_row.get(), 4, short_dst);
+  }
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(TimeZoneNames, fillZoneStrings, "(Ljava/lang/String;[[Ljava/lang/String;)V"),
+};
+void register_libcore_icu_TimeZoneNames(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "libcore/icu/TimeZoneNames", gMethods, NELEM(gMethods));
+}
diff --git a/luni/src/main/native/libcore_icu_TimeZones.cpp b/luni/src/main/native/libcore_icu_TimeZones.cpp
deleted file mode 100644
index 15c18c5..0000000
--- a/luni/src/main/native/libcore_icu_TimeZones.cpp
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "TimeZones"
-
-#include <map>
-#include <vector>
-
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "JniException.h"
-#include "ScopedJavaUnicodeString.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
-#include "UniquePtr.h"
-#include "unicode/smpdtfmt.h"
-#include "unicode/timezone.h"
-
-extern Locale getLocale(JNIEnv* env, jstring localeName);
-
-static jobjectArray TimeZones_forCountryCode(JNIEnv* env, jclass, jstring countryCode) {
-    ScopedUtfChars countryChars(env, countryCode);
-    if (countryChars.c_str() == NULL) {
-        return NULL;
-    }
-
-    UniquePtr<StringEnumeration> ids(TimeZone::createEnumeration(countryChars.c_str()));
-    if (ids.get() == NULL) {
-        return NULL;
-    }
-    UErrorCode status = U_ZERO_ERROR;
-    int32_t idCount = ids->count(status);
-    if (maybeThrowIcuException(env, "StringEnumeration::count", status)) {
-        return NULL;
-    }
-
-    jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringClass, NULL);
-    for (int32_t i = 0; i < idCount; ++i) {
-        const UnicodeString* id = ids->snext(status);
-        if (maybeThrowIcuException(env, "StringEnumeration::snext", status)) {
-            return NULL;
-        }
-        ScopedLocalRef<jstring> idString(env, env->NewString(id->getBuffer(), id->length()));
-        env->SetObjectArrayElement(result, i, idString.get());
-    }
-    return result;
-}
-
-struct TimeZoneNames {
-    TimeZone* tz;
-
-    UnicodeString longStd;
-    UnicodeString shortStd;
-    UnicodeString longDst;
-    UnicodeString shortDst;
-
-    UDate standardDate;
-    UDate daylightSavingDate;
-};
-
-static void setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const UnicodeString& s) {
-    ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length()));
-    env->SetObjectArrayElement(array, i, javaString.get());
-}
-
-static bool isUtc(const UnicodeString& id) {
-    static UnicodeString etcUct("Etc/UCT", 7, US_INV);
-    static UnicodeString etcUtc("Etc/UTC", 7, US_INV);
-    static UnicodeString etcUniversal("Etc/Universal", 13, US_INV);
-    static UnicodeString etcZulu("Etc/Zulu", 8, US_INV);
-
-    static UnicodeString uct("UCT", 3, US_INV);
-    static UnicodeString utc("UTC", 3, US_INV);
-    static UnicodeString universal("Universal", 9, US_INV);
-    static UnicodeString zulu("Zulu", 4, US_INV);
-
-    return id == etcUct || id == etcUtc || id == etcUniversal || id == etcZulu ||
-            id == uct || id == utc || id == universal || id == zulu;
-}
-
-static jobjectArray TimeZones_getZoneStringsImpl(JNIEnv* env, jclass, jstring localeName, jobjectArray timeZoneIds) {
-    Locale locale = getLocale(env, localeName);
-
-    // We could use TimeZone::getDisplayName, but that's even slower
-    // because it creates a new SimpleDateFormat each time.
-    // We're better off using SimpleDateFormat directly.
-
-    // We can't use DateFormatSymbols::getZoneStrings because that
-    // uses its own set of time zone ids and contains empty strings
-    // instead of GMT offsets (a pity, because it's a bit faster than this code).
-
-    UErrorCode status = U_ZERO_ERROR;
-    UnicodeString longPattern("zzzz", 4, US_INV);
-    SimpleDateFormat longFormat(longPattern, locale, status);
-    // 'z' only uses "common" abbreviations. 'V' allows all known abbreviations.
-    // For example, "PST" is in common use in en_US, but "CET" isn't.
-    UnicodeString commonShortPattern("z", 1, US_INV);
-    SimpleDateFormat shortFormat(commonShortPattern, locale, status);
-    UnicodeString allShortPattern("V", 1, US_INV);
-    SimpleDateFormat allShortFormat(allShortPattern, locale, status);
-
-    UnicodeString utc("UTC", 3, US_INV);
-
-    // TODO: use of fixed dates prevents us from using the correct historical name when formatting dates.
-    // TODO: use of dates not in the current year could cause us to output obsoleted names.
-    // 15th January 2008
-    UDate date1 = 1203105600000.0;
-    // 15th July 2008
-    UDate date2 = 1218826800000.0;
-
-    // In the first pass, we get the long names for the time zone.
-    // We also get any commonly-used abbreviations.
-    std::vector<TimeZoneNames> table;
-    typedef std::map<UnicodeString, UnicodeString*> AbbreviationMap;
-    AbbreviationMap usedAbbreviations;
-    size_t idCount = env->GetArrayLength(timeZoneIds);
-    for (size_t i = 0; i < idCount; ++i) {
-        ScopedLocalRef<jstring> javaZoneId(env,
-                reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
-        ScopedJavaUnicodeString zoneId(env, javaZoneId.get());
-        UnicodeString id(zoneId.unicodeString());
-
-        TimeZoneNames row;
-        if (isUtc(id)) {
-            // ICU doesn't have names for the UTC zones; it just says "GMT+00:00" for both
-            // long and short names. We don't want this. The best we can do is use "UTC"
-            // for everything (since we don't know how to say "Universal Coordinated Time").
-            row.tz = NULL;
-            row.longStd = row.shortStd = row.longDst = row.shortDst = utc;
-            table.push_back(row);
-            usedAbbreviations[utc] = &utc;
-            continue;
-        }
-
-        row.tz = TimeZone::createTimeZone(id);
-        longFormat.setTimeZone(*row.tz);
-        shortFormat.setTimeZone(*row.tz);
-
-        int32_t daylightOffset;
-        int32_t rawOffset;
-        row.tz->getOffset(date1, false, rawOffset, daylightOffset, status);
-        if (daylightOffset != 0) {
-            // The TimeZone is reporting that we are in daylight time for the winter date.
-            // The dates are for the wrong hemisphere, so swap them.
-            row.standardDate = date2;
-            row.daylightSavingDate = date1;
-        } else {
-            row.standardDate = date1;
-            row.daylightSavingDate = date2;
-        }
-
-        longFormat.format(row.standardDate, row.longStd);
-        shortFormat.format(row.standardDate, row.shortStd);
-        if (row.tz->useDaylightTime()) {
-            longFormat.format(row.daylightSavingDate, row.longDst);
-            shortFormat.format(row.daylightSavingDate, row.shortDst);
-        } else {
-            row.longDst = row.longStd;
-            row.shortDst = row.shortStd;
-        }
-
-        table.push_back(row);
-        usedAbbreviations[row.shortStd] = &row.longStd;
-        usedAbbreviations[row.shortDst] = &row.longDst;
-    }
-
-    // In the second pass, we create the Java String[][].
-    // We also look for any uncommon abbreviations that don't conflict with ones we've already seen.
-    jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringArrayClass, NULL);
-    UnicodeString gmt("GMT", 3, US_INV);
-    for (size_t i = 0; i < table.size(); ++i) {
-        TimeZoneNames& row(table[i]);
-        // Did we get a GMT offset instead of an abbreviation?
-        if (row.shortStd.length() > 3 && row.shortStd.startsWith(gmt)) {
-            // See if we can do better...
-            UnicodeString uncommonStd, uncommonDst;
-            allShortFormat.setTimeZone(*row.tz);
-            allShortFormat.format(row.standardDate, uncommonStd);
-            if (row.tz->useDaylightTime()) {
-                allShortFormat.format(row.daylightSavingDate, uncommonDst);
-            } else {
-                uncommonDst = uncommonStd;
-            }
-
-            // If this abbreviation isn't already in use, we can use it.
-            AbbreviationMap::iterator it = usedAbbreviations.find(uncommonStd);
-            if (it == usedAbbreviations.end() || *(it->second) == row.longStd) {
-                row.shortStd = uncommonStd;
-                usedAbbreviations[row.shortStd] = &row.longStd;
-            }
-            it = usedAbbreviations.find(uncommonDst);
-            if (it == usedAbbreviations.end() || *(it->second) == row.longDst) {
-                row.shortDst = uncommonDst;
-                usedAbbreviations[row.shortDst] = &row.longDst;
-            }
-        }
-        // Fill in whatever we got.
-        ScopedLocalRef<jobjectArray> javaRow(env, env->NewObjectArray(5, JniConstants::stringClass, NULL));
-        ScopedLocalRef<jstring> id(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
-        env->SetObjectArrayElement(javaRow.get(), 0, id.get());
-        setStringArrayElement(env, javaRow.get(), 1, row.longStd);
-        setStringArrayElement(env, javaRow.get(), 2, row.shortStd);
-        setStringArrayElement(env, javaRow.get(), 3, row.longDst);
-        setStringArrayElement(env, javaRow.get(), 4, row.shortDst);
-        env->SetObjectArrayElement(result, i, javaRow.get());
-        delete row.tz;
-    }
-
-    return result;
-}
-
-static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(TimeZones, forCountryCode, "(Ljava/lang/String;)[Ljava/lang/String;"),
-    NATIVE_METHOD(TimeZones, getZoneStringsImpl, "(Ljava/lang/String;[Ljava/lang/String;)[[Ljava/lang/String;"),
-};
-void register_libcore_icu_TimeZones(JNIEnv* env) {
-    jniRegisterNativeMethods(env, "libcore/icu/TimeZones", gMethods, NELEM(gMethods));
-}
diff --git a/luni/src/main/native/libcore_icu_Transliterator.cpp b/luni/src/main/native/libcore_icu_Transliterator.cpp
new file mode 100644
index 0000000..282e642
--- /dev/null
+++ b/luni/src/main/native/libcore_icu_Transliterator.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Transliterator"
+
+#include "IcuUtilities.h"
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "JniException.h"
+#include "ScopedJavaUnicodeString.h"
+#include "ScopedStringChars.h"
+#include "unicode/translit.h"
+
+static Transliterator* fromPeer(jlong peer) {
+  return reinterpret_cast<Transliterator*>(static_cast<uintptr_t>(peer));
+}
+
+static jlong Transliterator_create(JNIEnv* env, jclass, jstring javaId) {
+  ScopedJavaUnicodeString id(env, javaId);
+  if (!id.valid()) {
+    return 0;
+  }
+  UErrorCode status = U_ZERO_ERROR;
+  Transliterator* t = Transliterator::createInstance(id.unicodeString(), UTRANS_FORWARD, status);
+  if (maybeThrowIcuException(env, "Transliterator::createInstance", status)) {
+    return 0;
+  }
+  return reinterpret_cast<uintptr_t>(t);
+}
+
+static void Transliterator_destroy(JNIEnv*, jclass, jlong peer) {
+  delete fromPeer(peer);
+}
+
+static jobjectArray Transliterator_getAvailableIDs(JNIEnv* env, jclass) {
+  UErrorCode status = U_ZERO_ERROR;
+  return fromStringEnumeration(env, Transliterator::getAvailableIDs(status));
+}
+
+static jstring Transliterator_transliterate(JNIEnv* env, jclass, jlong peer, jstring javaString) {
+  Transliterator* t = fromPeer(peer);
+  ScopedJavaUnicodeString string(env, javaString);
+  if (!string.valid()) {
+    return NULL;
+  }
+
+  UnicodeString& s(string.unicodeString());
+  t->transliterate(s);
+  return env->NewString(s.getBuffer(), s.length());
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(Transliterator, create, "(Ljava/lang/String;)J"),
+  NATIVE_METHOD(Transliterator, destroy, "(J)V"),
+  NATIVE_METHOD(Transliterator, getAvailableIDs, "()[Ljava/lang/String;"),
+  NATIVE_METHOD(Transliterator, transliterate, "(JLjava/lang/String;)Ljava/lang/String;"),
+};
+void register_libcore_icu_Transliterator(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "libcore/icu/Transliterator", gMethods, NELEM(gMethods));
+}
diff --git a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
index 1b888be..4f50ce5 100644
--- a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
+++ b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp
@@ -29,7 +29,6 @@
 static JNINativeMethod gMethods[] = {
     NATIVE_METHOD(AsynchronousCloseMonitor, signalBlockedThreads, "(Ljava/io/FileDescriptor;)V"),
 };
-
 void register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env) {
     AsynchronousSocketCloseMonitor::init();
     jniRegisterNativeMethods(env, "libcore/io/AsynchronousCloseMonitor", gMethods, NELEM(gMethods));
diff --git a/luni/src/main/native/libcore_io_Memory.cpp b/luni/src/main/native/libcore_io_Memory.cpp
index fe5f12d..77aef5b 100644
--- a/luni/src/main/native/libcore_io_Memory.cpp
+++ b/luni/src/main/native/libcore_io_Memory.cpp
@@ -18,11 +18,11 @@
 
 #include "JNIHelp.h"
 #include "JniConstants.h"
+#include "Portability.h"
 #include "ScopedBytes.h"
 #include "ScopedPrimitiveArray.h"
 #include "UniquePtr.h"
 
-#include <byteswap.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
@@ -38,7 +38,7 @@
 #define LONG_ALIGNMENT_MASK 0x7
 #define INT_ALIGNMENT_MASK 0x3
 #define SHORT_ALIGNMENT_MASK 0x1
-#elif defined(__i386__)
+#elif defined(__i386__) || defined(__x86_64__)
 // x86 can load anything at any alignment.
 #define LONG_ALIGNMENT_MASK 0x0
 #define INT_ALIGNMENT_MASK 0x0
@@ -62,7 +62,7 @@
     p->v = v;
 }
 
-template <typename T> static T cast(jint address) {
+template <typename T> static T cast(jlong address) {
     return reinterpret_cast<T>(static_cast<uintptr_t>(address));
 }
 
@@ -154,11 +154,11 @@
     memmove(dstBytes.get() + dstOffset, srcBytes.get() + srcOffset, length);
 }
 
-static jbyte Memory_peekByte(JNIEnv*, jclass, jint srcAddress) {
+static jbyte Memory_peekByte(JNIEnv*, jclass, jlong srcAddress) {
     return *cast<const jbyte*>(srcAddress);
 }
 
-static void Memory_peekByteArray(JNIEnv* env, jclass, jint srcAddress, jbyteArray dst, jint dstOffset, jint byteCount) {
+static void Memory_peekByteArray(JNIEnv* env, jclass, jlong srcAddress, jbyteArray dst, jint dstOffset, jint byteCount) {
     env->SetByteArrayRegion(dst, dstOffset, byteCount, cast<const jbyte*>(srcAddress));
 }
 
@@ -183,35 +183,35 @@
     } \
 }
 
-static void Memory_peekCharArray(JNIEnv* env, jclass, jint srcAddress, jcharArray dst, jint dstOffset, jint count, jboolean swap) {
+static void Memory_peekCharArray(JNIEnv* env, jclass, jlong srcAddress, jcharArray dst, jint dstOffset, jint count, jboolean swap) {
     PEEKER(jchar, Char, jshort, swapShorts);
 }
 
-static void Memory_peekDoubleArray(JNIEnv* env, jclass, jint srcAddress, jdoubleArray dst, jint dstOffset, jint count, jboolean swap) {
+static void Memory_peekDoubleArray(JNIEnv* env, jclass, jlong srcAddress, jdoubleArray dst, jint dstOffset, jint count, jboolean swap) {
     PEEKER(jdouble, Double, jlong, swapLongs);
 }
 
-static void Memory_peekFloatArray(JNIEnv* env, jclass, jint srcAddress, jfloatArray dst, jint dstOffset, jint count, jboolean swap) {
+static void Memory_peekFloatArray(JNIEnv* env, jclass, jlong srcAddress, jfloatArray dst, jint dstOffset, jint count, jboolean swap) {
     PEEKER(jfloat, Float, jint, swapInts);
 }
 
-static void Memory_peekIntArray(JNIEnv* env, jclass, jint srcAddress, jintArray dst, jint dstOffset, jint count, jboolean swap) {
+static void Memory_peekIntArray(JNIEnv* env, jclass, jlong srcAddress, jintArray dst, jint dstOffset, jint count, jboolean swap) {
     PEEKER(jint, Int, jint, swapInts);
 }
 
-static void Memory_peekLongArray(JNIEnv* env, jclass, jint srcAddress, jlongArray dst, jint dstOffset, jint count, jboolean swap) {
+static void Memory_peekLongArray(JNIEnv* env, jclass, jlong srcAddress, jlongArray dst, jint dstOffset, jint count, jboolean swap) {
     PEEKER(jlong, Long, jlong, swapLongs);
 }
 
-static void Memory_peekShortArray(JNIEnv* env, jclass, jint srcAddress, jshortArray dst, jint dstOffset, jint count, jboolean swap) {
+static void Memory_peekShortArray(JNIEnv* env, jclass, jlong srcAddress, jshortArray dst, jint dstOffset, jint count, jboolean swap) {
     PEEKER(jshort, Short, jshort, swapShorts);
 }
 
-static void Memory_pokeByte(JNIEnv*, jclass, jint dstAddress, jbyte value) {
+static void Memory_pokeByte(JNIEnv*, jclass, jlong dstAddress, jbyte value) {
     *cast<jbyte*>(dstAddress) = value;
 }
 
-static void Memory_pokeByteArray(JNIEnv* env, jclass, jint dstAddress, jbyteArray src, jint offset, jint length) {
+static void Memory_pokeByteArray(JNIEnv* env, jclass, jlong dstAddress, jbyteArray src, jint offset, jint length) {
     env->GetByteArrayRegion(src, offset, length, cast<jbyte*>(dstAddress));
 }
 
@@ -235,31 +235,31 @@
     } \
 }
 
-static void Memory_pokeCharArray(JNIEnv* env, jclass, jint dstAddress, jcharArray src, jint srcOffset, jint count, jboolean swap) {
+static void Memory_pokeCharArray(JNIEnv* env, jclass, jlong dstAddress, jcharArray src, jint srcOffset, jint count, jboolean swap) {
     POKER(jchar, Char, jshort, swapShorts);
 }
 
-static void Memory_pokeDoubleArray(JNIEnv* env, jclass, jint dstAddress, jdoubleArray src, jint srcOffset, jint count, jboolean swap) {
+static void Memory_pokeDoubleArray(JNIEnv* env, jclass, jlong dstAddress, jdoubleArray src, jint srcOffset, jint count, jboolean swap) {
     POKER(jdouble, Double, jlong, swapLongs);
 }
 
-static void Memory_pokeFloatArray(JNIEnv* env, jclass, jint dstAddress, jfloatArray src, jint srcOffset, jint count, jboolean swap) {
+static void Memory_pokeFloatArray(JNIEnv* env, jclass, jlong dstAddress, jfloatArray src, jint srcOffset, jint count, jboolean swap) {
     POKER(jfloat, Float, jint, swapInts);
 }
 
-static void Memory_pokeIntArray(JNIEnv* env, jclass, jint dstAddress, jintArray src, jint srcOffset, jint count, jboolean swap) {
+static void Memory_pokeIntArray(JNIEnv* env, jclass, jlong dstAddress, jintArray src, jint srcOffset, jint count, jboolean swap) {
     POKER(jint, Int, jint, swapInts);
 }
 
-static void Memory_pokeLongArray(JNIEnv* env, jclass, jint dstAddress, jlongArray src, jint srcOffset, jint count, jboolean swap) {
+static void Memory_pokeLongArray(JNIEnv* env, jclass, jlong dstAddress, jlongArray src, jint srcOffset, jint count, jboolean swap) {
     POKER(jlong, Long, jlong, swapLongs);
 }
 
-static void Memory_pokeShortArray(JNIEnv* env, jclass, jint dstAddress, jshortArray src, jint srcOffset, jint count, jboolean swap) {
+static void Memory_pokeShortArray(JNIEnv* env, jclass, jlong dstAddress, jshortArray src, jint srcOffset, jint count, jboolean swap) {
     POKER(jshort, Short, jshort, swapShorts);
 }
 
-static jshort Memory_peekShort(JNIEnv*, jclass, jint srcAddress, jboolean swap) {
+static jshort Memory_peekShort(JNIEnv*, jclass, jlong srcAddress, jboolean swap) {
     jshort result = *cast<const jshort*>(srcAddress);
     if (swap) {
         result = bswap_16(result);
@@ -267,14 +267,14 @@
     return result;
 }
 
-static void Memory_pokeShort(JNIEnv*, jclass, jint dstAddress, jshort value, jboolean swap) {
+static void Memory_pokeShort(JNIEnv*, jclass, jlong dstAddress, jshort value, jboolean swap) {
     if (swap) {
         value = bswap_16(value);
     }
     *cast<jshort*>(dstAddress) = value;
 }
 
-static jint Memory_peekInt(JNIEnv*, jclass, jint srcAddress, jboolean swap) {
+static jint Memory_peekInt(JNIEnv*, jclass, jlong srcAddress, jboolean swap) {
     jint result = *cast<const jint*>(srcAddress);
     if (swap) {
         result = bswap_32(result);
@@ -282,14 +282,14 @@
     return result;
 }
 
-static void Memory_pokeInt(JNIEnv*, jclass, jint dstAddress, jint value, jboolean swap) {
+static void Memory_pokeInt(JNIEnv*, jclass, jlong dstAddress, jint value, jboolean swap) {
     if (swap) {
         value = bswap_32(value);
     }
     *cast<jint*>(dstAddress) = value;
 }
 
-static jlong Memory_peekLong(JNIEnv*, jclass, jint srcAddress, jboolean swap) {
+static jlong Memory_peekLong(JNIEnv*, jclass, jlong srcAddress, jboolean swap) {
     jlong result;
     const jlong* src = cast<const jlong*>(srcAddress);
     if ((srcAddress & LONG_ALIGNMENT_MASK) == 0) {
@@ -303,7 +303,7 @@
     return result;
 }
 
-static void Memory_pokeLong(JNIEnv*, jclass, jint dstAddress, jlong value, jboolean swap) {
+static void Memory_pokeLong(JNIEnv*, jclass, jlong dstAddress, jlong value, jboolean swap) {
     jlong* dst = cast<jlong*>(dstAddress);
     if (swap) {
         value = bswap_64(value);
@@ -373,28 +373,28 @@
 
 static JNINativeMethod gMethods[] = {
     NATIVE_METHOD(Memory, memmove, "(Ljava/lang/Object;ILjava/lang/Object;IJ)V"),
-    NATIVE_METHOD(Memory, peekByte, "!(I)B"),
-    NATIVE_METHOD(Memory, peekByteArray, "(I[BII)V"),
-    NATIVE_METHOD(Memory, peekCharArray, "(I[CIIZ)V"),
-    NATIVE_METHOD(Memory, peekDoubleArray, "(I[DIIZ)V"),
-    NATIVE_METHOD(Memory, peekFloatArray, "(I[FIIZ)V"),
-    NATIVE_METHOD(Memory, peekInt, "!(IZ)I"),
-    NATIVE_METHOD(Memory, peekIntArray, "(I[IIIZ)V"),
-    NATIVE_METHOD(Memory, peekLong, "!(IZ)J"),
-    NATIVE_METHOD(Memory, peekLongArray, "(I[JIIZ)V"),
-    NATIVE_METHOD(Memory, peekShort, "!(IZ)S"),
-    NATIVE_METHOD(Memory, peekShortArray, "(I[SIIZ)V"),
-    NATIVE_METHOD(Memory, pokeByte, "!(IB)V"),
-    NATIVE_METHOD(Memory, pokeByteArray, "(I[BII)V"),
-    NATIVE_METHOD(Memory, pokeCharArray, "(I[CIIZ)V"),
-    NATIVE_METHOD(Memory, pokeDoubleArray, "(I[DIIZ)V"),
-    NATIVE_METHOD(Memory, pokeFloatArray, "(I[FIIZ)V"),
-    NATIVE_METHOD(Memory, pokeInt, "!(IIZ)V"),
-    NATIVE_METHOD(Memory, pokeIntArray, "(I[IIIZ)V"),
-    NATIVE_METHOD(Memory, pokeLong, "!(IJZ)V"),
-    NATIVE_METHOD(Memory, pokeLongArray, "(I[JIIZ)V"),
-    NATIVE_METHOD(Memory, pokeShort, "!(ISZ)V"),
-    NATIVE_METHOD(Memory, pokeShortArray, "(I[SIIZ)V"),
+    NATIVE_METHOD(Memory, peekByte, "!(J)B"),
+    NATIVE_METHOD(Memory, peekByteArray, "(J[BII)V"),
+    NATIVE_METHOD(Memory, peekCharArray, "(J[CIIZ)V"),
+    NATIVE_METHOD(Memory, peekDoubleArray, "(J[DIIZ)V"),
+    NATIVE_METHOD(Memory, peekFloatArray, "(J[FIIZ)V"),
+    NATIVE_METHOD(Memory, peekInt, "!(JZ)I"),
+    NATIVE_METHOD(Memory, peekIntArray, "(J[IIIZ)V"),
+    NATIVE_METHOD(Memory, peekLong, "!(JZ)J"),
+    NATIVE_METHOD(Memory, peekLongArray, "(J[JIIZ)V"),
+    NATIVE_METHOD(Memory, peekShort, "!(JZ)S"),
+    NATIVE_METHOD(Memory, peekShortArray, "(J[SIIZ)V"),
+    NATIVE_METHOD(Memory, pokeByte, "!(JB)V"),
+    NATIVE_METHOD(Memory, pokeByteArray, "(J[BII)V"),
+    NATIVE_METHOD(Memory, pokeCharArray, "(J[CIIZ)V"),
+    NATIVE_METHOD(Memory, pokeDoubleArray, "(J[DIIZ)V"),
+    NATIVE_METHOD(Memory, pokeFloatArray, "(J[FIIZ)V"),
+    NATIVE_METHOD(Memory, pokeInt, "!(JIZ)V"),
+    NATIVE_METHOD(Memory, pokeIntArray, "(J[IIIZ)V"),
+    NATIVE_METHOD(Memory, pokeLong, "!(JJZ)V"),
+    NATIVE_METHOD(Memory, pokeLongArray, "(J[JIIZ)V"),
+    NATIVE_METHOD(Memory, pokeShort, "!(JSZ)V"),
+    NATIVE_METHOD(Memory, pokeShortArray, "(J[SIIZ)V"),
     NATIVE_METHOD(Memory, unsafeBulkGet, "(Ljava/lang/Object;II[BIIZ)V"),
     NATIVE_METHOD(Memory, unsafeBulkPut, "([BIILjava/lang/Object;IIZ)V"),
 };
diff --git a/luni/src/main/native/libcore_io_OsConstants.cpp b/luni/src/main/native/libcore_io_OsConstants.cpp
index 4a719af..56a159f 100644
--- a/luni/src/main/native/libcore_io_OsConstants.cpp
+++ b/luni/src/main/native/libcore_io_OsConstants.cpp
@@ -21,7 +21,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <net/if.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
@@ -35,6 +34,8 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <net/if.h> // After <sys/socket.h> to work around a Mac header file bug.
+
 static void initConstant(JNIEnv* env, jclass c, const char* fieldName, int value) {
     jfieldID field = env->GetStaticFieldID(c, fieldName, "I");
     env->SetStaticIntField(c, field, value);
@@ -49,7 +50,9 @@
     initConstant(env, c, "AI_ALL", AI_ALL);
     initConstant(env, c, "AI_CANONNAME", AI_CANONNAME);
     initConstant(env, c, "AI_NUMERICHOST", AI_NUMERICHOST);
+#if defined(AI_NUMERICSERV)
     initConstant(env, c, "AI_NUMERICSERV", AI_NUMERICSERV);
+#endif
     initConstant(env, c, "AI_PASSIVE", AI_PASSIVE);
     initConstant(env, c, "AI_V4MAPPED", AI_V4MAPPED);
     initConstant(env, c, "E2BIG", E2BIG);
@@ -65,7 +68,9 @@
     initConstant(env, c, "EAI_MEMORY", EAI_MEMORY);
     initConstant(env, c, "EAI_NODATA", EAI_NODATA);
     initConstant(env, c, "EAI_NONAME", EAI_NONAME);
+#if defined(EAI_OVERFLOW)
     initConstant(env, c, "EAI_OVERFLOW", EAI_OVERFLOW);
+#endif
     initConstant(env, c, "EAI_SERVICE", EAI_SERVICE);
     initConstant(env, c, "EAI_SOCKTYPE", EAI_SOCKTYPE);
     initConstant(env, c, "EAI_SYSTEM", EAI_SYSTEM);
@@ -140,7 +145,9 @@
     initConstant(env, c, "ETIME", ETIME);
     initConstant(env, c, "ETIMEDOUT", ETIMEDOUT);
     initConstant(env, c, "ETXTBSY", ETXTBSY);
-    initConstant(env, c, "EWOULDBLOCK", EWOULDBLOCK);
+#if EWOULDBLOCK != EAGAIN
+#error EWOULDBLOCK != EAGAIN
+#endif
     initConstant(env, c, "EXDEV", EXDEV);
     initConstant(env, c, "EXIT_FAILURE", EXIT_FAILURE);
     initConstant(env, c, "EXIT_SUCCESS", EXIT_SUCCESS);
@@ -150,34 +157,50 @@
     initConstant(env, c, "F_GETFD", F_GETFD);
     initConstant(env, c, "F_GETFL", F_GETFL);
     initConstant(env, c, "F_GETLK", F_GETLK);
+#if defined(F_GETLK64)
     initConstant(env, c, "F_GETLK64", F_GETLK64);
+#endif
     initConstant(env, c, "F_GETOWN", F_GETOWN);
     initConstant(env, c, "F_OK", F_OK);
     initConstant(env, c, "F_RDLCK", F_RDLCK);
     initConstant(env, c, "F_SETFD", F_SETFD);
     initConstant(env, c, "F_SETFL", F_SETFL);
     initConstant(env, c, "F_SETLK", F_SETLK);
+#if defined(F_SETLK64)
     initConstant(env, c, "F_SETLK64", F_SETLK64);
+#endif
     initConstant(env, c, "F_SETLKW", F_SETLKW);
+#if defined(F_SETLKW64)
     initConstant(env, c, "F_SETLKW64", F_SETLKW64);
+#endif
     initConstant(env, c, "F_SETOWN", F_SETOWN);
     initConstant(env, c, "F_UNLCK", F_UNLCK);
     initConstant(env, c, "F_WRLCK", F_WRLCK);
     initConstant(env, c, "IFF_ALLMULTI", IFF_ALLMULTI);
+#if defined(IFF_AUTOMEDIA)
     initConstant(env, c, "IFF_AUTOMEDIA", IFF_AUTOMEDIA);
+#endif
     initConstant(env, c, "IFF_BROADCAST", IFF_BROADCAST);
     initConstant(env, c, "IFF_DEBUG", IFF_DEBUG);
+#if defined(IFF_DYNAMIC)
     initConstant(env, c, "IFF_DYNAMIC", IFF_DYNAMIC);
+#endif
     initConstant(env, c, "IFF_LOOPBACK", IFF_LOOPBACK);
+#if defined(IFF_MASTER)
     initConstant(env, c, "IFF_MASTER", IFF_MASTER);
+#endif
     initConstant(env, c, "IFF_MULTICAST", IFF_MULTICAST);
     initConstant(env, c, "IFF_NOARP", IFF_NOARP);
     initConstant(env, c, "IFF_NOTRAILERS", IFF_NOTRAILERS);
     initConstant(env, c, "IFF_POINTOPOINT", IFF_POINTOPOINT);
+#if defined(IFF_PORTSEL)
     initConstant(env, c, "IFF_PORTSEL", IFF_PORTSEL);
+#endif
     initConstant(env, c, "IFF_PROMISC", IFF_PROMISC);
     initConstant(env, c, "IFF_RUNNING", IFF_RUNNING);
+#if defined(IFF_SLAVE)
     initConstant(env, c, "IFF_SLAVE", IFF_SLAVE);
+#endif
     initConstant(env, c, "IFF_UP", IFF_UP);
     initConstant(env, c, "IPPROTO_ICMP", IPPROTO_ICMP);
     initConstant(env, c, "IPPROTO_IP", IPPROTO_IP);
@@ -189,13 +212,27 @@
     initConstant(env, c, "IPV6_MULTICAST_HOPS", IPV6_MULTICAST_HOPS);
     initConstant(env, c, "IPV6_MULTICAST_IF", IPV6_MULTICAST_IF);
     initConstant(env, c, "IPV6_MULTICAST_LOOP", IPV6_MULTICAST_LOOP);
+#if defined(IPV6_RECVDSTOPTS)
     initConstant(env, c, "IPV6_RECVDSTOPTS", IPV6_RECVDSTOPTS);
+#endif
+#if defined(IPV6_RECVHOPLIMIT)
     initConstant(env, c, "IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT);
+#endif
+#if defined(IPV6_RECVHOPOPTS)
     initConstant(env, c, "IPV6_RECVHOPOPTS", IPV6_RECVHOPOPTS);
+#endif
+#if defined(IPV6_RECVPKTINFO)
     initConstant(env, c, "IPV6_RECVPKTINFO", IPV6_RECVPKTINFO);
+#endif
+#if defined(IPV6_RECVRTHDR)
     initConstant(env, c, "IPV6_RECVRTHDR", IPV6_RECVRTHDR);
+#endif
+#if defined(IPV6_RECVTCLASS)
     initConstant(env, c, "IPV6_RECVTCLASS", IPV6_RECVTCLASS);
+#endif
+#if defined(IPV6_TCLASS)
     initConstant(env, c, "IPV6_TCLASS", IPV6_TCLASS);
+#endif
     initConstant(env, c, "IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS);
     initConstant(env, c, "IPV6_V6ONLY", IPV6_V6ONLY);
     initConstant(env, c, "IP_MULTICAST_IF", IP_MULTICAST_IF);
@@ -206,8 +243,12 @@
     initConstant(env, c, "MAP_FIXED", MAP_FIXED);
     initConstant(env, c, "MAP_PRIVATE", MAP_PRIVATE);
     initConstant(env, c, "MAP_SHARED", MAP_SHARED);
+#if defined(MCAST_JOIN_GROUP)
     initConstant(env, c, "MCAST_JOIN_GROUP", MCAST_JOIN_GROUP);
+#endif
+#if defined(MCAST_LEAVE_GROUP)
     initConstant(env, c, "MCAST_LEAVE_GROUP", MCAST_LEAVE_GROUP);
+#endif
     initConstant(env, c, "MCL_CURRENT", MCL_CURRENT);
     initConstant(env, c, "MCL_FUTURE", MCL_FUTURE);
     initConstant(env, c, "MSG_CTRUNC", MSG_CTRUNC);
@@ -271,10 +312,16 @@
     initConstant(env, c, "SIGKILL", SIGKILL);
     initConstant(env, c, "SIGPIPE", SIGPIPE);
     initConstant(env, c, "SIGPROF", SIGPROF);
+#if defined(SIGPWR)
     initConstant(env, c, "SIGPWR", SIGPWR);
+#endif
     initConstant(env, c, "SIGQUIT", SIGQUIT);
+#if defined(SIGRTMAX)
     initConstant(env, c, "SIGRTMAX", SIGRTMAX);
+#endif
+#if defined(SIGRTMIN)
     initConstant(env, c, "SIGRTMIN", SIGRTMIN);
+#endif
     initConstant(env, c, "SIGSEGV", SIGSEGV);
 #if defined(SIGSTKFLT)
     initConstant(env, c, "SIGSTKFLT", SIGSTKFLT);
@@ -302,7 +349,9 @@
     initConstant(env, c, "SOCK_SEQPACKET", SOCK_SEQPACKET);
     initConstant(env, c, "SOCK_STREAM", SOCK_STREAM);
     initConstant(env, c, "SOL_SOCKET", SOL_SOCKET);
+#if defined(SO_BINDTODEVICE)
     initConstant(env, c, "SO_BINDTODEVICE", SO_BINDTODEVICE);
+#endif
     initConstant(env, c, "SO_BROADCAST", SO_BROADCAST);
     initConstant(env, c, "SO_DEBUG", SO_DEBUG);
     initConstant(env, c, "SO_DONTROUTE", SO_DONTROUTE);
@@ -310,6 +359,8 @@
     initConstant(env, c, "SO_KEEPALIVE", SO_KEEPALIVE);
     initConstant(env, c, "SO_LINGER", SO_LINGER);
     initConstant(env, c, "SO_OOBINLINE", SO_OOBINLINE);
+    initConstant(env, c, "SO_PASSCRED", SO_PASSCRED);
+    initConstant(env, c, "SO_PEERCRED", SO_PEERCRED);
     initConstant(env, c, "SO_RCVBUF", SO_RCVBUF);
     initConstant(env, c, "SO_RCVLOWAT", SO_RCVLOWAT);
     initConstant(env, c, "SO_RCVTIMEO", SO_RCVTIMEO);
@@ -356,7 +407,9 @@
     initConstant(env, c, "_SC_2_CHAR_TERM", _SC_2_CHAR_TERM);
     initConstant(env, c, "_SC_2_C_BIND", _SC_2_C_BIND);
     initConstant(env, c, "_SC_2_C_DEV", _SC_2_C_DEV);
+#if defined(_SC_2_C_VERSION)
     initConstant(env, c, "_SC_2_C_VERSION", _SC_2_C_VERSION);
+#endif
     initConstant(env, c, "_SC_2_FORT_DEV", _SC_2_FORT_DEV);
     initConstant(env, c, "_SC_2_FORT_RUN", _SC_2_FORT_RUN);
     initConstant(env, c, "_SC_2_LOCALEDEF", _SC_2_LOCALEDEF);
@@ -369,7 +422,9 @@
     initConstant(env, c, "_SC_ARG_MAX", _SC_ARG_MAX);
     initConstant(env, c, "_SC_ASYNCHRONOUS_IO", _SC_ASYNCHRONOUS_IO);
     initConstant(env, c, "_SC_ATEXIT_MAX", _SC_ATEXIT_MAX);
+#if defined(_SC_AVPHYS_PAGES)
     initConstant(env, c, "_SC_AVPHYS_PAGES", _SC_AVPHYS_PAGES);
+#endif
     initConstant(env, c, "_SC_BC_BASE_MAX", _SC_BC_BASE_MAX);
     initConstant(env, c, "_SC_BC_DIM_MAX", _SC_BC_DIM_MAX);
     initConstant(env, c, "_SC_BC_SCALE_MAX", _SC_BC_SCALE_MAX);
@@ -400,7 +455,9 @@
     initConstant(env, c, "_SC_PAGESIZE", _SC_PAGESIZE);
     initConstant(env, c, "_SC_PAGE_SIZE", _SC_PAGE_SIZE);
     initConstant(env, c, "_SC_PASS_MAX", _SC_PASS_MAX);
+#if defined(_SC_PHYS_PAGES)
     initConstant(env, c, "_SC_PHYS_PAGES", _SC_PHYS_PAGES);
+#endif
     initConstant(env, c, "_SC_PRIORITIZED_IO", _SC_PRIORITIZED_IO);
     initConstant(env, c, "_SC_PRIORITY_SCHEDULING", _SC_PRIORITY_SCHEDULING);
     initConstant(env, c, "_SC_REALTIME_SIGNALS", _SC_REALTIME_SIGNALS);
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index acb3dc2..1daa83d 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -21,6 +21,7 @@
 #include "JniConstants.h"
 #include "JniException.h"
 #include "NetworkUtilities.h"
+#include "Portability.h"
 #include "ScopedBytes.h"
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
@@ -42,7 +43,6 @@
 #include <stdlib.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
-#include <sys/sendfile.h>
 #include <sys/socket.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
@@ -50,7 +50,6 @@
 #include <sys/types.h>
 #include <sys/uio.h>
 #include <sys/utsname.h>
-#include <sys/vfs.h> // Bionic doesn't have <sys/statvfs.h>
 #include <sys/wait.h>
 #include <termios.h>
 #include <unistd.h>
@@ -129,7 +128,12 @@
 }
 
 static void throwGaiException(JNIEnv* env, const char* functionName, int error) {
-    if (errno != 0) {
+  // Cache the methods ids before we throw, so we don't call GetMethodID with a pending exception.
+  static jmethodID ctor3 = env->GetMethodID(JniConstants::gaiExceptionClass, "<init>",
+                                            "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+  static jmethodID ctor2 = env->GetMethodID(JniConstants::gaiExceptionClass, "<init>",
+                                            "(Ljava/lang/String;I)V");
+  if (errno != 0) {
         // EAI_SYSTEM should mean "look at errno instead", but both glibc and bionic seem to
         // mess this up. In particular, if you don't have INTERNET permission, errno will be EACCES
         // but you'll get EAI_NONAME or EAI_NODATA. So we want our GaiException to have a
@@ -138,10 +142,6 @@
         throwErrnoException(env, functionName);
         // Deliberately fall through to throw another exception...
     }
-    static jmethodID ctor3 = env->GetMethodID(JniConstants::gaiExceptionClass,
-            "<init>", "(Ljava/lang/String;ILjava/lang/Throwable;)V");
-    static jmethodID ctor2 = env->GetMethodID(JniConstants::gaiExceptionClass,
-            "<init>", "(Ljava/lang/String;I)V");
     throwException(env, JniConstants::gaiExceptionClass, ctor3, ctor2, functionName, error);
 }
 
@@ -211,8 +211,7 @@
     std::vector<ScopedT*> mScopedBuffers;
 };
 
-static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage* ss) {
-    // TODO: support AF_UNIX and AF_UNSPEC (and other families?)
+static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage& ss) {
     jint port;
     jobject inetAddress = sockaddrToInetAddress(env, ss, &port);
     if (inetAddress == NULL) {
@@ -247,16 +246,24 @@
 }
 
 static jobject makeStructStatFs(JNIEnv* env, const struct statfs& sb) {
+#if defined(__APPLE__)
+    // Mac OS has no f_namelen field in struct statfs.
+    jlong max_name_length = 255; // __DARWIN_MAXNAMLEN
+#else
+    // Until Mac OS 10.7, these were 32-bit fields.
     STATIC_ASSERT(sizeof(sb.f_bavail) == sizeof(jlong), statfs_not_64_bit);
     STATIC_ASSERT(sizeof(sb.f_bfree) == sizeof(jlong), statfs_not_64_bit);
     STATIC_ASSERT(sizeof(sb.f_blocks) == sizeof(jlong), statfs_not_64_bit);
 
+    jlong max_name_length = static_cast<jlong>(sb.f_namelen);
+#endif
+
     static jmethodID ctor = env->GetMethodID(JniConstants::structStatFsClass, "<init>",
             "(JJJJJJJJ)V");
     return env->NewObject(JniConstants::structStatFsClass, ctor, static_cast<jlong>(sb.f_bsize),
             static_cast<jlong>(sb.f_blocks), static_cast<jlong>(sb.f_bfree),
             static_cast<jlong>(sb.f_bavail), static_cast<jlong>(sb.f_files),
-            static_cast<jlong>(sb.f_ffree), static_cast<jlong>(sb.f_namelen),
+            static_cast<jlong>(sb.f_ffree), max_name_length,
             static_cast<jlong>(sb.f_frsize));
 }
 
@@ -271,6 +278,11 @@
             static_cast<jlong>(tv.tv_sec), static_cast<jlong>(tv.tv_usec));
 }
 
+static jobject makeStructUcred(JNIEnv* env, const struct ucred& u) {
+  static jmethodID ctor = env->GetMethodID(JniConstants::structUcredClass, "<init>", "(III)V");
+  return env->NewObject(JniConstants::structUcredClass, ctor, u.pid, u.uid, u.gid);
+}
+
 static jobject makeStructUtsname(JNIEnv* env, const struct utsname& buf) {
     TO_JAVA_STRING(sysname, buf.sysname);
     TO_JAVA_STRING(nodename, buf.nodename);
@@ -294,7 +306,7 @@
     return true;
 }
 
-static bool fillInetSocketAddress(JNIEnv* env, jint rc, jobject javaInetSocketAddress, const sockaddr_storage* ss) {
+static bool fillInetSocketAddress(JNIEnv* env, jint rc, jobject javaInetSocketAddress, const sockaddr_storage& ss) {
     if (rc == -1 || javaInetSocketAddress == NULL) {
         return true;
     }
@@ -326,6 +338,21 @@
     return makeStructStat(env, sb);
 }
 
+static jobject doGetSockName(JNIEnv* env, jobject javaFd, bool is_sockname) {
+  int fd = jniGetFDFromFileDescriptor(env, javaFd);
+  sockaddr_storage ss;
+  sockaddr* sa = reinterpret_cast<sockaddr*>(&ss);
+  socklen_t byteCount = sizeof(ss);
+  memset(&ss, 0, byteCount);
+  int rc = is_sockname ? TEMP_FAILURE_RETRY(getsockname(fd, sa, &byteCount))
+      : TEMP_FAILURE_RETRY(getpeername(fd, sa, &byteCount));
+  if (rc == -1) {
+    throwErrnoException(env, is_sockname ? "getsockname" : "getpeername");
+    return NULL;
+  }
+  return makeSocketAddress(env, ss);
+}
+
 class Passwd {
 public:
     Passwd(JNIEnv* env) : mEnv(env), mResult(NULL) {
@@ -374,7 +401,7 @@
     sockaddr* peer = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
     socklen_t* peerLength = (javaInetSocketAddress != NULL) ? &sl : 0;
     jint clientFd = NET_FAILURE_RETRY(env, int, accept, javaFd, peer, peerLength);
-    if (clientFd == -1 || !fillInetSocketAddress(env, clientFd, javaInetSocketAddress, &ss)) {
+    if (clientFd == -1 || !fillInetSocketAddress(env, clientFd, javaInetSocketAddress, ss)) {
         close(clientFd);
         return NULL;
     }
@@ -395,12 +422,13 @@
 
 static void Posix_bind(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) {
     sockaddr_storage ss;
-    if (!inetAddressToSockaddr(env, javaAddress, port, &ss)) {
+    socklen_t sa_len;
+    if (!inetAddressToSockaddr(env, javaAddress, port, ss, sa_len)) {
         return;
     }
     const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss);
     // We don't need the return value because we'll already have thrown.
-    (void) NET_FAILURE_RETRY(env, int, bind, javaFd, sa, sizeof(sockaddr_storage));
+    (void) NET_FAILURE_RETRY(env, int, bind, javaFd, sa, sa_len);
 }
 
 static void Posix_chmod(JNIEnv* env, jobject, jstring javaPath, jint mode) {
@@ -433,12 +461,13 @@
 
 static void Posix_connect(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) {
     sockaddr_storage ss;
-    if (!inetAddressToSockaddr(env, javaAddress, port, &ss)) {
+    socklen_t sa_len;
+    if (!inetAddressToSockaddr(env, javaAddress, port, ss, sa_len)) {
         return;
     }
     const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss);
     // We don't need the return value because we'll already have thrown.
-    (void) NET_FAILURE_RETRY(env, int, connect, javaFd, sa, sizeof(sockaddr_storage));
+    (void) NET_FAILURE_RETRY(env, int, connect, javaFd, sa, sa_len);
 }
 
 static jobject Posix_dup(JNIEnv* env, jobject, jobject javaOldFd) {
@@ -458,6 +487,34 @@
     return toStringArray(env, environ);
 }
 
+static void Posix_execve(JNIEnv* env, jobject, jstring javaFilename, jobjectArray javaArgv, jobjectArray javaEnvp) {
+    ScopedUtfChars path(env, javaFilename);
+    if (path.c_str() == NULL) {
+        return;
+    }
+
+    char** argv = convertStrings(env, javaArgv);
+    char** envp = convertStrings(env, javaEnvp);
+    execve(path.c_str(), argv, envp);
+
+    freeStrings(env, javaArgv, argv);
+    freeStrings(env, javaEnvp, envp);
+    throwErrnoException(env, "execve");
+}
+
+static void Posix_execv(JNIEnv* env, jobject, jstring javaFilename, jobjectArray javaArgv) {
+    ScopedUtfChars path(env, javaFilename);
+    if (path.c_str() == NULL) {
+        return;
+    }
+
+    char** argv = convertStrings(env, javaArgv);
+    execv(path.c_str(), argv);
+
+    freeStrings(env, javaArgv, argv);
+    throwErrnoException(env, "execv");
+}
+
 static void Posix_fchmod(JNIEnv* env, jobject, jobject javaFd, jint mode) {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
     throwIfMinusOne(env, "fchmod", TEMP_FAILURE_RETRY(fchmod(fd, mode)));
@@ -602,7 +659,7 @@
         }
 
         // Convert each IP address into a Java byte array.
-        sockaddr_storage* address = reinterpret_cast<sockaddr_storage*>(ai->ai_addr);
+        sockaddr_storage& address = *reinterpret_cast<sockaddr_storage*>(ai->ai_addr);
         ScopedLocalRef<jobject> inetAddress(env, sockaddrToInetAddress(env, address, NULL));
         if (inetAddress.get() == NULL) {
             return NULL;
@@ -635,17 +692,13 @@
 
 static jstring Posix_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint flags) {
     sockaddr_storage ss;
-    if (!inetAddressToSockaddrVerbatim(env, javaAddress, 0, &ss)) {
+    socklen_t sa_len;
+    if (!inetAddressToSockaddrVerbatim(env, javaAddress, 0, ss, sa_len)) {
         return NULL;
     }
-    // TODO: bionic's getnameinfo(3) seems to want its length parameter to be exactly
-    // sizeof(sockaddr_in) for an IPv4 address and sizeof (sockaddr_in6) for an
-    // IPv6 address. Fix getnameinfo so it accepts sizeof(sockaddr_storage), and
-    // then remove this hack.
-    socklen_t size = (ss.ss_family == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
     char buf[NI_MAXHOST]; // NI_MAXHOST is longer than INET6_ADDRSTRLEN.
     errno = 0;
-    int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), size, buf, sizeof(buf), NULL, 0, flags);
+    int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), sa_len, buf, sizeof(buf), NULL, 0, flags);
     if (rc != 0) {
         throwGaiException(env, "getnameinfo", rc);
         return NULL;
@@ -653,6 +706,10 @@
     return env->NewStringUTF(buf);
 }
 
+static jobject Posix_getpeername(JNIEnv* env, jobject, jobject javaFd) {
+  return doGetSockName(env, javaFd, false);
+}
+
 static jint Posix_getpid(JNIEnv*, jobject) {
     return getpid();
 }
@@ -674,17 +731,7 @@
 }
 
 static jobject Posix_getsockname(JNIEnv* env, jobject, jobject javaFd) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
-    sockaddr_storage ss;
-    sockaddr* sa = reinterpret_cast<sockaddr*>(&ss);
-    socklen_t byteCount = sizeof(ss);
-    memset(&ss, 0, byteCount);
-    int rc = TEMP_FAILURE_RETRY(getsockname(fd, sa, &byteCount));
-    if (rc == -1) {
-        throwErrnoException(env, "getsockname");
-        return NULL;
-    }
-    return makeSocketAddress(env, &ss);
+  return doGetSockName(env, javaFd, true);
 }
 
 static jint Posix_getsockoptByte(JNIEnv* env, jobject, jobject javaFd, jint level, jint option) {
@@ -707,7 +754,7 @@
         throwErrnoException(env, "getsockopt");
         return NULL;
     }
-    return sockaddrToInetAddress(env, &ss, NULL);
+    return sockaddrToInetAddress(env, ss, NULL);
 }
 
 static jint Posix_getsockoptInt(JNIEnv* env, jobject, jobject javaFd, jint level, jint option) {
@@ -744,6 +791,19 @@
     return makeStructTimeval(env, tv);
 }
 
+static jobject Posix_getsockoptUcred(JNIEnv* env, jobject, jobject javaFd, jint level, jint option) {
+  int fd = jniGetFDFromFileDescriptor(env, javaFd);
+  struct ucred u;
+  socklen_t size = sizeof(u);
+  memset(&u, 0, size);
+  int rc = TEMP_FAILURE_RETRY(getsockopt(fd, level, option, &u, &size));
+  if (rc == -1) {
+    throwErrnoException(env, "getsockopt");
+    return NULL;
+  }
+  return makeStructUcred(env, u);
+}
+
 static jint Posix_getuid(JNIEnv*, jobject) {
     return getuid();
 }
@@ -769,7 +829,7 @@
         return NULL;
     }
     ss.ss_family = family;
-    return sockaddrToInetAddress(env, &ss, NULL);
+    return sockaddrToInetAddress(env, ss, NULL);
 }
 
 static jobject Posix_ioctlInetAddress(JNIEnv* env, jobject, jobject javaFd, jint cmd, jstring javaInterfaceName) {
@@ -782,7 +842,7 @@
     if (rc == -1) {
         return NULL;
     }
-    return sockaddrToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr), NULL);
+    return sockaddrToInetAddress(env, reinterpret_cast<sockaddr_storage&>(req.ifr_addr), NULL);
 }
 
 static jint Posix_ioctlInt(JNIEnv* env, jobject, jobject javaFd, jint cmd, jobject javaArg) {
@@ -1003,7 +1063,7 @@
     sockaddr* from = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
     socklen_t* fromLength = (javaInetSocketAddress != NULL) ? &sl : 0;
     jint recvCount = NET_FAILURE_RETRY(env, ssize_t, recvfrom, javaFd, bytes.get() + byteOffset, byteCount, flags, from, fromLength);
-    fillInetSocketAddress(env, recvCount, javaInetSocketAddress, &ss);
+    fillInetSocketAddress(env, recvCount, javaInetSocketAddress, ss);
     return recvCount;
 }
 
@@ -1051,18 +1111,30 @@
         return -1;
     }
     sockaddr_storage ss;
-    if (javaInetAddress != NULL && !inetAddressToSockaddr(env, javaInetAddress, port, &ss)) {
+    socklen_t sa_len = 0;
+    if (javaInetAddress != NULL && !inetAddressToSockaddr(env, javaInetAddress, port, ss, sa_len)) {
         return -1;
     }
     const sockaddr* to = (javaInetAddress != NULL) ? reinterpret_cast<const sockaddr*>(&ss) : NULL;
-    socklen_t toLength = (javaInetAddress != NULL) ? sizeof(ss) : 0;
-    return NET_FAILURE_RETRY(env, ssize_t, sendto, javaFd, bytes.get() + byteOffset, byteCount, flags, to, toLength);
+    return NET_FAILURE_RETRY(env, ssize_t, sendto, javaFd, bytes.get() + byteOffset, byteCount, flags, to, sa_len);
 }
 
 static void Posix_setegid(JNIEnv* env, jobject, jint egid) {
     throwIfMinusOne(env, "setegid", TEMP_FAILURE_RETRY(setegid(egid)));
 }
 
+static void Posix_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaValue, jboolean overwrite) {
+    ScopedUtfChars name(env, javaName);
+    if (name.c_str() == NULL) {
+        return;
+    }
+    ScopedUtfChars value(env, javaValue);
+    if (value.c_str() == NULL) {
+        return;
+    }
+    throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite));
+}
+
 static void Posix_seteuid(JNIEnv* env, jobject, jint euid) {
     throwIfMinusOne(env, "seteuid", TEMP_FAILURE_RETRY(seteuid(euid)));
 }
@@ -1095,6 +1167,11 @@
     throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
 }
 
+#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED < 1070
+// Mac OS didn't support modern multicast APIs until 10.7.
+static void Posix_setsockoptIpMreqn(JNIEnv*, jobject, jobject, jint, jint, jint) { abort(); }
+static void Posix_setsockoptGroupReq(JNIEnv*, jobject, jobject, jint, jint, jobject) { abort(); }
+#else
 static void Posix_setsockoptIpMreqn(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
     ip_mreqn req;
     memset(&req, 0, sizeof(req));
@@ -1112,7 +1189,8 @@
     // Get the IPv4 or IPv6 multicast address to join or leave.
     static jfieldID grGroupFid = env->GetFieldID(JniConstants::structGroupReqClass, "gr_group", "Ljava/net/InetAddress;");
     ScopedLocalRef<jobject> javaGroup(env, env->GetObjectField(javaGroupReq, grGroupFid));
-    if (!inetAddressToSockaddrVerbatim(env, javaGroup.get(), 0, &req.gr_group)) {
+    socklen_t sa_len;
+    if (!inetAddressToSockaddrVerbatim(env, javaGroup.get(), 0, req.gr_group, sa_len)) {
         return;
     }
 
@@ -1133,6 +1211,7 @@
     }
     throwIfMinusOne(env, "setsockopt", rc);
 }
+#endif
 
 static void Posix_setsockoptLinger(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaLinger) {
     static jfieldID lOnoffFid = env->GetFieldID(JniConstants::structLingerClass, "l_onoff", "I");
@@ -1201,6 +1280,10 @@
     return env->NewStringUTF(message);
 }
 
+static jstring Posix_strsignal(JNIEnv* env, jobject, jint signal) {
+    return env->NewStringUTF(strsignal(signal));
+}
+
 static void Posix_symlink(JNIEnv* env, jobject, jstring javaOldPath, jstring javaNewPath) {
     ScopedUtfChars oldPath(env, javaOldPath);
     if (oldPath.c_str() == NULL) {
@@ -1228,7 +1311,12 @@
     throwIfMinusOne(env, "tcdrain", TEMP_FAILURE_RETRY(tcdrain(fd)));
 }
 
-static jint Posix_umask(JNIEnv*, jobject, jint mask) {
+static void Posix_tcsendbreak(JNIEnv* env, jobject, jobject javaFd, jint duration) {
+  int fd = jniGetFDFromFileDescriptor(env, javaFd);
+  throwIfMinusOne(env, "tcsendbreak", TEMP_FAILURE_RETRY(tcsendbreak(fd, duration)));
+}
+
+static jint Posix_umaskImpl(JNIEnv*, jobject, jint mask) {
     return umask(mask);
 }
 
@@ -1240,6 +1328,14 @@
     return makeStructUtsname(env, buf);
 }
 
+static void Posix_unsetenv(JNIEnv* env, jobject, jstring javaName) {
+    ScopedUtfChars name(env, javaName);
+    if (name.c_str() == NULL) {
+        return;
+    }
+    throwIfMinusOne(env, "unsetenv", unsetenv(name.c_str()));
+}
+
 static jint Posix_waitpid(JNIEnv* env, jobject, jint pid, jobject javaStatus, jint options) {
     int status;
     int rc = throwIfMinusOne(env, "waitpid", TEMP_FAILURE_RETRY(waitpid(pid, &status, options)));
@@ -1279,6 +1375,8 @@
     NATIVE_METHOD(Posix, dup, "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;"),
     NATIVE_METHOD(Posix, dup2, "(Ljava/io/FileDescriptor;I)Ljava/io/FileDescriptor;"),
     NATIVE_METHOD(Posix, environ, "()[Ljava/lang/String;"),
+    NATIVE_METHOD(Posix, execv, "(Ljava/lang/String;[Ljava/lang/String;)V"),
+    NATIVE_METHOD(Posix, execve, "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V"),
     NATIVE_METHOD(Posix, fchmod, "(Ljava/io/FileDescriptor;I)V"),
     NATIVE_METHOD(Posix, fchown, "(Ljava/io/FileDescriptor;II)V"),
     NATIVE_METHOD(Posix, fcntlVoid, "(Ljava/io/FileDescriptor;I)I"),
@@ -1296,6 +1394,7 @@
     NATIVE_METHOD(Posix, getgid, "()I"),
     NATIVE_METHOD(Posix, getenv, "(Ljava/lang/String;)Ljava/lang/String;"),
     NATIVE_METHOD(Posix, getnameinfo, "(Ljava/net/InetAddress;I)Ljava/lang/String;"),
+    NATIVE_METHOD(Posix, getpeername, "(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;"),
     NATIVE_METHOD(Posix, getpid, "()I"),
     NATIVE_METHOD(Posix, getppid, "()I"),
     NATIVE_METHOD(Posix, getpwnam, "(Ljava/lang/String;)Llibcore/io/StructPasswd;"),
@@ -1306,6 +1405,7 @@
     NATIVE_METHOD(Posix, getsockoptInt, "(Ljava/io/FileDescriptor;II)I"),
     NATIVE_METHOD(Posix, getsockoptLinger, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructLinger;"),
     NATIVE_METHOD(Posix, getsockoptTimeval, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructTimeval;"),
+    NATIVE_METHOD(Posix, getsockoptUcred, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructUcred;"),
     NATIVE_METHOD(Posix, getuid, "()I"),
     NATIVE_METHOD(Posix, if_indextoname, "(I)Ljava/lang/String;"),
     NATIVE_METHOD(Posix, inet_pton, "(ILjava/lang/String;)Ljava/net/InetAddress;"),
@@ -1337,6 +1437,7 @@
     NATIVE_METHOD(Posix, sendfile, "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Llibcore/util/MutableLong;J)J"),
     NATIVE_METHOD(Posix, sendtoBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/InetAddress;I)I"),
     NATIVE_METHOD(Posix, setegid, "(I)V"),
+    NATIVE_METHOD(Posix, setenv, "(Ljava/lang/String;Ljava/lang/String;Z)V"),
     NATIVE_METHOD(Posix, seteuid, "(I)V"),
     NATIVE_METHOD(Posix, setgid, "(I)V"),
     NATIVE_METHOD(Posix, setsid, "()I"),
@@ -1354,11 +1455,14 @@
     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;"),
+    NATIVE_METHOD(Posix, strsignal, "(I)Ljava/lang/String;"),
     NATIVE_METHOD(Posix, symlink, "(Ljava/lang/String;Ljava/lang/String;)V"),
     NATIVE_METHOD(Posix, sysconf, "(I)J"),
     NATIVE_METHOD(Posix, tcdrain, "(Ljava/io/FileDescriptor;)V"),
-    NATIVE_METHOD(Posix, umask, "(I)I"),
+    NATIVE_METHOD(Posix, tcsendbreak, "(Ljava/io/FileDescriptor;I)V"),
+    NATIVE_METHOD(Posix, umaskImpl, "(I)I"),
     NATIVE_METHOD(Posix, uname, "()Llibcore/io/StructUtsname;"),
+    NATIVE_METHOD(Posix, unsetenv, "(Ljava/lang/String;)V"),
     NATIVE_METHOD(Posix, waitpid, "(ILlibcore/util/MutableInt;I)I"),
     NATIVE_METHOD(Posix, writeBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;II)I"),
     NATIVE_METHOD(Posix, writev, "(Ljava/io/FileDescriptor;[Ljava/lang/Object;[I[I)I"),
diff --git a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
index ce41a51..9b4bb4d 100644
--- a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
+++ b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
@@ -77,6 +77,7 @@
             }
             memcpy(newArray, array, capacity * sizeof(jstring));
 
+            delete[] array;
             array = newArray;
             capacity = newCapacity;
         }
@@ -139,7 +140,7 @@
             if (javaBuffer == NULL) return NULL;
 
             // Create a global reference.
-            javaBuffer = (jcharArray) env->NewGlobalRef(javaBuffer);
+            javaBuffer = reinterpret_cast<jcharArray>(env->NewGlobalRef(javaBuffer));
             if (javaBuffer == NULL) return NULL;
 
             buffer = javaBuffer;
@@ -199,6 +200,14 @@
     return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser));
 }
 
+static XML_Parser toXMLParser(jlong address) {
+  return reinterpret_cast<XML_Parser>(address);
+}
+
+static jlong fromXMLParser(XML_Parser parser) {
+  return reinterpret_cast<uintptr_t>(parser);
+}
+
 static jmethodID commentMethod;
 static jmethodID endCdataMethod;
 static jmethodID endDtdMethod;
@@ -278,7 +287,7 @@
     }
 
     // Create a global reference to the interned string.
-    wrapper->interned = (jstring) env->NewGlobalRef(interned.get());
+    wrapper->interned = reinterpret_cast<jstring>(env->NewGlobalRef(interned.get()));
     if (env->ExceptionCheck()) {
         return NULL;
     }
@@ -417,7 +426,9 @@
 
     // Grow buffer if necessary (the length in bytes is always >= the length in chars).
     jcharArray javaChars = parsingContext->ensureCapacity(byteCount);
-    if (javaChars == NULL) return -1;
+    if (javaChars == NULL) {
+        return -1;
+    }
 
     // Decode UTF-8 characters into our char[].
     ScopedCharArrayRW chars(env, javaChars);
@@ -454,7 +465,7 @@
     env->CallVoidMethod(javaParser, method, buffer, utf16length);
 }
 
-static const char** toAttributes(jint attributePointer) {
+static const char** toAttributes(jlong attributePointer) {
     return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer));
 }
 
@@ -463,7 +474,7 @@
  */
 class ExpatElementName {
 public:
-    ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jint attributePointer, jint index) {
+    ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jlong attributePointer, jint index) {
         const char** attributes = toAttributes(attributePointer);
         const char* name = attributes[index * 2];
         init(env, parsingContext, name);
@@ -503,7 +514,7 @@
         }
 
         // return prefix + ":" + localName
-        LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1);
+        ::LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1);
         snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName);
         return internString(mEnv, mParsingContext, &qName[0]);
     }
@@ -831,19 +842,19 @@
  * @param javaContext that was provided to handleExternalEntity
  * @returns the pointer to the C Expat entity parser
  */
-static jint ExpatParser_createEntityParser(JNIEnv* env, jobject, jint parentParser, jstring javaContext) {
+static jlong ExpatParser_createEntityParser(JNIEnv* env, jobject, jlong parentParser, jstring javaContext) {
     ScopedUtfChars context(env, javaContext);
     if (context.c_str() == NULL) {
         return 0;
     }
 
-    XML_Parser parent = (XML_Parser) parentParser;
+    XML_Parser parent = toXMLParser(parentParser);
     XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
     if (entityParser == NULL) {
         jniThrowOutOfMemoryError(env, NULL);
     }
 
-    return (jint) entityParser;
+    return fromXMLParser(entityParser);
 }
 
 /**
@@ -944,7 +955,7 @@
  * @param processNamespaces true if the parser should handle namespaces
  * @returns the pointer to the C Expat parser
  */
-static jint ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
+static jlong ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
         jboolean processNamespaces) {
     // Allocate parsing context.
     UniquePtr<ParsingContext> context(new ParsingContext(object));
@@ -953,7 +964,7 @@
         return 0;
     }
 
-    context->processNamespaces = (bool) processNamespaces;
+    context->processNamespaces = processNamespaces;
 
     // Create a parser.
     XML_Parser parser;
@@ -989,7 +1000,7 @@
         return 0;
     }
 
-    return (jint) parser;
+    return fromXMLParser(parser);
 }
 
 /**
@@ -999,9 +1010,9 @@
  * can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars
  * and appendString all call through this method.
  */
-static void append(JNIEnv* env, jobject object, jint pointer,
+static void append(JNIEnv* env, jobject object, jlong pointer,
         const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) {
-    XML_Parser parser = (XML_Parser) pointer;
+    XML_Parser parser = toXMLParser(pointer);
     ParsingContext* context = toParsingContext(parser);
     context->env = env;
     context->object = object;
@@ -1012,7 +1023,7 @@
     context->env = NULL;
 }
 
-static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jint pointer,
+static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jlong pointer,
         jbyteArray xml, jint byteOffset, jint byteCount) {
     ScopedByteArrayRO byteArray(env, xml);
     if (byteArray.get() == NULL) {
@@ -1023,7 +1034,7 @@
     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
 }
 
-static void ExpatParser_appendChars(JNIEnv* env, jobject object, jint pointer,
+static void ExpatParser_appendChars(JNIEnv* env, jobject object, jlong pointer,
         jcharArray xml, jint charOffset, jint charCount) {
     ScopedCharArrayRO charArray(env, xml);
     if (charArray.get() == NULL) {
@@ -1036,7 +1047,7 @@
     append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
 }
 
-static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer, jstring javaXml, jboolean isFinal) {
+static void ExpatParser_appendString(JNIEnv* env, jobject object, jlong pointer, jstring javaXml, jboolean isFinal) {
     ScopedStringChars xml(env, javaXml);
     if (xml.get() == NULL) {
         return;
@@ -1048,101 +1059,69 @@
 
 /**
  * Releases parser only.
- *
- * @param object the Java ExpatParser instance
- * @param i pointer to the C expat parser
  */
-static void ExpatParser_releaseParser(JNIEnv*, jobject, jint i) {
-    XML_Parser parser = (XML_Parser) i;
-    XML_ParserFree(parser);
+static void ExpatParser_releaseParser(JNIEnv*, jobject, jlong address) {
+  XML_ParserFree(toXMLParser(address));
 }
 
 /**
  * Cleans up after the parser. Called at garbage collection time.
- *
- * @param object the Java ExpatParser instance
- * @param i pointer to the C expat parser
  */
-static void ExpatParser_release(JNIEnv* env, jobject, jint i) {
-    XML_Parser parser = (XML_Parser) i;
+static void ExpatParser_release(JNIEnv* env, jobject, jlong address) {
+  XML_Parser parser = toXMLParser(address);
 
-    ParsingContext* context = toParsingContext(parser);
-    context->env = env;
-    delete context;
+  ParsingContext* context = toParsingContext(parser);
+  context->env = env;
+  delete context;
 
-    XML_ParserFree(parser);
+  XML_ParserFree(parser);
 }
 
-/**
- * Gets the current line.
- *
- * @param object the Java ExpatParser instance
- * @param pointer to the C expat parser
- * @returns current line number
- */
-static int ExpatParser_line(JNIEnv*, jobject, jint pointer) {
-    XML_Parser parser = (XML_Parser) pointer;
-    return XML_GetCurrentLineNumber(parser);
+static int ExpatParser_line(JNIEnv*, jobject, jlong address) {
+  return XML_GetCurrentLineNumber(toXMLParser(address));
 }
 
-/**
- * Gets the current column.
- *
- * @param object the Java ExpatParser instance
- * @param pointer to the C expat parser
- * @returns current column number
- */
-static int ExpatParser_column(JNIEnv*, jobject, jint pointer) {
-    XML_Parser parser = (XML_Parser) pointer;
-    return XML_GetCurrentColumnNumber(parser);
+static int ExpatParser_column(JNIEnv*, jobject, jlong address) {
+  return XML_GetCurrentColumnNumber(toXMLParser(address));
 }
 
 /**
  * Gets the URI of the attribute at the given index.
  *
- * @param object Java ExpatParser instance
- * @param pointer to the C expat parser
  * @param attributePointer to the attribute array
  * @param index of the attribute
  * @returns interned Java string containing attribute's URI
  */
-static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jint pointer,
-        jint attributePointer, jint index) {
-    XML_Parser parser = (XML_Parser) pointer;
-    ParsingContext* context = toParsingContext(parser);
-    return ExpatElementName(env, context, attributePointer, index).uri();
+static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jlong address,
+        jlong attributePointer, jint index) {
+  ParsingContext* context = toParsingContext(toXMLParser(address));
+  return ExpatElementName(env, context, attributePointer, index).uri();
 }
 
 /**
  * Gets the local name of the attribute at the given index.
  *
- * @param object Java ExpatParser instance
- * @param pointer to the C expat parser
  * @param attributePointer to the attribute array
  * @param index of the attribute
  * @returns interned Java string containing attribute's local name
  */
-static jstring ExpatAttributes_getLocalName(JNIEnv* env, jobject, jint pointer,
-        jint attributePointer, jint index) {
-    XML_Parser parser = (XML_Parser) pointer;
-    ParsingContext* context = toParsingContext(parser);
-    return ExpatElementName(env, context, attributePointer, index).localName();
+static jstring ExpatAttributes_getLocalName(JNIEnv* env, jobject, jlong address,
+        jlong attributePointer, jint index) {
+  ParsingContext* context = toParsingContext(toXMLParser(address));
+  return ExpatElementName(env, context, attributePointer, index).localName();
 }
 
 /**
  * Gets the qualified name of the attribute at the given index.
  *
- * @param object Java ExpatParser instance
- * @param pointer to the C expat parser
  * @param attributePointer to the attribute array
  * @param index of the attribute
  * @returns interned Java string containing attribute's local name
  */
-static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jint pointer,
-        jint attributePointer, jint index) {
-    XML_Parser parser = (XML_Parser) pointer;
-    ParsingContext* context = toParsingContext(parser);
-    return ExpatElementName(env, context, attributePointer, index).qName();
+static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jlong address,
+        jlong attributePointer, jint index) {
+  ParsingContext* context = toParsingContext(toXMLParser(address));
+  return ExpatElementName(env, context, attributePointer, index).qName();
 }
 
 /**
@@ -1154,7 +1133,7 @@
  * @returns Java string containing attribute's value
  */
 static jstring ExpatAttributes_getValueByIndex(JNIEnv* env, jobject,
-        jint attributePointer, jint index) {
+        jlong attributePointer, jint index) {
     const char** attributes = toAttributes(attributePointer);
     const char* value = attributes[(index * 2) + 1];
     return env->NewStringUTF(value);
@@ -1169,7 +1148,7 @@
  *  found
  */
 static jint ExpatAttributes_getIndexForQName(JNIEnv* env, jobject,
-        jint attributePointer, jstring qName) {
+        jlong attributePointer, jstring qName) {
     ScopedUtfChars qNameBytes(env, qName);
     if (qNameBytes.c_str() == NULL) {
         return -1;
@@ -1196,7 +1175,7 @@
  * @returns index of attribute with the given uri and local name or -1 if not
  *  found
  */
-static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jint attributePointer,
+static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jlong attributePointer,
         jstring uri, jstring localName) {
     ScopedUtfChars uriBytes(env, uri);
     if (uriBytes.c_str() == NULL) {
@@ -1228,7 +1207,7 @@
  *  found
  */
 static jstring ExpatAttributes_getValueForQName(JNIEnv* env, jobject clazz,
-        jint attributePointer, jstring qName) {
+        jlong attributePointer, jstring qName) {
     jint index = ExpatAttributes_getIndexForQName(env, clazz, attributePointer, qName);
     return index == -1 ? NULL
             : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
@@ -1244,7 +1223,7 @@
  *  found
  */
 static jstring ExpatAttributes_getValue(JNIEnv* env, jobject clazz,
-        jint attributePointer, jstring uri, jstring localName) {
+        jlong attributePointer, jstring uri, jstring localName) {
     jint index = ExpatAttributes_getIndex(env, clazz, attributePointer, uri, localName);
     return index == -1 ? NULL
             : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index);
@@ -1257,7 +1236,7 @@
  * @param address char** to clone
  * @param count number of attributes
  */
-static jint ExpatParser_cloneAttributes(JNIEnv* env, jobject, jint address, jint count) {
+static jlong ExpatParser_cloneAttributes(JNIEnv* env, jobject, jlong address, jint count) {
     const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address));
     count *= 2;
 
@@ -1291,13 +1270,13 @@
         destinationString += stringLength + 1;
     }
 
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(buffer));
+    return reinterpret_cast<uintptr_t>(buffer);
 }
 
 /**
  * Frees cloned attributes.
  */
-static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jint pointer) {
+static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jlong pointer) {
     delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer));
 }
 
@@ -1364,33 +1343,33 @@
     if (internMethod == NULL) return;
 
     // Reference to "".
-    emptyString = (jstring) env->NewGlobalRef(empty);
+    emptyString = reinterpret_cast<jstring>(env->NewGlobalRef(empty));
 }
 
 static JNINativeMethod parserMethods[] = {
-    NATIVE_METHOD(ExpatParser, appendString, "(ILjava/lang/String;Z)V"),
-    NATIVE_METHOD(ExpatParser, appendBytes, "(I[BII)V"),
-    NATIVE_METHOD(ExpatParser, appendChars, "(I[CII)V"),
-    NATIVE_METHOD(ExpatParser, cloneAttributes, "(II)I"),
-    NATIVE_METHOD(ExpatParser, column, "(I)I"),
-    NATIVE_METHOD(ExpatParser, createEntityParser, "(ILjava/lang/String;)I"),
-    NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)I"),
-    NATIVE_METHOD(ExpatParser, line, "(I)I"),
-    NATIVE_METHOD(ExpatParser, release, "(I)V"),
-    NATIVE_METHOD(ExpatParser, releaseParser, "(I)V"),
+    NATIVE_METHOD(ExpatParser, appendString, "(JLjava/lang/String;Z)V"),
+    NATIVE_METHOD(ExpatParser, appendBytes, "(J[BII)V"),
+    NATIVE_METHOD(ExpatParser, appendChars, "(J[CII)V"),
+    NATIVE_METHOD(ExpatParser, cloneAttributes, "(JI)J"),
+    NATIVE_METHOD(ExpatParser, column, "(J)I"),
+    NATIVE_METHOD(ExpatParser, createEntityParser, "(JLjava/lang/String;)J"),
+    NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)J"),
+    NATIVE_METHOD(ExpatParser, line, "(J)I"),
+    NATIVE_METHOD(ExpatParser, release, "(J)V"),
+    NATIVE_METHOD(ExpatParser, releaseParser, "(J)V"),
     NATIVE_METHOD(ExpatParser, staticInitialize, "(Ljava/lang/String;)V"),
 };
 
 static JNINativeMethod attributeMethods[] = {
-    NATIVE_METHOD(ExpatAttributes, freeAttributes, "(I)V"),
-    NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(ILjava/lang/String;)I"),
-    NATIVE_METHOD(ExpatAttributes, getIndex, "(ILjava/lang/String;Ljava/lang/String;)I"),
-    NATIVE_METHOD(ExpatAttributes, getLocalName, "(III)Ljava/lang/String;"),
-    NATIVE_METHOD(ExpatAttributes, getQName, "(III)Ljava/lang/String;"),
-    NATIVE_METHOD(ExpatAttributes, getURI, "(III)Ljava/lang/String;"),
-    NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(II)Ljava/lang/String;"),
-    NATIVE_METHOD(ExpatAttributes, getValueForQName, "(ILjava/lang/String;)Ljava/lang/String;"),
-    NATIVE_METHOD(ExpatAttributes, getValue, "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(ExpatAttributes, freeAttributes, "(J)V"),
+    NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(JLjava/lang/String;)I"),
+    NATIVE_METHOD(ExpatAttributes, getIndex, "(JLjava/lang/String;Ljava/lang/String;)I"),
+    NATIVE_METHOD(ExpatAttributes, getLocalName, "(JJI)Ljava/lang/String;"),
+    NATIVE_METHOD(ExpatAttributes, getQName, "(JJI)Ljava/lang/String;"),
+    NATIVE_METHOD(ExpatAttributes, getURI, "(JJI)Ljava/lang/String;"),
+    NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(JI)Ljava/lang/String;"),
+    NATIVE_METHOD(ExpatAttributes, getValueForQName, "(JLjava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(ExpatAttributes, getValue, "(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
 };
 void register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) {
     jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser", parserMethods, NELEM(parserMethods));
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 322f416..ee6a57e 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
@@ -29,6 +29,7 @@
 
 #include <jni.h>
 
+#include <openssl/asn1t.h>
 #include <openssl/dsa.h>
 #include <openssl/engine.h>
 #include <openssl/err.h>
@@ -36,6 +37,7 @@
 #include <openssl/rand.h>
 #include <openssl/rsa.h>
 #include <openssl/ssl.h>
+#include <openssl/x509v3.h>
 
 #include "AsynchronousSocketCloseMonitor.h"
 #include "JNIHelp.h"
@@ -65,6 +67,23 @@
 // don't overwhelm logcat
 #define WITH_JNI_TRACE_DATA_CHUNK_SIZE 512
 
+static JavaVM* gJavaVM;
+static jclass openSslOutputStreamClass;
+
+static jmethodID calendar_setMethod;
+static jmethodID inputStream_readMethod;
+static jmethodID integer_valueOfMethod;
+static jmethodID openSslInputStream_readLineMethod;
+static jmethodID outputStream_writeMethod;
+static jmethodID outputStream_flushMethod;
+
+struct OPENSSL_Delete {
+    void operator()(void* p) const {
+        OPENSSL_free(p);
+    }
+};
+typedef UniquePtr<unsigned char, OPENSSL_Delete> Unique_OPENSSL_str;
+
 struct BIO_Delete {
     void operator()(BIO* p) const {
         BIO_free(p);
@@ -79,6 +98,13 @@
 };
 typedef UniquePtr<BIGNUM, BIGNUM_Delete> Unique_BIGNUM;
 
+struct ASN1_INTEGER_Delete {
+    void operator()(ASN1_INTEGER* p) const {
+        ASN1_INTEGER_free(p);
+    }
+};
+typedef UniquePtr<ASN1_INTEGER, ASN1_INTEGER_Delete> Unique_ASN1_INTEGER;
+
 struct DH_Delete {
     void operator()(DH* p) const {
         DH_free(p);
@@ -93,6 +119,20 @@
 };
 typedef UniquePtr<DSA, DSA_Delete> Unique_DSA;
 
+struct EC_GROUP_Delete {
+    void operator()(EC_GROUP* p) const {
+        EC_GROUP_clear_free(p);
+    }
+};
+typedef UniquePtr<EC_GROUP, EC_GROUP_Delete> Unique_EC_GROUP;
+
+struct EC_POINT_Delete {
+    void operator()(EC_POINT* p) const {
+        EC_POINT_clear_free(p);
+    }
+};
+typedef UniquePtr<EC_POINT, EC_POINT_Delete> Unique_EC_POINT;
+
 struct EC_KEY_Delete {
     void operator()(EC_KEY* p) const {
         EC_KEY_free(p);
@@ -135,6 +175,27 @@
 };
 typedef UniquePtr<RSA, RSA_Delete> Unique_RSA;
 
+struct ASN1_BIT_STRING_Delete {
+    void operator()(ASN1_BIT_STRING* p) const {
+        ASN1_BIT_STRING_free(p);
+    }
+};
+typedef UniquePtr<ASN1_BIT_STRING, ASN1_BIT_STRING_Delete> Unique_ASN1_BIT_STRING;
+
+struct ASN1_OBJECT_Delete {
+    void operator()(ASN1_OBJECT* p) const {
+        ASN1_OBJECT_free(p);
+    }
+};
+typedef UniquePtr<ASN1_OBJECT, ASN1_OBJECT_Delete> Unique_ASN1_OBJECT;
+
+struct ASN1_GENERALIZEDTIME_Delete {
+    void operator()(ASN1_GENERALIZEDTIME* p) const {
+        ASN1_GENERALIZEDTIME_free(p);
+    }
+};
+typedef UniquePtr<ASN1_GENERALIZEDTIME, ASN1_GENERALIZEDTIME_Delete> Unique_ASN1_GENERALIZEDTIME;
+
 struct SSL_Delete {
     void operator()(SSL* p) const {
         SSL_free(p);
@@ -192,6 +253,13 @@
 };
 typedef UniquePtr<X509_NAME, X509_NAME_Delete> Unique_X509_NAME;
 
+struct PKCS7_Delete {
+    void operator()(PKCS7* p) const {
+        PKCS7_free(p);
+    }
+};
+typedef UniquePtr<PKCS7, PKCS7_Delete> Unique_PKCS7;
+
 struct sk_SSL_CIPHER_Delete {
     void operator()(STACK_OF(SSL_CIPHER)* p) const {
         sk_SSL_CIPHER_free(p);
@@ -213,6 +281,20 @@
 };
 typedef UniquePtr<STACK_OF(X509_NAME), sk_X509_NAME_Delete> Unique_sk_X509_NAME;
 
+struct sk_ASN1_OBJECT_Delete {
+    void operator()(STACK_OF(ASN1_OBJECT)* p) const {
+        sk_ASN1_OBJECT_free(p);
+    }
+};
+typedef UniquePtr<STACK_OF(ASN1_OBJECT), sk_ASN1_OBJECT_Delete> Unique_sk_ASN1_OBJECT;
+
+struct sk_GENERAL_NAME_Delete {
+    void operator()(STACK_OF(GENERAL_NAME)* p) const {
+        sk_GENERAL_NAME_free(p);
+    }
+};
+typedef UniquePtr<STACK_OF(GENERAL_NAME), sk_GENERAL_NAME_Delete> Unique_sk_GENERAL_NAME;
+
 /**
  * Many OpenSSL APIs take ownership of an argument on success but don't free the argument
  * on failure. This means we need to tell our scoped pointers when we've transferred ownership,
@@ -250,6 +332,111 @@
     jniThrowException(env, "java/security/SignatureException", message);
 }
 
+/**
+ * Throws a InvalidKeyException with the given string as a message.
+ */
+static void throwInvalidKeyException(JNIEnv* env, const char* message) {
+    JNI_TRACE("throwInvalidKeyException %s", message);
+    jniThrowException(env, "java/security/InvalidKeyException", message);
+}
+
+/**
+ * Throws a SignatureException with the given string as a message.
+ */
+static void throwIllegalBlockSizeException(JNIEnv* env, const char* message) {
+    JNI_TRACE("throwIllegalBlockSizeException %s", message);
+    jniThrowException(env, "javax/crypto/IllegalBlockSizeException", message);
+}
+
+/**
+ * Throws a NoSuchAlgorithmException with the given string as a message.
+ */
+static void throwNoSuchAlgorithmException(JNIEnv* env, const char* message) {
+    JNI_TRACE("throwUnknownAlgorithmException %s", message);
+    jniThrowException(env, "java/security/NoSuchAlgorithmException", message);
+}
+
+static void throwForAsn1Error(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case ASN1_R_UNABLE_TO_DECODE_RSA_KEY:
+    case ASN1_R_UNABLE_TO_DECODE_RSA_PRIVATE_KEY:
+    case ASN1_R_UNKNOWN_PUBLIC_KEY_TYPE:
+    case ASN1_R_UNSUPPORTED_PUBLIC_KEY_TYPE:
+    case ASN1_R_WRONG_PUBLIC_KEY_TYPE:
+        throwInvalidKeyException(env, message);
+        break;
+    case ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM:
+        throwNoSuchAlgorithmException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
+static void throwForEvpError(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case EVP_R_BAD_DECRYPT:
+        throwBadPaddingException(env, message);
+        break;
+    case EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
+        throwIllegalBlockSizeException(env, message);
+        break;
+    case EVP_R_BAD_KEY_LENGTH:
+    case EVP_R_BN_DECODE_ERROR:
+    case EVP_R_BN_PUBKEY_ERROR:
+    case EVP_R_INVALID_KEY_LENGTH:
+    case EVP_R_MISSING_PARAMETERS:
+    case EVP_R_UNSUPPORTED_KEY_SIZE:
+    case EVP_R_UNSUPPORTED_KEYLENGTH:
+        throwInvalidKeyException(env, message);
+        break;
+    case EVP_R_WRONG_PUBLIC_KEY_TYPE:
+        throwSignatureException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
+static void throwForRsaError(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case RSA_R_BLOCK_TYPE_IS_NOT_01:
+    case RSA_R_BLOCK_TYPE_IS_NOT_02:
+        throwBadPaddingException(env, message);
+        break;
+    case RSA_R_ALGORITHM_MISMATCH:
+    case RSA_R_BAD_SIGNATURE:
+    case RSA_R_DATA_GREATER_THAN_MOD_LEN:
+    case RSA_R_INVALID_MESSAGE_LENGTH:
+    case RSA_R_WRONG_SIGNATURE_LENGTH:
+        throwSignatureException(env, message);
+        break;
+    case RSA_R_UNKNOWN_ALGORITHM_TYPE:
+        throwNoSuchAlgorithmException(env, message);
+        break;
+    case RSA_R_MODULUS_TOO_LARGE:
+    case RSA_R_NO_PUBLIC_EXPONENT:
+        throwInvalidKeyException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
+static void throwForX509Error(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case X509_R_UNSUPPORTED_ALGORITHM:
+        throwNoSuchAlgorithmException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
 /*
  * Checks this thread's OpenSSL error queue and throws a RuntimeException if
  * necessary.
@@ -272,14 +459,25 @@
         JNI_TRACE("OpenSSL error in %s error=%lx library=%x reason=%x (%s:%d): %s %s",
                   location, error, library, reason, file, line, message,
                   (flags & ERR_TXT_STRING) ? data : "(no data)");
-        if ((library == ERR_LIB_RSA)
-                && ((reason == RSA_R_BLOCK_TYPE_IS_NOT_01)
-                    || (reason == RSA_R_BLOCK_TYPE_IS_NOT_02))) {
-            throwBadPaddingException(env, message);
-        } else if (library == ERR_LIB_RSA && reason == RSA_R_DATA_GREATER_THAN_MOD_LEN) {
-            throwSignatureException(env, message);
-        } else {
+        switch (library) {
+        case ERR_LIB_RSA:
+            throwForRsaError(env, reason, message);
+            break;
+        case ERR_LIB_ASN1:
+            throwForAsn1Error(env, reason, message);
+            break;
+        case ERR_LIB_EVP:
+            throwForEvpError(env, reason, message);
+            break;
+        case ERR_LIB_X509:
+            throwForX509Error(env, reason, message);
+            break;
+        case ERR_LIB_DSA:
+            throwInvalidKeyException(env, message);
+            break;
+        default:
             jniThrowRuntimeException(env, message);
+            break;
         }
         result = true;
     }
@@ -448,7 +646,7 @@
  * @param throwIfNull whether to throw if the SSL pointer is NULL
  * @returns the pointer, which may be NULL
  */
-static SSL_CTX* to_SSL_CTX(JNIEnv* env, int ssl_ctx_address, bool throwIfNull) {
+static SSL_CTX* to_SSL_CTX(JNIEnv* env, jlong ssl_ctx_address, bool throwIfNull) {
     SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
     if ((ssl_ctx == NULL) && throwIfNull) {
         JNI_TRACE("ssl_ctx == null");
@@ -457,7 +655,7 @@
     return ssl_ctx;
 }
 
-static SSL* to_SSL(JNIEnv* env, int ssl_address, bool throwIfNull) {
+static SSL* to_SSL(JNIEnv* env, jlong ssl_address, bool throwIfNull) {
     SSL* ssl = reinterpret_cast<SSL*>(static_cast<uintptr_t>(ssl_address));
     if ((ssl == NULL) && throwIfNull) {
         JNI_TRACE("ssl == null");
@@ -466,7 +664,7 @@
     return ssl;
 }
 
-static SSL_SESSION* to_SSL_SESSION(JNIEnv* env, int ssl_session_address, bool throwIfNull) {
+static SSL_SESSION* to_SSL_SESSION(JNIEnv* env, jlong ssl_session_address, bool throwIfNull) {
     SSL_SESSION* ssl_session
         = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
     if ((ssl_session == NULL) && throwIfNull) {
@@ -505,7 +703,7 @@
 /**
  * Converts an OpenSSL BIGNUM to a Java byte[] array.
  */
-static jbyteArray bignumToArray(JNIEnv* env, BIGNUM* source, const char* sourceName) {
+static jbyteArray bignumToArray(JNIEnv* env, const BIGNUM* source, const char* sourceName) {
     JNI_TRACE("bignumToArray(%p, %s)", source, sourceName);
 
     if (source == NULL) {
@@ -540,6 +738,305 @@
 }
 
 /**
+ * Converts various OpenSSL ASN.1 types to a jbyteArray with DER-encoded data
+ * inside. The "i2d_func" function pointer is a function of the "i2d_<TYPE>"
+ * from the OpenSSL ASN.1 API.
+ */
+template<typename T, int (*i2d_func)(T*, unsigned char**)>
+jbyteArray ASN1ToByteArray(JNIEnv* env, T* obj) {
+    if (obj == NULL) {
+        jniThrowNullPointerException(env, "ASN1 input == null");
+        JNI_TRACE("ASN1ToByteArray(%p) => null input", obj);
+        return NULL;
+    }
+
+    int derLen = i2d_func(obj, NULL);
+    if (derLen < 0) {
+        throwExceptionIfNecessary(env, "ASN1ToByteArray");
+        JNI_TRACE("ASN1ToByteArray(%p) => measurement failed", obj);
+        return NULL;
+    }
+
+    ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(derLen));
+    if (byteArray.get() == NULL) {
+        JNI_TRACE("ASN1ToByteArray(%p) => creating byte array failed", obj);
+        return NULL;
+    }
+
+    ScopedByteArrayRW bytes(env, byteArray.get());
+    if (bytes.get() == NULL) {
+        JNI_TRACE("ASN1ToByteArray(%p) => using byte array failed", obj);
+        return NULL;
+    }
+
+    unsigned char* p = reinterpret_cast<unsigned char*>(bytes.get());
+    int ret = i2d_func(obj, &p);
+    if (ret < 0) {
+        throwExceptionIfNecessary(env, "ASN1ToByteArray");
+        JNI_TRACE("ASN1ToByteArray(%p) => final conversion failed", obj);
+        return NULL;
+    }
+
+    JNI_TRACE("ASN1ToByteArray(%p) => success (%d bytes written)", obj, ret);
+    return byteArray.release();
+}
+
+template<typename T, T* (*d2i_func)(T**, const unsigned char**, long)>
+T* ByteArrayToASN1(JNIEnv* env, jbyteArray byteArray) {
+    ScopedByteArrayRO bytes(env, byteArray);
+    if (bytes.get() == NULL) {
+        JNI_TRACE("ByteArrayToASN1(%p) => using byte array failed", byteArray);
+        return 0;
+    }
+
+    const unsigned char* tmp = reinterpret_cast<const unsigned char*>(bytes.get());
+    return d2i_func(NULL, &tmp, bytes.size());
+}
+
+/**
+ * Converts ASN.1 BIT STRING to a jbooleanArray.
+ */
+jbooleanArray ASN1BitStringToBooleanArray(JNIEnv* env, ASN1_BIT_STRING* bitStr) {
+    int size = bitStr->length * 8;
+    if (bitStr->flags & ASN1_STRING_FLAG_BITS_LEFT) {
+        size -= bitStr->flags & 0x07;
+    }
+
+    ScopedLocalRef<jbooleanArray> bitsRef(env, env->NewBooleanArray(size));
+    if (bitsRef.get() == NULL) {
+        return NULL;
+    }
+
+    ScopedBooleanArrayRW bitsArray(env, bitsRef.get());
+    for (int i = 0; i < static_cast<int>(bitsArray.size()); i++) {
+        bitsArray[i] = ASN1_BIT_STRING_get_bit(bitStr, i);
+    }
+
+    return bitsRef.release();
+}
+
+/**
+ * BIO for InputStream
+ */
+class BIO_Stream {
+public:
+    BIO_Stream(jobject stream) :
+            mEof(false) {
+        JNIEnv* env = getEnv();
+        mStream = env->NewGlobalRef(stream);
+    }
+
+    ~BIO_Stream() {
+        JNIEnv* env = getEnv();
+
+        env->DeleteGlobalRef(mStream);
+    }
+
+    bool isEof() const {
+        JNI_TRACE("isEof? %s", mEof ? "yes" : "no");
+        return mEof;
+    }
+
+    int flush() {
+        JNIEnv* env = getEnv();
+        if (env == NULL) {
+            return -1;
+        }
+
+        env->CallVoidMethod(mStream, outputStream_flushMethod);
+        if (env->ExceptionCheck()) {
+            return -1;
+        }
+
+        return 1;
+    }
+
+protected:
+    jobject getStream() {
+        return mStream;
+    }
+
+    void setEof(bool eof) {
+        mEof = eof;
+    }
+
+    JNIEnv* getEnv() {
+        JNIEnv* env;
+
+        if (gJavaVM->AttachCurrentThread(&env, NULL) < 0) {
+            return NULL;
+        }
+
+        return env;
+    }
+
+private:
+    jobject mStream;
+    bool mEof;
+};
+
+class BIO_InputStream : public BIO_Stream {
+public:
+    BIO_InputStream(jobject stream) :
+            BIO_Stream(stream) {
+    }
+
+    int read(char *buf, int len) {
+        return read_internal(buf, len, inputStream_readMethod);
+    }
+
+    int gets(char *buf, int len) {
+        if (len > PEM_LINE_LENGTH) {
+            len = PEM_LINE_LENGTH;
+        }
+
+        int read = read_internal(buf, len - 1, openSslInputStream_readLineMethod);
+        buf[read] = '\0';
+        JNI_TRACE("BIO::gets \"%s\"", buf);
+        return read;
+    }
+
+private:
+    int read_internal(char *buf, int len, jmethodID method) {
+        JNIEnv* env = getEnv();
+        if (env == NULL) {
+            JNI_TRACE("BIO_InputStream::read could not get JNIEnv");
+            return -1;
+        }
+
+        ScopedLocalRef<jbyteArray> javaBytes(env, env->NewByteArray(len));
+        if (javaBytes.get() == NULL) {
+            JNI_TRACE("BIO_InputStream::read failed call to NewByteArray");
+            return -1;
+        }
+
+        jint read = env->CallIntMethod(getStream(), method, javaBytes.get());
+        if (env->ExceptionCheck()) {
+            JNI_TRACE("BIO_InputStream::read failed call to InputStream#read");
+            return -1;
+        }
+
+        /* Java uses -1 to indicate EOF condition. */
+        if (read == -1) {
+            setEof(true);
+            read = 0;
+        } else if (read > 0) {
+            env->GetByteArrayRegion(javaBytes.get(), 0, read, reinterpret_cast<jbyte*>(buf));
+        }
+
+        return read;
+    }
+
+public:
+    /** Length of PEM-encoded line (64) plus CR plus NULL */
+    static const int PEM_LINE_LENGTH = 66;
+};
+
+class BIO_OutputStream : public BIO_Stream {
+public:
+    BIO_OutputStream(jobject stream) :
+            BIO_Stream(stream) {
+    }
+
+    int write(const char *buf, int len) {
+        JNIEnv* env = getEnv();
+        if (env == NULL) {
+            JNI_TRACE("BIO_OutputStream::write => could not get JNIEnv");
+            return -1;
+        }
+
+        ScopedLocalRef<jbyteArray> javaBytes(env, env->NewByteArray(len));
+        if (javaBytes.get() == NULL) {
+            JNI_TRACE("BIO_OutputStream::write => failed call to NewByteArray");
+            return -1;
+        }
+
+        env->SetByteArrayRegion(javaBytes.get(), 0, len, reinterpret_cast<const jbyte*>(buf));
+
+        env->CallVoidMethod(getStream(), outputStream_writeMethod, javaBytes.get());
+        if (env->ExceptionCheck()) {
+            JNI_TRACE("BIO_OutputStream::write => failed call to OutputStream#write");
+            return -1;
+        }
+
+        return 1;
+    }
+};
+
+static int bio_stream_create(BIO *b) {
+    b->init = 1;
+    b->num = 0;
+    b->ptr = NULL;
+    b->flags = 0;
+    return 1;
+}
+
+static int bio_stream_destroy(BIO *b) {
+    if (b == NULL) {
+        return 0;
+    }
+
+    if (b->ptr != NULL) {
+        delete static_cast<BIO_Stream*>(b->ptr);
+        b->ptr = NULL;
+    }
+
+    b->init = 0;
+    b->flags = 0;
+    return 1;
+}
+
+static int bio_stream_read(BIO *b, char *buf, int len) {
+    BIO_InputStream* stream = static_cast<BIO_InputStream*>(b->ptr);
+    return stream->read(buf, len);
+}
+
+static int bio_stream_write(BIO *b, const char *buf, int len) {
+    BIO_OutputStream* stream = static_cast<BIO_OutputStream*>(b->ptr);
+    return stream->write(buf, len);
+}
+
+static int bio_stream_puts(BIO *b, const char *buf) {
+    BIO_OutputStream* stream = static_cast<BIO_OutputStream*>(b->ptr);
+    return stream->write(buf, strlen(buf));
+}
+
+static int bio_stream_gets(BIO *b, char *buf, int len) {
+    BIO_InputStream* stream = static_cast<BIO_InputStream*>(b->ptr);
+    return stream->gets(buf, len);
+}
+
+static void bio_stream_assign(BIO *b, BIO_Stream* stream) {
+    b->ptr = static_cast<void*>(stream);
+}
+
+static long bio_stream_ctrl(BIO *b, int cmd, long, void *) {
+    BIO_Stream* stream = static_cast<BIO_Stream*>(b->ptr);
+
+    switch (cmd) {
+    case BIO_CTRL_EOF:
+        return stream->isEof() ? 1 : 0;
+    case BIO_CTRL_FLUSH:
+        return stream->flush();
+    default:
+        return 0;
+    }
+}
+
+static BIO_METHOD stream_bio_method = {
+        ( 100 | 0x0400 ), /* source/sink BIO */
+        "InputStream/OutputStream BIO",
+        bio_stream_write, /* bio_write */
+        bio_stream_read, /* bio_read */
+        bio_stream_puts, /* bio_puts */
+        bio_stream_gets, /* bio_gets */
+        bio_stream_ctrl, /* bio_ctrl */
+        bio_stream_create, /* bio_create */
+        bio_stream_destroy, /* bio_free */
+        NULL, /* no bio_callback_ctrl */
+};
+
+/**
  * OpenSSL locking support. Taken from the O'Reilly book by Viega et al., but I
  * suppose there are not many other ways to do this on a Linux system (modulo
  * isomorphism).
@@ -621,26 +1118,26 @@
     ENGINE_load_dynamic();
 }
 
-static jint NativeCrypto_ENGINE_by_id(JNIEnv* env, jclass, jstring idJava) {
+static jlong NativeCrypto_ENGINE_by_id(JNIEnv* env, jclass, jstring idJava) {
     JNI_TRACE("ENGINE_by_id(%p)", idJava);
 
     ScopedUtfChars id(env, idJava);
     if (id.c_str() == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "id == NULL");
+        JNI_TRACE("ENGINE_by_id(%p) => id == null", idJava);
         return 0;
     }
+    JNI_TRACE("ENGINE_by_id(\"%s\")", id.c_str());
 
     ENGINE* e = ENGINE_by_id(id.c_str());
     if (e == NULL) {
-        throwExceptionIfNecessary(env, "ENGINE_by_id");
-        return 0;
+        freeOpenSslErrorState();
     }
 
-    JNI_TRACE("ENGINE_by_id(%p) => %p", idJava, e);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(e));
+    JNI_TRACE("ENGINE_by_id(\"%s\") => %p", id.c_str(), e);
+    return reinterpret_cast<uintptr_t>(e);
 }
 
-static jint NativeCrypto_ENGINE_add(JNIEnv* env, jclass, jint engineRef) {
+static jint NativeCrypto_ENGINE_add(JNIEnv* env, jclass, jlong engineRef) {
     ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
     JNI_TRACE("ENGINE_add(%p)", e);
 
@@ -661,7 +1158,7 @@
     return ret;
 }
 
-static jint NativeCrypto_ENGINE_init(JNIEnv* env, jclass, jint engineRef) {
+static jint NativeCrypto_ENGINE_init(JNIEnv* env, jclass, jlong engineRef) {
     ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
     JNI_TRACE("ENGINE_init(%p)", e);
 
@@ -675,7 +1172,7 @@
     return ret;
 }
 
-static jint NativeCrypto_ENGINE_finish(JNIEnv* env, jclass, jint engineRef) {
+static jint NativeCrypto_ENGINE_finish(JNIEnv* env, jclass, jlong engineRef) {
     ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
     JNI_TRACE("ENGINE_finish(%p)", e);
 
@@ -689,7 +1186,7 @@
     return ret;
 }
 
-static jint NativeCrypto_ENGINE_free(JNIEnv* env, jclass, jint engineRef) {
+static jint NativeCrypto_ENGINE_free(JNIEnv* env, jclass, jlong engineRef) {
     ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
     JNI_TRACE("ENGINE_free(%p)", e);
 
@@ -703,7 +1200,7 @@
     return ret;
 }
 
-static jint NativeCrypto_ENGINE_load_private_key(JNIEnv* env, jclass, jint engineRef,
+static jlong NativeCrypto_ENGINE_load_private_key(JNIEnv* env, jclass, jlong engineRef,
         jstring idJava) {
     ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
     JNI_TRACE("ENGINE_load_private_key(%p, %p)", e, idJava);
@@ -721,14 +1218,75 @@
     }
 
     JNI_TRACE("ENGINE_load_private_key(%p, %p) => %p", e, idJava, pkey.get());
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(pkey.release()));
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static jstring NativeCrypto_ENGINE_get_id(JNIEnv* env, jclass, jlong engineRef)
+{
+    ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
+    JNI_TRACE("ENGINE_get_id(%p)", e);
+
+    if (e == NULL) {
+        jniThrowNullPointerException(env, "engine == null");
+        JNI_TRACE("ENGINE_get_id(%p) => engine == null", e);
+        return NULL;
+    }
+
+    const char *id = ENGINE_get_id(e);
+    ScopedLocalRef<jstring> idJava(env, env->NewStringUTF(id));
+
+    JNI_TRACE("ENGINE_get_id(%p) => \"%s\"", e, id);
+    return idJava.release();
+}
+
+static jint NativeCrypto_ENGINE_ctrl_cmd_string(JNIEnv* env, jclass, jlong engineRef,
+        jstring cmdJava, jstring argJava, jint cmd_optional)
+{
+    ENGINE* e = reinterpret_cast<ENGINE*>(static_cast<uintptr_t>(engineRef));
+    JNI_TRACE("ENGINE_ctrl_cmd_string(%p, %p, %p, %d)", e, cmdJava, argJava, cmd_optional);
+
+    if (e == NULL) {
+        jniThrowNullPointerException(env, "engine == null");
+        JNI_TRACE("ENGINE_ctrl_cmd_string(%p, %p, %p, %d) => engine == null", e, cmdJava, argJava,
+                cmd_optional);
+        return 0;
+    }
+
+    ScopedUtfChars cmdChars(env, cmdJava);
+    if (cmdChars.c_str() == NULL) {
+        return 0;
+    }
+
+    UniquePtr<ScopedUtfChars> arg;
+    const char* arg_c_str = NULL;
+    if (argJava != NULL) {
+        arg.reset(new ScopedUtfChars(env, argJava));
+        arg_c_str = arg->c_str();
+        if (arg_c_str == NULL) {
+            return 0;
+        }
+    }
+    JNI_TRACE("ENGINE_ctrl_cmd_string(%p, \"%s\", \"%s\", %d)", e, cmdChars.c_str(), arg_c_str,
+            cmd_optional);
+
+    int ret = ENGINE_ctrl_cmd_string(e, cmdChars.c_str(), arg_c_str, cmd_optional);
+    if (ret != 1) {
+        throwExceptionIfNecessary(env, "ENGINE_ctrl_cmd_string");
+        JNI_TRACE("ENGINE_ctrl_cmd_string(%p, \"%s\", \"%s\", %d) => threw error", e,
+                cmdChars.c_str(), arg_c_str, cmd_optional);
+        return 0;
+    }
+
+    JNI_TRACE("ENGINE_ctrl_cmd_string(%p, \"%s\", \"%s\", %d) => %d", e, cmdChars.c_str(),
+            arg_c_str, cmd_optional, ret);
+    return ret;
 }
 
 /**
  * public static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g,
  *                                           byte[] pub_key, byte[] priv_key);
  */
-static EVP_PKEY* NativeCrypto_EVP_PKEY_new_DSA(JNIEnv* env, jclass,
+static jlong NativeCrypto_EVP_PKEY_new_DSA(JNIEnv* env, jclass,
                                                jbyteArray p, jbyteArray q, jbyteArray g,
                                                jbyteArray pub_key, jbyteArray priv_key) {
     JNI_TRACE("EVP_PKEY_new_DSA(p=%p, q=%p, g=%p, pub_key=%p, priv_key=%p)",
@@ -737,54 +1295,54 @@
     Unique_DSA dsa(DSA_new());
     if (dsa.get() == NULL) {
         jniThrowRuntimeException(env, "DSA_new failed");
-        return NULL;
+        return 0;
     }
 
     if (!arrayToBignum(env, p, &dsa->p)) {
-        return NULL;
+        return 0;
     }
 
     if (!arrayToBignum(env, q, &dsa->q)) {
-        return NULL;
+        return 0;
     }
 
     if (!arrayToBignum(env, g, &dsa->g)) {
-        return NULL;
+        return 0;
     }
 
     if (pub_key != NULL && !arrayToBignum(env, pub_key, &dsa->pub_key)) {
-        return NULL;
+        return 0;
     }
 
     if (priv_key != NULL && !arrayToBignum(env, priv_key, &dsa->priv_key)) {
-        return NULL;
+        return 0;
     }
 
     if (dsa->p == NULL || dsa->q == NULL || dsa->g == NULL
             || (dsa->pub_key == NULL && dsa->priv_key == NULL)) {
         jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM");
-        return NULL;
+        return 0;
     }
 
     Unique_EVP_PKEY pkey(EVP_PKEY_new());
     if (pkey.get() == NULL) {
         jniThrowRuntimeException(env, "EVP_PKEY_new failed");
-        return NULL;
+        return 0;
     }
     if (EVP_PKEY_assign_DSA(pkey.get(), dsa.get()) != 1) {
         jniThrowRuntimeException(env, "EVP_PKEY_assign_DSA failed");
-        return NULL;
+        return 0;
     }
     OWNERSHIP_TRANSFERRED(dsa);
     JNI_TRACE("EVP_PKEY_new_DSA(p=%p, q=%p, g=%p, pub_key=%p, priv_key=%p) => %p",
               p, q, g, pub_key, priv_key, pkey.get());
-    return pkey.release();
+    return reinterpret_cast<jlong>(pkey.release());
 }
 
 /**
  * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q);
  */
-static jint NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass,
+static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass,
                                                jbyteArray n, jbyteArray e, jbyteArray d,
                                                jbyteArray p, jbyteArray q,
                                                jbyteArray dmp1, jbyteArray dmq1,
@@ -875,13 +1433,111 @@
     OWNERSHIP_TRANSFERRED(rsa);
     JNI_TRACE("EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, iqmp=%p) => %p",
             n, e, d, p, q, dmp1, dmq1, iqmp, pkey.get());
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(pkey.release()));
+    return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
-/**
- * private static native int EVP_PKEY_size(int pkey);
- */
-static int NativeCrypto_EVP_PKEY_type(JNIEnv* env, jclass, jint pkeyRef) {
+static jlong NativeCrypto_EVP_PKEY_new_EC_KEY(JNIEnv* env, jclass, jlong groupRef,
+        jlong pubkeyRef, jbyteArray keyJavaBytes) {
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    const EC_POINT* pubkey = reinterpret_cast<const EC_POINT*>(pubkeyRef);
+    JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p)", group, pubkey, keyJavaBytes);
+
+    Unique_BIGNUM key(NULL);
+    if (keyJavaBytes != NULL) {
+        BIGNUM* keyRef;
+        if (!arrayToBignum(env, keyJavaBytes, &keyRef)) {
+            return 0;
+        }
+        key.reset(keyRef);
+    }
+
+    Unique_EC_KEY eckey(EC_KEY_new());
+    if (eckey.get() == NULL) {
+        jniThrowRuntimeException(env, "EC_KEY_new failed");
+        return 0;
+    }
+
+    if (EC_KEY_set_group(eckey.get(), group) != 1) {
+        JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) > EC_KEY_set_group failed", group, pubkey,
+                keyJavaBytes);
+        throwExceptionIfNecessary(env, "EC_KEY_set_group");
+        return 0;
+    }
+
+    if (pubkey != NULL) {
+        if (EC_KEY_set_public_key(eckey.get(), pubkey) != 1) {
+            JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => EC_KEY_set_private_key failed", group,
+                    pubkey, keyJavaBytes);
+            throwExceptionIfNecessary(env, "EC_KEY_set_public_key");
+            return 0;
+        }
+    }
+
+    if (key.get() != NULL) {
+        if (EC_KEY_set_private_key(eckey.get(), key.get()) != 1) {
+            JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => EC_KEY_set_private_key failed", group,
+                    pubkey, keyJavaBytes);
+            throwExceptionIfNecessary(env, "EC_KEY_set_private_key");
+            return 0;
+        }
+        if (pubkey == NULL) {
+            Unique_EC_POINT calcPubkey(EC_POINT_new(group));
+            if (!EC_POINT_mul(group, calcPubkey.get(), key.get(), NULL, NULL, NULL)) {
+                JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => can't calulate public key", group,
+                        pubkey, keyJavaBytes);
+                throwExceptionIfNecessary(env, "EC_KEY_set_private_key");
+                return 0;
+            }
+            EC_KEY_set_public_key(eckey.get(), calcPubkey.get());
+        }
+    }
+
+    if (!EC_KEY_check_key(eckey.get())) {
+        JNI_TRACE("EVP_KEY_new_EC_KEY(%p, %p, %p) => invalid key created", group, pubkey, keyJavaBytes);
+        throwExceptionIfNecessary(env, "EC_KEY_check_key");
+        return 0;
+    }
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        JNI_TRACE("EVP_PKEY_new_EC(%p, %p, %p) => threw error", group, pubkey, keyJavaBytes);
+        throwExceptionIfNecessary(env, "EVP_PKEY_new failed");
+        return 0;
+    }
+    if (EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get()) != 1) {
+        JNI_TRACE("EVP_PKEY_new_EC(%p, %p, %p) => threw error", group, pubkey, keyJavaBytes);
+        jniThrowRuntimeException(env, "EVP_PKEY_assign_EC_KEY failed");
+        return 0;
+    }
+    OWNERSHIP_TRANSFERRED(eckey);
+
+    JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => %p", group, pubkey, keyJavaBytes, pkey.get());
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static jlong NativeCrypto_EVP_PKEY_new_mac_key(JNIEnv* env, jclass, jint pkeyType,
+        jbyteArray keyJavaBytes)
+{
+    JNI_TRACE("EVP_PKEY_new_mac_key(%d, %p)", pkeyType, keyJavaBytes);
+
+    ScopedByteArrayRO key(env, keyJavaBytes);
+    if (key.get() == NULL) {
+        return 0;
+    }
+
+    const unsigned char* tmp = reinterpret_cast<const unsigned char*>(key.get());
+    Unique_EVP_PKEY pkey(EVP_PKEY_new_mac_key(pkeyType, (ENGINE *) NULL, tmp, key.size()));
+    if (pkey.get() == NULL) {
+        JNI_TRACE("EVP_PKEY_new_mac_key(%d, %p) => threw error", pkeyType, keyJavaBytes);
+        throwExceptionIfNecessary(env, "ENGINE_load_private_key");
+        return 0;
+    }
+
+    JNI_TRACE("EVP_PKEY_new_mac_key(%d, %p) => %p", pkeyType, keyJavaBytes, pkey.get());
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static int NativeCrypto_EVP_PKEY_type(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("EVP_PKEY_type(%p)", pkey);
 
@@ -898,7 +1554,7 @@
 /**
  * private static native int EVP_PKEY_size(int pkey);
  */
-static int NativeCrypto_EVP_PKEY_size(JNIEnv* env, jclass, jint pkeyRef) {
+static int NativeCrypto_EVP_PKEY_size(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("EVP_PKEY_size(%p)", pkey);
 
@@ -912,10 +1568,68 @@
     return result;
 }
 
-/**
- * private static native void EVP_PKEY_free(int pkey);
- */
-static void NativeCrypto_EVP_PKEY_free(JNIEnv*, jclass, EVP_PKEY* pkey) {
+static jstring NativeCrypto_EVP_PKEY_print_public(JNIEnv* env, jclass, jlong pkeyRef) {
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EVP_PKEY_print_public(%p)", pkey);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return NULL;
+    }
+
+    Unique_BIO buffer(BIO_new(BIO_s_mem()));
+    if (buffer.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable to allocate BIO");
+        return NULL;
+    }
+
+    if (EVP_PKEY_print_public(buffer.get(), pkey, 0, (ASN1_PCTX*) NULL) != 1) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_print_public");
+        return NULL;
+    }
+    // Null terminate this
+    BIO_write(buffer.get(), "\0", 1);
+
+    char *tmp;
+    BIO_get_mem_data(buffer.get(), &tmp);
+    jstring description = env->NewStringUTF(tmp);
+
+    JNI_TRACE("EVP_PKEY_print_public(%p) => \"%s\"", pkey, tmp);
+    return description;
+}
+
+static jstring NativeCrypto_EVP_PKEY_print_private(JNIEnv* env, jclass, jlong pkeyRef) {
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EVP_PKEY_print_private(%p)", pkey);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return NULL;
+    }
+
+    Unique_BIO buffer(BIO_new(BIO_s_mem()));
+    if (buffer.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable to allocate BIO");
+        return NULL;
+    }
+
+    if (EVP_PKEY_print_private(buffer.get(), pkey, 0, (ASN1_PCTX*) NULL) != 1) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_print_private");
+        return NULL;
+    }
+    // Null terminate this
+    BIO_write(buffer.get(), "\0", 1);
+
+    char *tmp;
+    BIO_get_mem_data(buffer.get(), &tmp);
+    jstring description = env->NewStringUTF(tmp);
+
+    JNI_TRACE("EVP_PKEY_print_private(%p) => \"%s\"", pkey, tmp);
+    return description;
+}
+
+static void NativeCrypto_EVP_PKEY_free(JNIEnv*, jclass, jlong pkeyRef) {
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("EVP_PKEY_free(%p)", pkey);
 
     if (pkey != NULL) {
@@ -923,7 +1637,7 @@
     }
 }
 
-static jint NativeCrypto_EVP_PKEY_cmp(JNIEnv* env, jclass, jint pkey1Ref, jint pkey2Ref) {
+static jint NativeCrypto_EVP_PKEY_cmp(JNIEnv* env, jclass, jlong pkey1Ref, jlong 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);
@@ -946,7 +1660,7 @@
 /*
  * static native byte[] i2d_PKCS8_PRIV_KEY_INFO(int, byte[])
  */
-static jbyteArray NativeCrypto_i2d_PKCS8_PRIV_KEY_INFO(JNIEnv* env, jclass, jint pkeyRef) {
+static jbyteArray NativeCrypto_i2d_PKCS8_PRIV_KEY_INFO(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("i2d_PKCS8_PRIV_KEY_INFO(%p)", pkey);
 
@@ -962,32 +1676,13 @@
         return NULL;
     }
 
-    int len = i2d_PKCS8_PRIV_KEY_INFO(pkcs8.get(), NULL);
-    if (len < 0) {
-        throwExceptionIfNecessary(env, "i2d_PKCS8_PRIV_KEY_INFO");
-        return NULL;
-    }
-
-    jbyteArray javaBytes = env->NewByteArray(len);
-    ScopedByteArrayRW bytes(env, javaBytes);
-    if (bytes.get() == NULL) {
-        return NULL;
-    }
-
-    unsigned char* tmp = reinterpret_cast<unsigned char*>(bytes.get());
-    if (i2d_PKCS8_PRIV_KEY_INFO(pkcs8.get(), &tmp) < 0) {
-        throwExceptionIfNecessary(env, "i2d_PKCS8_PRIV_KEY_INFO");
-        return NULL;
-    }
-
-    JNI_TRACE("pkey=%p i2d_PKCS8_PRIV_KEY_INFO => size=%d", pkey, len);
-    return javaBytes;
+    return ASN1ToByteArray<PKCS8_PRIV_KEY_INFO, i2d_PKCS8_PRIV_KEY_INFO>(env, pkcs8.get());
 }
 
 /*
  * static native int d2i_PKCS8_PRIV_KEY_INFO(byte[])
  */
-static jint NativeCrypto_d2i_PKCS8_PRIV_KEY_INFO(JNIEnv* env, jclass, jbyteArray keyJavaBytes) {
+static jlong NativeCrypto_d2i_PKCS8_PRIV_KEY_INFO(JNIEnv* env, jclass, jbyteArray keyJavaBytes) {
     JNI_TRACE("d2i_PKCS8_PRIV_KEY_INFO(%p)", keyJavaBytes);
 
     ScopedByteArrayRO bytes(env, keyJavaBytes);
@@ -1012,49 +1707,22 @@
     }
 
     JNI_TRACE("bytes=%p d2i_PKCS8_PRIV_KEY_INFO => %p", keyJavaBytes, pkey.get());
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(pkey.release()));
+    return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
 /*
  * static native byte[] i2d_PUBKEY(int)
  */
-static jbyteArray NativeCrypto_i2d_PUBKEY(JNIEnv* env, jclass, jint pkeyRef) {
+static jbyteArray NativeCrypto_i2d_PUBKEY(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("i2d_PUBKEY(%p)", pkey);
-
-    if (pkey == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return NULL;
-    }
-
-    int len = i2d_PUBKEY(pkey, NULL);
-    if (len < 0) {
-        throwExceptionIfNecessary(env, "i2d_PUBKEY");
-        JNI_TRACE("i2d_PUBKEY(%p) => threw error measuring key", pkey);
-        return NULL;
-    }
-
-    jbyteArray javaBytes = env->NewByteArray(len);
-    ScopedByteArrayRW bytes(env, javaBytes);
-    if (bytes.get() == NULL) {
-        return NULL;
-    }
-
-    unsigned char* tmp = reinterpret_cast<unsigned char*>(bytes.get());
-    if (i2d_PUBKEY(pkey, &tmp) < 0) {
-        throwExceptionIfNecessary(env, "i2d_PUBKEY");
-        JNI_TRACE("i2d_PUBKEY(%p) => threw error converting key", pkey);
-        return NULL;
-    }
-
-    JNI_TRACE("pkey=%p i2d_PUBKEY => size=%d", pkey, len);
-    return javaBytes;
+    return ASN1ToByteArray<EVP_PKEY, i2d_PUBKEY>(env, pkey);
 }
 
 /*
  * static native int d2i_PUBKEY(byte[])
  */
-static jint NativeCrypto_d2i_PUBKEY(JNIEnv* env, jclass, jbyteArray javaBytes) {
+static jlong NativeCrypto_d2i_PUBKEY(JNIEnv* env, jclass, jbyteArray javaBytes) {
     JNI_TRACE("d2i_PUBKEY(%p)", javaBytes);
 
     ScopedByteArrayRO bytes(env, javaBytes);
@@ -1071,13 +1739,13 @@
         return 0;
     }
 
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(pkey.release()));
+    return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
 /*
  * public static native int RSA_generate_key(int modulusBits, byte[] publicExponent);
  */
-static jint NativeCrypto_RSA_generate_key_ex(JNIEnv* env, jclass, jint modulusBits,
+static jlong NativeCrypto_RSA_generate_key_ex(JNIEnv* env, jclass, jint modulusBits,
         jbyteArray publicExponent) {
     JNI_TRACE("RSA_generate_key_ex(%d, %p)", modulusBits, publicExponent);
 
@@ -1111,10 +1779,10 @@
 
     OWNERSHIP_TRANSFERRED(rsa);
     JNI_TRACE("RSA_generate_key_ex(n=%d, e=%p) => %p", modulusBits, publicExponent, pkey.get());
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(pkey.release()));
+    return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
-static jint NativeCrypto_RSA_size(JNIEnv* env, jclass, jint pkeyRef) {
+static jint NativeCrypto_RSA_size(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("RSA_size(%p)", pkey);
 
@@ -1132,7 +1800,7 @@
 
 static jint RSA_crypt_operation(RSACryptOperation operation,
         const char* caller __attribute__ ((unused)), JNIEnv* env, jint flen,
-        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jint pkeyRef, jint padding) {
+        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jlong pkeyRef, jint padding) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("%s(%d, %p, %p, %p)", caller, flen, fromJavaBytes, toJavaBytes, pkey);
 
@@ -1166,22 +1834,22 @@
 }
 
 static jint NativeCrypto_RSA_private_encrypt(JNIEnv* env, jclass, jint flen,
-        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jint pkeyRef, jint padding) {
+        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jlong pkeyRef, jint padding) {
     return RSA_crypt_operation(RSA_private_encrypt, __FUNCTION__,
                                env, flen, fromJavaBytes, toJavaBytes, pkeyRef, padding);
 }
 static jint NativeCrypto_RSA_public_decrypt(JNIEnv* env, jclass, jint flen,
-        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jint pkeyRef, jint padding) {
+        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jlong pkeyRef, jint padding) {
     return RSA_crypt_operation(RSA_public_decrypt, __FUNCTION__,
                                env, flen, fromJavaBytes, toJavaBytes, pkeyRef, padding);
 }
 static jint NativeCrypto_RSA_public_encrypt(JNIEnv* env, jclass, jint flen,
-        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jint pkeyRef, jint padding) {
+        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jlong pkeyRef, jint padding) {
     return RSA_crypt_operation(RSA_public_encrypt, __FUNCTION__,
                                env, flen, fromJavaBytes, toJavaBytes, pkeyRef, padding);
 }
 static jint NativeCrypto_RSA_private_decrypt(JNIEnv* env, jclass, jint flen,
-        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jint pkeyRef, jint padding) {
+        jbyteArray fromJavaBytes, jbyteArray toJavaBytes, jlong pkeyRef, jint padding) {
     return RSA_crypt_operation(RSA_private_decrypt, __FUNCTION__,
                                env, flen, fromJavaBytes, toJavaBytes, pkeyRef, padding);
 }
@@ -1189,13 +1857,18 @@
 /*
  * public static native byte[][] get_RSA_public_params(int);
  */
-static jobjectArray NativeCrypto_get_RSA_public_params(JNIEnv* env, jclass, jint pkeyRef) {
+static jobjectArray NativeCrypto_get_RSA_public_params(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("get_RSA_public_params(%p)", pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return 0;
+    }
+
     Unique_RSA rsa(EVP_PKEY_get1_RSA(pkey));
     if (rsa.get() == NULL) {
-        jniThrowRuntimeException(env, "get_RSA_public_params failed");
+        throwExceptionIfNecessary(env, "get_RSA_public_params failed");
         return 0;
     }
 
@@ -1222,13 +1895,18 @@
 /*
  * public static native byte[][] get_RSA_private_params(int);
  */
-static jobjectArray NativeCrypto_get_RSA_private_params(JNIEnv* env, jclass, jint pkeyRef) {
+static jobjectArray NativeCrypto_get_RSA_private_params(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("get_RSA_public_params(%p)", pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return 0;
+    }
+
     Unique_RSA rsa(EVP_PKEY_get1_RSA(pkey));
     if (rsa.get() == NULL) {
-        jniThrowRuntimeException(env, "get_RSA_public_params failed");
+        throwExceptionIfNecessary(env, "get_RSA_public_params failed");
         return 0;
     }
 
@@ -1305,7 +1983,7 @@
 /*
  * public static native int DSA_generate_key(int, byte[]);
  */
-static jint NativeCrypto_DSA_generate_key(JNIEnv* env, jclass, jint primeBits,
+static jlong NativeCrypto_DSA_generate_key(JNIEnv* env, jclass, jint primeBits,
         jbyteArray seedJavaBytes, jbyteArray gBytes, jbyteArray pBytes, jbyteArray qBytes) {
     JNI_TRACE("DSA_generate_key(%d, %p, %p, %p, %p)", primeBits, seedJavaBytes,
             gBytes, pBytes, qBytes);
@@ -1328,6 +2006,7 @@
     if (dsa.get() == NULL) {
         JNI_TRACE("DSA_generate_key failed");
         jniThrowOutOfMemoryError(env, "Unable to allocate DSA key");
+        freeOpenSslErrorState();
         return 0;
     }
 
@@ -1365,30 +2044,31 @@
     if (pkey.get() == NULL) {
         JNI_TRACE("DSA_generate_key failed");
         jniThrowRuntimeException(env, "NativeCrypto_DSA_generate_key failed");
+        freeOpenSslErrorState();
         return 0;
     }
 
     if (EVP_PKEY_assign_DSA(pkey.get(), dsa.get()) != 1) {
         JNI_TRACE("DSA_generate_key failed");
-        jniThrowRuntimeException(env, "NativeCrypto_DSA_generate_key failed");
+        throwExceptionIfNecessary(env, "NativeCrypto_DSA_generate_key failed");
         return 0;
     }
 
     OWNERSHIP_TRANSFERRED(dsa);
     JNI_TRACE("DSA_generate_key(n=%d, e=%p) => %p", primeBits, seedPtr.get(), pkey.get());
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(pkey.release()));
+    return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
 /*
  * public static native byte[][] get_DSA_params(int);
  */
-static jobjectArray NativeCrypto_get_DSA_params(JNIEnv* env, jclass, jint pkeyRef) {
+static jobjectArray NativeCrypto_get_DSA_params(JNIEnv* env, jclass, jlong pkeyRef) {
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("get_DSA_params(%p)", pkey);
 
     Unique_DSA dsa(EVP_PKEY_get1_DSA(pkey));
     if (dsa.get() == NULL) {
-        jniThrowRuntimeException(env, "get_DSA_params failed");
+        throwExceptionIfNecessary(env, "get_DSA_params failed");
         return 0;
     }
 
@@ -1397,23 +2077,29 @@
         return NULL;
     }
 
-    jbyteArray g = bignumToArray(env, dsa->g, "g");
-    if (env->ExceptionCheck()) {
-        return NULL;
+    if (dsa->g != NULL) {
+        jbyteArray g = bignumToArray(env, dsa->g, "g");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 0, g);
     }
-    env->SetObjectArrayElement(joa, 0, g);
 
-    jbyteArray p = bignumToArray(env, dsa->p, "p");
-    if (env->ExceptionCheck()) {
-        return NULL;
+    if (dsa->p != NULL) {
+        jbyteArray p = bignumToArray(env, dsa->p, "p");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 1, p);
     }
-    env->SetObjectArrayElement(joa, 1, p);
 
-    jbyteArray q = bignumToArray(env, dsa->q, "q");
-    if (env->ExceptionCheck()) {
-        return NULL;
+    if (dsa->q != NULL) {
+        jbyteArray q = bignumToArray(env, dsa->q, "q");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 2, q);
     }
-    env->SetObjectArrayElement(joa, 2, q);
 
     if (dsa->pub_key != NULL) {
         jbyteArray pub_key = bignumToArray(env, dsa->pub_key, "pub_key");
@@ -1434,10 +2120,716 @@
     return joa;
 }
 
-/*
- * public static native void EVP_MD_CTX_destroy(int)
+#define EC_CURVE_GFP 1
+#define EC_CURVE_GF2M 2
+
+/**
+ * Return group type or 0 if unknown group.
+ * EC_GROUP_GFP or EC_GROUP_GF2M
  */
-static void NativeCrypto_EVP_MD_CTX_destroy(JNIEnv*, jclass, EVP_MD_CTX* ctx) {
+static int get_EC_GROUP_type(const EC_GROUP* group)
+{
+    const EC_METHOD* method = EC_GROUP_method_of(group);
+    if (method == EC_GFp_nist_method()
+                || method == EC_GFp_mont_method()
+                || method == EC_GFp_simple_method()) {
+        return EC_CURVE_GFP;
+    } else if (method == EC_GF2m_simple_method()) {
+        return EC_CURVE_GF2M;
+    }
+
+    return 0;
+}
+
+static jlong NativeCrypto_EC_GROUP_new_by_curve_name(JNIEnv* env, jclass, jstring curveNameJava)
+{
+    JNI_TRACE("EC_GROUP_new_by_curve_name(%p)", curveNameJava);
+
+    ScopedUtfChars curveName(env, curveNameJava);
+    if (curveName.c_str() == NULL) {
+        return 0;
+    }
+    JNI_TRACE("EC_GROUP_new_by_curve_name(%s)", curveName.c_str());
+
+    int nid = OBJ_sn2nid(curveName.c_str());
+    if (nid == NID_undef) {
+        JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => unknown NID name", curveName.c_str());
+        return 0;
+    }
+
+    EC_GROUP* group = EC_GROUP_new_by_curve_name(nid);
+    if (group == NULL) {
+        JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => unknown NID %d", curveName.c_str(), nid);
+        freeOpenSslErrorState();
+        return 0;
+    }
+
+    JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => %p", curveName.c_str(), group);
+    return reinterpret_cast<uintptr_t>(group);
+}
+
+static void NativeCrypto_EC_GROUP_set_asn1_flag(JNIEnv* env, jclass, jlong groupRef,
+        jint flag)
+{
+    EC_GROUP* group = reinterpret_cast<EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_set_asn1_flag(%p, %d)", group, flag);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_GROUP_set_asn1_flag => group == NULL");
+        jniThrowNullPointerException(env, "group == NULL");
+        return;
+    }
+
+    EC_GROUP_set_asn1_flag(group, flag);
+    JNI_TRACE("EC_GROUP_set_asn1_flag(%p, %d) => success", group, flag);
+}
+
+static void NativeCrypto_EC_GROUP_set_point_conversion_form(JNIEnv* env, jclass,
+        jlong groupRef, jint form)
+{
+    EC_GROUP* group = reinterpret_cast<EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_set_point_conversion_form(%p, %d)", group, form);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_GROUP_set_point_conversion_form => group == NULL");
+        jniThrowNullPointerException(env, "group == NULL");
+        return;
+    }
+
+    EC_GROUP_set_point_conversion_form(group, static_cast<point_conversion_form_t>(form));
+    JNI_TRACE("EC_GROUP_set_point_conversion_form(%p, %d) => success", group, form);
+}
+
+static jlong NativeCrypto_EC_GROUP_new_curve(JNIEnv* env, jclass, jint type, jbyteArray pJava,
+        jbyteArray aJava, jbyteArray bJava)
+{
+    JNI_TRACE("EC_GROUP_new_curve(%d, %p, %p, %p)", type, pJava, aJava, bJava);
+
+    BIGNUM* pRef;
+    if (!arrayToBignum(env, pJava, &pRef)) {
+        return 0;
+    }
+    Unique_BIGNUM p(pRef);
+
+    BIGNUM* aRef;
+    if (!arrayToBignum(env, aJava, &aRef)) {
+        return 0;
+    }
+    Unique_BIGNUM a(aRef);
+
+    BIGNUM* bRef;
+    if (!arrayToBignum(env, bJava, &bRef)) {
+        return 0;
+    }
+    Unique_BIGNUM b(bRef);
+
+    EC_GROUP* group;
+    switch (type) {
+    case EC_CURVE_GFP:
+        group = EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), (BN_CTX*) NULL);
+        break;
+    case EC_CURVE_GF2M:
+        group = EC_GROUP_new_curve_GF2m(p.get(), a.get(), b.get(), (BN_CTX*) NULL);
+        break;
+    default:
+        jniThrowRuntimeException(env, "invalid group");
+        return 0;
+    }
+
+    if (group == NULL) {
+        throwExceptionIfNecessary(env, "EC_GROUP_new_curve");
+    }
+
+    JNI_TRACE("EC_GROUP_new_curve(%d, %p, %p, %p) => %p", type, pJava, aJava, bJava, group);
+    return reinterpret_cast<uintptr_t>(group);
+}
+
+static jlong NativeCrypto_EC_GROUP_dup(JNIEnv* env, jclass, jlong groupRef) {
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_dup(%p)", group);
+
+    if (group == NULL) {
+         JNI_TRACE("EC_GROUP_dup => group == NULL");
+         jniThrowNullPointerException(env, "group == NULL");
+         return 0;
+     }
+
+     EC_GROUP* groupDup = EC_GROUP_dup(group);
+     JNI_TRACE("EC_GROUP_dup(%p) => %p", group, groupDup);
+     return reinterpret_cast<uintptr_t>(groupDup);
+}
+
+static jstring NativeCrypto_EC_GROUP_get_curve_name(JNIEnv* env, jclass, jlong groupRef) {
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_get_curve_name(%p)", group);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_GROUP_get_curve_name => group == NULL");
+        jniThrowNullPointerException(env, "group == NULL");
+        return 0;
+    }
+
+    int nid = EC_GROUP_get_curve_name(group);
+    if (nid == NID_undef) {
+        JNI_TRACE("EC_GROUP_get_curve_name(%p) => unnamed curve", group);
+        return NULL;
+    }
+
+    const char* shortName = OBJ_nid2sn(nid);
+    JNI_TRACE("EC_GROUP_get_curve_name(%p) => \"%s\"", group, shortName);
+    return env->NewStringUTF(shortName);
+}
+
+static jobjectArray NativeCrypto_EC_GROUP_get_curve(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_get_curve(%p)", group);
+
+    Unique_BIGNUM p(BN_new());
+    Unique_BIGNUM a(BN_new());
+    Unique_BIGNUM b(BN_new());
+
+    int ret;
+    switch (get_EC_GROUP_type(group)) {
+    case EC_CURVE_GFP:
+        ret = EC_GROUP_get_curve_GFp(group, p.get(), a.get(), b.get(), (BN_CTX*) NULL);
+        break;
+    case EC_CURVE_GF2M:
+        ret = EC_GROUP_get_curve_GF2m(group, p.get(), a.get(), b.get(), (BN_CTX*)NULL);
+        break;
+    default:
+        jniThrowRuntimeException(env, "invalid group");
+        return NULL;
+    }
+    if (ret != 1) {
+        throwExceptionIfNecessary(env, "EC_GROUP_get_curve");
+        return NULL;
+    }
+
+    jobjectArray joa = env->NewObjectArray(3, JniConstants::byteArrayClass, NULL);
+    if (joa == NULL) {
+        return NULL;
+    }
+
+    jbyteArray pArray = bignumToArray(env, p.get(), "p");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+    env->SetObjectArrayElement(joa, 0, pArray);
+
+    jbyteArray aArray = bignumToArray(env, a.get(), "a");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+    env->SetObjectArrayElement(joa, 1, aArray);
+
+    jbyteArray bArray = bignumToArray(env, b.get(), "b");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+    env->SetObjectArrayElement(joa, 2, bArray);
+
+    JNI_TRACE("EC_GROUP_get_curve(%p) => %p", group, joa);
+    return joa;
+}
+
+static jbyteArray NativeCrypto_EC_GROUP_get_order(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_get_order(%p)", group);
+
+    Unique_BIGNUM order(BN_new());
+    if (order.get() == NULL) {
+        JNI_TRACE("EC_GROUP_get_order(%p) => can't create BN", group);
+        jniThrowOutOfMemoryError(env, "BN_new");
+        return NULL;
+    }
+
+    if (EC_GROUP_get_order(group, order.get(), NULL) != 1) {
+        JNI_TRACE("EC_GROUP_get_order(%p) => threw error", group);
+        throwExceptionIfNecessary(env, "EC_GROUP_get_order");
+        return NULL;
+    }
+
+    jbyteArray orderArray = bignumToArray(env, order.get(), "order");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+
+    JNI_TRACE("EC_GROUP_get_order(%p) => %p", group, orderArray);
+    return orderArray;
+}
+
+static jbyteArray NativeCrypto_EC_GROUP_get_cofactor(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_get_cofactor(%p)", group);
+
+    Unique_BIGNUM cofactor(BN_new());
+    if (cofactor.get() == NULL) {
+        JNI_TRACE("EC_GROUP_get_cofactor(%p) => can't create BN", group);
+        jniThrowOutOfMemoryError(env, "BN_new");
+        return NULL;
+    }
+
+    if (EC_GROUP_get_cofactor(group, cofactor.get(), NULL) != 1) {
+        JNI_TRACE("EC_GROUP_get_cofactor(%p) => threw error", group);
+        throwExceptionIfNecessary(env, "EC_GROUP_get_cofactor");
+        return NULL;
+    }
+
+    jbyteArray cofactorArray = bignumToArray(env, cofactor.get(), "cofactor");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+
+    JNI_TRACE("EC_GROUP_get_cofactor(%p) => %p", group, cofactorArray);
+    return cofactorArray;
+}
+
+static jint NativeCrypto_get_EC_GROUP_type(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("get_EC_GROUP_type(%p)", group);
+
+    int type = get_EC_GROUP_type(group);
+    if (type == 0) {
+        JNI_TRACE("get_EC_GROUP_type(%p) => curve type", group);
+        jniThrowRuntimeException(env, "unknown curve type");
+    } else {
+        JNI_TRACE("get_EC_GROUP_type(%p) => %d", group, type);
+    }
+    return type;
+}
+
+static void NativeCrypto_EC_GROUP_clear_free(JNIEnv* env, jclass, jlong groupRef)
+{
+    EC_GROUP* group = reinterpret_cast<EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_clear_free(%p)", group);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_GROUP_clear_free => group == NULL");
+        jniThrowNullPointerException(env, "group == NULL");
+        return;
+    }
+
+    EC_GROUP_clear_free(group);
+    JNI_TRACE("EC_GROUP_clear_free(%p) => success", group);
+}
+
+static jboolean NativeCrypto_EC_GROUP_cmp(JNIEnv* env, jclass, jlong group1Ref, jlong group2Ref)
+{
+    const EC_GROUP* group1 = reinterpret_cast<const EC_GROUP*>(group1Ref);
+    const EC_GROUP* group2 = reinterpret_cast<const EC_GROUP*>(group2Ref);
+    JNI_TRACE("EC_GROUP_cmp(%p, %p)", group1, group2);
+
+    if (group1 == NULL || group2 == NULL) {
+        JNI_TRACE("EC_GROUP_cmp(%p, %p) => group1 == null || group2 == null", group1, group2);
+        jniThrowNullPointerException(env, "group1 == null || group2 == null");
+        return false;
+    }
+
+    int ret = EC_GROUP_cmp(group1, group2, (BN_CTX*)NULL);
+
+    JNI_TRACE("ECP_GROUP_cmp(%p, %p) => %d", group1, group2, ret);
+    return ret == 0;
+}
+
+static void NativeCrypto_EC_GROUP_set_generator(JNIEnv* env, jclass, jlong groupRef, jlong pointRef, jbyteArray njavaBytes, jbyteArray hjavaBytes)
+{
+    EC_GROUP* group = reinterpret_cast<EC_GROUP*>(groupRef);
+    const EC_POINT* point = reinterpret_cast<const EC_POINT*>(pointRef);
+    JNI_TRACE("EC_GROUP_set_generator(%p, %p, %p, %p)", group, point, njavaBytes, hjavaBytes);
+
+    if (group == NULL || point == NULL) {
+        JNI_TRACE("EC_GROUP_set_generator(%p, %p, %p, %p) => group == null || point == null",
+                group, point, njavaBytes, hjavaBytes);
+        jniThrowNullPointerException(env, "group == null || point == null");
+        return;
+    }
+
+    BIGNUM* nRef;
+    if (!arrayToBignum(env, njavaBytes, &nRef)) {
+        return;
+    }
+    Unique_BIGNUM n(nRef);
+
+    BIGNUM* hRef;
+    if (!arrayToBignum(env, hjavaBytes, &hRef)) {
+        return;
+    }
+    Unique_BIGNUM h(hRef);
+
+    int ret = EC_GROUP_set_generator(group, point, n.get(), h.get());
+    if (ret == 0) {
+        throwExceptionIfNecessary(env, "EC_GROUP_set_generator");
+    }
+
+    JNI_TRACE("EC_GROUP_set_generator(%p, %p, %p, %p) => %d", group, point, njavaBytes, hjavaBytes, ret);
+}
+
+static jlong NativeCrypto_EC_GROUP_get_generator(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_get_generator(%p)", group);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_POINT_get_generator(%p) => group == null", group);
+        jniThrowNullPointerException(env, "group == null");
+        return 0;
+    }
+
+    const EC_POINT* generator = EC_GROUP_get0_generator(group);
+
+    Unique_EC_POINT dup(EC_POINT_dup(generator, group));
+    if (dup.get() == NULL) {
+        JNI_TRACE("EC_GROUP_get_generator(%p) => oom error", group);
+        jniThrowOutOfMemoryError(env, "unable to dupe generator");
+        return 0;
+    }
+
+    JNI_TRACE("EC_GROUP_get_generator(%p) => %p", group, dup.get());
+    return reinterpret_cast<uintptr_t>(dup.release());
+}
+
+static jlong NativeCrypto_EC_POINT_new(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_POINT_new(%p)", group);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_POINT_new(%p) => group == null", group);
+        jniThrowNullPointerException(env, "group == null");
+        return 0;
+    }
+
+    EC_POINT* point = EC_POINT_new(group);
+    if (point == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable create an EC_POINT");
+        return 0;
+    }
+
+    return reinterpret_cast<uintptr_t>(point);
+}
+
+static void NativeCrypto_EC_POINT_clear_free(JNIEnv* env, jclass, jlong groupRef) {
+    EC_POINT* group = reinterpret_cast<EC_POINT*>(groupRef);
+    JNI_TRACE("EC_POINT_clear_free(%p)", group);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_POINT_clear_free => group == NULL");
+        jniThrowNullPointerException(env, "group == NULL");
+        return;
+    }
+
+    EC_POINT_clear_free(group);
+    JNI_TRACE("EC_POINT_clear_free(%p) => success", group);
+}
+
+static jboolean NativeCrypto_EC_POINT_cmp(JNIEnv* env, jclass, jlong groupRef, jlong point1Ref, jlong point2Ref)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    const EC_POINT* point1 = reinterpret_cast<const EC_POINT*>(point1Ref);
+    const EC_POINT* point2 = reinterpret_cast<const EC_POINT*>(point2Ref);
+    JNI_TRACE("EC_POINT_cmp(%p, %p, %p)", group, point1, point2);
+
+    if (group == NULL || point1 == NULL || point2 == NULL) {
+        JNI_TRACE("EC_POINT_cmp(%p, %p, %p) => group == null || point1 == null || point2 == null",
+                group, point1, point2);
+        jniThrowNullPointerException(env, "group == null || point1 == null || point2 == null");
+        return false;
+    }
+
+    int ret = EC_POINT_cmp(group, point1, point2, (BN_CTX*)NULL);
+
+    JNI_TRACE("ECP_GROUP_cmp(%p, %p) => %d", point1, point2, ret);
+    return ret == 0;
+}
+
+static void NativeCrypto_EC_POINT_set_affine_coordinates(JNIEnv* env, jclass,
+        jlong groupRef, jlong pointRef, jbyteArray xjavaBytes, jbyteArray yjavaBytes)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    EC_POINT* point = reinterpret_cast<EC_POINT*>(pointRef);
+    JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p)", group, point, xjavaBytes,
+            yjavaBytes);
+
+    if (group == NULL || point == NULL) {
+        JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p) => group == null || point == null",
+                group, point, xjavaBytes, yjavaBytes);
+        jniThrowNullPointerException(env, "group == null || point == null");
+        return;
+    }
+
+    BIGNUM* xRef;
+    if (!arrayToBignum(env, xjavaBytes, &xRef)) {
+        return;
+    }
+    Unique_BIGNUM x(xRef);
+
+    BIGNUM* yRef;
+    if (!arrayToBignum(env, yjavaBytes, &yRef)) {
+        return;
+    }
+    Unique_BIGNUM y(yRef);
+
+    int ret;
+    switch (get_EC_GROUP_type(group)) {
+    case EC_CURVE_GFP:
+        ret = EC_POINT_set_affine_coordinates_GFp(group, point, x.get(), y.get(), NULL);
+        break;
+    case EC_CURVE_GF2M:
+        ret = EC_POINT_set_affine_coordinates_GF2m(group, point, x.get(), y.get(), NULL);
+        break;
+    default:
+        jniThrowRuntimeException(env, "invalid curve type");
+        return;
+    }
+
+    if (ret != 1) {
+        throwExceptionIfNecessary(env, "EC_POINT_set_affine_coordinates");
+    }
+
+    JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p) => %d", group, point,
+            xjavaBytes, yjavaBytes, ret);
+}
+
+static jobjectArray NativeCrypto_EC_POINT_get_affine_coordinates(JNIEnv* env, jclass, jlong groupRef,
+        jlong pointRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    const EC_POINT* point = reinterpret_cast<const EC_POINT*>(pointRef);
+    JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p)", group, point);
+
+    Unique_BIGNUM x(BN_new());
+    Unique_BIGNUM y(BN_new());
+
+    int ret;
+    switch (get_EC_GROUP_type(group)) {
+    case EC_CURVE_GFP:
+        ret = EC_POINT_get_affine_coordinates_GFp(group, point, x.get(), y.get(), NULL);
+        break;
+    case EC_CURVE_GF2M:
+        ret = EC_POINT_get_affine_coordinates_GF2m(group, point, x.get(), y.get(), NULL);
+        break;
+    default:
+        jniThrowRuntimeException(env, "invalid curve type");
+        return NULL;
+    }
+    if (ret != 1) {
+        JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p)", group, point);
+        throwExceptionIfNecessary(env, "EC_POINT_get_affine_coordinates");
+        return NULL;
+    }
+
+    jobjectArray joa = env->NewObjectArray(2, JniConstants::byteArrayClass, NULL);
+    if (joa == NULL) {
+        return NULL;
+    }
+
+    jbyteArray xBytes = bignumToArray(env, x.get(), "x");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+    env->SetObjectArrayElement(joa, 0, xBytes);
+
+    jbyteArray yBytes = bignumToArray(env, y.get(), "y");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+    env->SetObjectArrayElement(joa, 1, yBytes);
+
+    JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p) => %p", group, point, joa);
+    return joa;
+}
+
+static jlong NativeCrypto_EC_KEY_generate_key(JNIEnv* env, jclass, jlong groupRef)
+{
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_KEY_generate_key(%p)", group);
+
+    Unique_EC_KEY eckey(EC_KEY_new());
+    if (eckey.get() == NULL) {
+        JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_new() oom", group);
+        jniThrowOutOfMemoryError(env, "Unable to create an EC_KEY");
+        return 0;
+    }
+
+    if (EC_KEY_set_group(eckey.get(), group) != 1) {
+        JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_set_group error", group);
+        throwExceptionIfNecessary(env, "EC_KEY_set_group");
+        return 0;
+    }
+
+    if (EC_KEY_generate_key(eckey.get()) != 1) {
+        JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_generate_key error", group);
+        throwExceptionIfNecessary(env, "EC_KEY_set_group");
+        return 0;
+    }
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        JNI_TRACE("EC_KEY_generate_key(%p) => threw error", group);
+        throwExceptionIfNecessary(env, "EC_KEY_generate_key");
+        return 0;
+    }
+    if (EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get()) != 1) {
+        jniThrowRuntimeException(env, "EVP_PKEY_assign_EC_KEY failed");
+        return 0;
+    }
+    OWNERSHIP_TRANSFERRED(eckey);
+
+    JNI_TRACE("EC_KEY_generate_key(%p) => %p", group, pkey.get());
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static jlong NativeCrypto_EC_KEY_get0_group(JNIEnv* env, jclass, jlong pkeyRef)
+{
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EC_KEY_get0_group(%p)", pkey);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        JNI_TRACE("EC_KEY_get0_group(%p) => pkey == null", pkey);
+        return 0;
+    }
+
+    if (EVP_PKEY_type(pkey->type) != EVP_PKEY_EC) {
+        jniThrowRuntimeException(env, "not EC key");
+        JNI_TRACE("EC_KEY_get0_group(%p) => not EC key (type == %d)", pkey,
+                EVP_PKEY_type(pkey->type));
+        return 0;
+    }
+
+    const EC_GROUP* group = EC_KEY_get0_group(pkey->pkey.ec);
+    JNI_TRACE("EC_KEY_get0_group(%p) => %p", pkey, group);
+    return reinterpret_cast<uintptr_t>(group);
+}
+
+static jbyteArray NativeCrypto_EC_KEY_get_private_key(JNIEnv* env, jclass, jlong pkeyRef)
+{
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EC_KEY_get_private_key(%p)", pkey);
+
+    Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(pkey));
+    if (eckey.get() == NULL) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY");
+        return NULL;
+    }
+
+    const BIGNUM *privkey = EC_KEY_get0_private_key(eckey.get());
+
+    jbyteArray privBytes = bignumToArray(env, privkey, "privkey");
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("EC_KEY_get_private_key(%p) => threw error", pkey);
+        return NULL;
+    }
+
+    JNI_TRACE("EC_KEY_get_private_key(%p) => %p", pkey, privBytes);
+    return privBytes;
+}
+
+static jlong NativeCrypto_EC_KEY_get_public_key(JNIEnv* env, jclass, jlong pkeyRef)
+{
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EC_KEY_get_public_key(%p)", pkey);
+
+    Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(pkey));
+    if (eckey.get() == NULL) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY");
+        return 0;
+    }
+
+    Unique_EC_POINT dup(EC_POINT_dup(EC_KEY_get0_public_key(eckey.get()),
+            EC_KEY_get0_group(eckey.get())));
+    if (dup.get() == NULL) {
+        JNI_TRACE("EC_KEY_get_public_key(%p) => can't dup public key", pkey);
+        jniThrowRuntimeException(env, "EC_POINT_dup");
+        return 0;
+    }
+
+    JNI_TRACE("EC_KEY_get_public_key(%p) => %p", pkey, dup.get());
+    return reinterpret_cast<uintptr_t>(dup.release());
+}
+
+static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass,
+     jbyteArray outArray, jint outOffset, jlong pubkeyRef, jlong privkeyRef)
+{
+    EVP_PKEY* pubPkey = reinterpret_cast<EVP_PKEY*>(pubkeyRef);
+    EVP_PKEY* privPkey = reinterpret_cast<EVP_PKEY*>(privkeyRef);
+    JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p)", outArray, outOffset, pubPkey, privPkey);
+
+    ScopedByteArrayRW out(env, outArray);
+    if (out.get() == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) can't get output buffer",
+                outArray, outOffset, pubPkey, privPkey);
+        return -1;
+    }
+
+    if ((outOffset < 0) || ((size_t) outOffset >= out.size())) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return -1;
+    }
+
+    Unique_EC_KEY pubkey(EVP_PKEY_get1_EC_KEY(pubPkey));
+    if (pubkey.get() == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey);
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+        return -1;
+    }
+
+    const EC_POINT* pubkeyPoint = EC_KEY_get0_public_key(pubkey.get());
+    if (pubkeyPoint == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p) => can't get public key point", pubPkey);
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+        return -1;
+    }
+
+    Unique_EC_KEY privkey(EVP_PKEY_get1_EC_KEY(privPkey));
+    if (privkey.get() == NULL) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY private");
+        return -1;
+    }
+
+    int outputLength = ECDH_compute_key(
+            &out[outOffset],
+            out.size() - outOffset,
+            pubkeyPoint,
+            privkey.get(),
+            NULL // No KDF
+            );
+    if (outputLength == -1) {
+        throwExceptionIfNecessary(env, "ECDH_compute_key");
+        return -1;
+    }
+
+    return outputLength;
+}
+
+static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
+    JNI_TRACE("EVP_MD_CTX_create()");
+
+    Unique_EVP_MD_CTX ctx(EVP_MD_CTX_create());
+    if (ctx.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable create a EVP_MD_CTX");
+        return 0;
+    }
+
+    JNI_TRACE("EVP_MD_CTX_create() => %p", ctx.get());
+    return reinterpret_cast<uintptr_t>(ctx.release());
+}
+
+static void NativeCrypto_EVP_MD_CTX_init(JNIEnv*, jclass, jlong ctxRef) {
+    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
+    JNI_TRACE("NativeCrypto_EVP_MD_CTX_init(%p)", ctx);
+
+    if (ctx != NULL) {
+        EVP_MD_CTX_init(ctx);
+    }
+}
+
+static void NativeCrypto_EVP_MD_CTX_destroy(JNIEnv*, jclass, jlong ctxRef) {
+    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     JNI_TRACE("NativeCrypto_EVP_MD_CTX_destroy(%p)", ctx);
 
     if (ctx != NULL) {
@@ -1445,14 +2837,12 @@
     }
 }
 
-/*
- * public static native int EVP_MD_CTX_copy(int)
- */
-static jint NativeCrypto_EVP_MD_CTX_copy(JNIEnv* env, jclass, EVP_MD_CTX* ctx) {
+static jlong NativeCrypto_EVP_MD_CTX_copy(JNIEnv* env, jclass, jlong ctxRef) {
+    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     JNI_TRACE("NativeCrypto_EVP_MD_CTX_copy(%p)", ctx);
 
     if (ctx == NULL) {
-        jniThrowNullPointerException(env, NULL);
+        jniThrowNullPointerException(env, "ctx == null");
         return 0;
     }
 
@@ -1467,23 +2857,24 @@
     if (result == 0) {
         EVP_MD_CTX_destroy(copy);
         jniThrowRuntimeException(env, "Unable to copy EVP_MD_CTX");
+        freeOpenSslErrorState();
         return 0;
     }
 
     JNI_TRACE("NativeCrypto_EVP_MD_CTX_copy(%p) => %p", ctx, copy);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(copy));
+    return reinterpret_cast<uintptr_t>(copy);
 }
 
 /*
  * public static native int EVP_DigestFinal(int, byte[], int)
  */
-static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass, jint ctxRef,
+static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass, jlong ctxRef,
                                          jbyteArray hash, jint offset) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     JNI_TRACE("NativeCrypto_EVP_DigestFinal(%p, %p, %d)", ctx, hash, offset);
 
     if (ctx == NULL || hash == NULL) {
-        jniThrowNullPointerException(env, NULL);
+        jniThrowNullPointerException(env, "ctx == null || hash == null");
         return -1;
     }
 
@@ -1507,7 +2898,7 @@
 /*
  * public static native int EVP_DigestInit(int)
  */
-static jint NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass, jint evpMdRef) {
+static jlong NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass, jlong evpMdRef) {
     EVP_MD* evp_md = reinterpret_cast<EVP_MD*>(evpMdRef);
     JNI_TRACE("NativeCrypto_EVP_DigestInit(%p)", evp_md);
 
@@ -1530,13 +2921,13 @@
             return 0;
         }
     }
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(ctx.release()));
+    return reinterpret_cast<uintptr_t>(ctx.release());
 }
 
 /*
  * public static native int EVP_get_digestbyname(java.lang.String)
  */
-static jint NativeCrypto_EVP_get_digestbyname(JNIEnv* env, jclass, jstring algorithm) {
+static jlong NativeCrypto_EVP_get_digestbyname(JNIEnv* env, jclass, jstring algorithm) {
     JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%p)", algorithm);
 
     if (algorithm == NULL) {
@@ -1557,13 +2948,14 @@
     }
 
     JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%s) => %p", algorithmChars.c_str(), evp_md);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(evp_md));
+    return reinterpret_cast<uintptr_t>(evp_md);
 }
 
 /*
  * public static native int EVP_MD_size(int)
  */
-static jint NativeCrypto_EVP_MD_size(JNIEnv* env, jclass, EVP_MD* evp_md) {
+static jint NativeCrypto_EVP_MD_size(JNIEnv* env, jclass, jint evpMdRef) {
+    EVP_MD* evp_md = reinterpret_cast<EVP_MD*>(evpMdRef);
     JNI_TRACE("NativeCrypto_EVP_MD_size(%p)", evp_md);
 
     if (evp_md == NULL) {
@@ -1579,7 +2971,8 @@
 /*
  * public static int void EVP_MD_block_size(int)
  */
-static jint NativeCrypto_EVP_MD_block_size(JNIEnv* env, jclass, EVP_MD* evp_md) {
+static jint NativeCrypto_EVP_MD_block_size(JNIEnv* env, jclass, jlong evpMdRef) {
+    EVP_MD* evp_md = reinterpret_cast<EVP_MD*>(evpMdRef);
     JNI_TRACE("NativeCrypto_EVP_MD_block_size(%p)", evp_md);
 
     if (evp_md == NULL) {
@@ -1595,7 +2988,7 @@
 /*
  * public static native void EVP_DigestUpdate(int, byte[], int, int)
  */
-static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass, jint ctxRef,
+static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass, jlong ctxRef,
                                           jbyteArray buffer, jint offset, jint length) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     JNI_TRACE("NativeCrypto_EVP_DigestUpdate(%p, %p, %d, %d)", ctx, buffer, offset, length);
@@ -1622,10 +3015,111 @@
     }
 }
 
-/*
- * public static native int EVP_SignInit(java.lang.String)
- */
-static jint NativeCrypto_EVP_SignInit(JNIEnv* env, jclass, jstring algorithm) {
+static void NativeCrypto_EVP_DigestSignInit(JNIEnv* env, jclass, jlong evpMdCtxRef,
+        const jlong evpMdRef, jlong pkeyRef) {
+    EVP_MD_CTX* mdCtx = reinterpret_cast<EVP_MD_CTX*>(evpMdCtxRef);
+    const EVP_MD* md = reinterpret_cast<const EVP_MD*>(evpMdRef);
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EVP_DigestSignInit(%p, %p, %p)", mdCtx, md, pkey);
+
+    if (mdCtx == NULL) {
+         jniThrowNullPointerException(env, "mdCtx == null");
+         return;
+    }
+
+    if (md == NULL) {
+         jniThrowNullPointerException(env, "md == null");
+         return;
+    }
+
+    if (pkey == NULL) {
+         jniThrowNullPointerException(env, "pkey == null");
+         return;
+    }
+
+    if (EVP_DigestSignInit(mdCtx, (EVP_PKEY_CTX **) NULL, md, (ENGINE *) NULL, pkey) <= 0) {
+        JNI_TRACE("ctx=%p EVP_DigestSignInit => threw exception", mdCtx);
+        throwExceptionIfNecessary(env, "EVP_DigestSignInit");
+        return;
+    }
+
+    JNI_TRACE("EVP_DigestSignInit(%p, %p, %p) => success", mdCtx, md, pkey);
+}
+
+static void NativeCrypto_EVP_DigestSignUpdate(JNIEnv* env, jclass, jint evpMdCtxRef,
+        jbyteArray inJavaBytes, jint inOffset, jint inLength)
+{
+    EVP_MD_CTX* mdCtx = reinterpret_cast<EVP_MD_CTX*>(evpMdCtxRef);
+    JNI_TRACE("EVP_DigestSignUpdate(%p, %p, %d, %d)", mdCtx, inJavaBytes, inOffset, inLength);
+
+    if (mdCtx == NULL) {
+         jniThrowNullPointerException(env, "mdCtx == null");
+         return;
+    }
+
+    ScopedByteArrayRO inBytes(env, inJavaBytes);
+    if (inBytes.get() == NULL) {
+        return;
+    }
+
+    if (inOffset < 0 || size_t(inOffset) > inBytes.size()) {
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "inOffset");
+        return;
+    }
+
+    const ssize_t inEnd = inOffset + inLength;
+    if (inEnd < 0 || size_t(inEnd) >= inBytes.size()) {
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "inLength");
+        return;
+    }
+
+    const unsigned char *tmp = reinterpret_cast<const unsigned char *>(inBytes.get());
+    if (!EVP_DigestSignUpdate(mdCtx, tmp + inOffset, inLength)) {
+        JNI_TRACE("ctx=%p EVP_DigestSignUpdate => threw exception", mdCtx);
+        throwExceptionIfNecessary(env, "EVP_DigestSignUpdate");
+    }
+
+    JNI_TRACE("EVP_DigestSignUpdate(%p, %p, %d, %d) => success", mdCtx, inJavaBytes, inOffset,
+            inLength);
+}
+
+static jbyteArray NativeCrypto_EVP_DigestSignFinal(JNIEnv* env, jclass, jlong evpMdCtxRef)
+{
+    EVP_MD_CTX* mdCtx = reinterpret_cast<EVP_MD_CTX*>(evpMdCtxRef);
+    JNI_TRACE("EVP_DigestSignFinal(%p)", mdCtx);
+
+    if (mdCtx == NULL) {
+         jniThrowNullPointerException(env, "mdCtx == null");
+         return NULL;
+    }
+
+    const size_t expectedSize = EVP_MD_CTX_size(mdCtx);
+    ScopedLocalRef<jbyteArray> outJavaBytes(env, env->NewByteArray(expectedSize));
+    if (outJavaBytes.get() == NULL) {
+        return NULL;
+    }
+    ScopedByteArrayRW outBytes(env, outJavaBytes.get());
+    if (outBytes.get() == NULL) {
+        return NULL;
+    }
+    unsigned char *tmp = reinterpret_cast<unsigned char*>(outBytes.get());
+    size_t len;
+    if (!EVP_DigestSignFinal(mdCtx, tmp, &len)) {
+        JNI_TRACE("ctx=%p EVP_DigestSignFinal => threw exception", mdCtx);
+        throwExceptionIfNecessary(env, "EVP_DigestSignFinal");
+        return 0;
+    }
+
+    if (len != expectedSize) {
+        jniThrowRuntimeException(env, "hash size unexpected");
+        return 0;
+    }
+
+    JNI_TRACE("EVP_DigestSignFinal(%p) => %p", mdCtx, outJavaBytes.get());
+    return outJavaBytes.release();
+}
+
+static jlong NativeCrypto_EVP_SignInit(JNIEnv* env, jclass, jstring algorithm) {
     JNI_TRACE("NativeCrypto_EVP_SignInit(%p)", algorithm);
 
     if (algorithm == NULL) {
@@ -1648,7 +3142,8 @@
 
     const EVP_MD* digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars.c_str()));
     if (digest == NULL) {
-        jniThrowRuntimeException(env, "Hash algorithm not found");
+        JNI_TRACE("NativeCrypto_EVP_SignInit(%s) => hash not found", algorithmChars.c_str());
+        throwExceptionIfNecessary(env, "Hash algorithm not found");
         return 0;
     }
 
@@ -1656,16 +3151,19 @@
     if (ok == 0) {
         bool exception = throwExceptionIfNecessary(env, "NativeCrypto_EVP_SignInit");
         if (exception) {
+            JNI_TRACE("NativeCrypto_EVP_SignInit(%s) => threw exception", algorithmChars.c_str());
             return 0;
         }
     }
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(ctx.release()));
+
+    JNI_TRACE("NativeCrypto_EVP_SignInit(%s) => %p", algorithmChars.c_str(), ctx.get());
+    return reinterpret_cast<uintptr_t>(ctx.release());
 }
 
 /*
  * public static native void EVP_SignUpdate(int, byte[], int, int)
  */
-static void NativeCrypto_EVP_SignUpdate(JNIEnv* env, jclass, jint ctxRef,
+static void NativeCrypto_EVP_SignUpdate(JNIEnv* env, jclass, jlong ctxRef,
                                           jbyteArray buffer, jint offset, jint length) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     JNI_TRACE("NativeCrypto_EVP_SignUpdate(%p, %p, %d, %d)", ctx, buffer, offset, length);
@@ -1690,8 +3188,8 @@
 /*
  * public static native int EVP_SignFinal(int, byte[], int, int)
  */
-static jint NativeCrypto_EVP_SignFinal(JNIEnv* env, jclass, jint ctxRef, jbyteArray signature,
-        jint offset, jint pkeyRef) {
+static jint NativeCrypto_EVP_SignFinal(JNIEnv* env, jclass, jlong ctxRef, jbyteArray signature,
+        jint offset, jlong pkeyRef) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("NativeCrypto_EVP_SignFinal(%p, %p, %d, %p)", ctx, signature, offset, pkey);
@@ -1722,7 +3220,7 @@
 /*
  * public static native int EVP_VerifyInit(java.lang.String)
  */
-static jint NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass, jstring algorithm) {
+static jlong NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass, jstring algorithm) {
     JNI_TRACE("NativeCrypto_EVP_VerifyInit(%p)", algorithm);
 
     if (algorithm == NULL) {
@@ -1756,13 +3254,13 @@
             return 0;
         }
     }
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(ctx.release()));
+    return reinterpret_cast<uintptr_t>(ctx.release());
 }
 
 /*
  * public static native void EVP_VerifyUpdate(int, byte[], int, int)
  */
-static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass, jint ctxRef,
+static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass, jlong ctxRef,
                                           jbyteArray buffer, jint offset, jint length) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     JNI_TRACE("NativeCrypto_EVP_VerifyUpdate(%p, %p, %d, %d)", ctx, buffer, offset, length);
@@ -1787,8 +3285,8 @@
 /*
  * public static native int EVP_VerifyFinal(int, byte[], int, int, int)
  */
-static jint NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass, jint ctxRef, jbyteArray buffer,
-                                        jint offset, jint length, jint pkeyRef) {
+static jint NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass, jlong ctxRef, jbyteArray buffer,
+                                        jint offset, jint length, jlong pkeyRef) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("NativeCrypto_EVP_VerifyFinal(%p, %p, %d, %d, %p)",
@@ -1823,10 +3321,7 @@
     return ok;
 }
 
-/*
- * public static native int EVP_get_cipherbyname(java.lang.String)
- */
-static jint NativeCrypto_EVP_get_cipherbyname(JNIEnv* env, jclass, jstring algorithm) {
+static jlong NativeCrypto_EVP_get_cipherbyname(JNIEnv* env, jclass, jstring algorithm) {
     JNI_TRACE("EVP_get_cipherbyname(%p)", algorithm);
     if (algorithm == NULL) {
         JNI_TRACE("EVP_get_cipherbyname(%p) => threw exception algorithm == null", algorithm);
@@ -1842,26 +3337,36 @@
 
     const EVP_CIPHER* evp_cipher = EVP_get_cipherbyname(algorithmChars.c_str());
     if (evp_cipher == NULL) {
-        jniThrowRuntimeException(env, "Cipher algorithm not found");
-        return 0;
+        freeOpenSslErrorState();
     }
 
     JNI_TRACE("EVP_get_cipherbyname(%s) => %p", algorithmChars.c_str(), evp_cipher);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(evp_cipher));
+    return reinterpret_cast<uintptr_t>(evp_cipher);
 }
 
-/*
- * public static native int EVP_CipherInit_ex(int cipherNid, byte[] key, byte[] iv,
- *          boolean encrypting);
- */
-static jint NativeCrypto_EVP_CipherInit_ex(JNIEnv* env, jclass, jint cipherRef, jbyteArray keyArray,
-        jbyteArray ivArray, jboolean encrypting) {
-    const EVP_CIPHER* evp_cipher = reinterpret_cast<const EVP_CIPHER*>(cipherRef);
-    JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %d)", evp_cipher, keyArray, ivArray, encrypting ? 1 : 0);
+static void NativeCrypto_EVP_CipherInit_ex(JNIEnv* env, jclass, jlong ctxRef, jlong evpCipherRef,
+        jbyteArray keyArray, jbyteArray ivArray, jboolean encrypting) {
+    EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
+    const EVP_CIPHER* evpCipher = reinterpret_cast<const EVP_CIPHER*>(evpCipherRef);
+    JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %p, %d)", ctx, evpCipher, keyArray, ivArray,
+            encrypting ? 1 : 0);
 
-    ScopedByteArrayRO keyBytes(env, keyArray);
-    if (keyBytes.get() == NULL) {
-        return 0;
+    if (ctx == NULL) {
+        jniThrowNullPointerException(env, "ctx == null");
+        JNI_TRACE("EVP_CipherUpdate => ctx == null");
+        return;
+    }
+
+    // The key can be null if we need to set extra parameters.
+    UniquePtr<unsigned char[]> keyPtr;
+    if (keyArray != NULL) {
+        ScopedByteArrayRO keyBytes(env, keyArray);
+        if (keyBytes.get() == NULL) {
+            return;
+        }
+
+        keyPtr.reset(new unsigned char[keyBytes.size()]);
+        memcpy(keyPtr.get(), keyBytes.get(), keyBytes.size());
     }
 
     // The IV can be null if we're using ECB.
@@ -1869,38 +3374,29 @@
     if (ivArray != NULL) {
         ScopedByteArrayRO ivBytes(env, ivArray);
         if (ivBytes.get() == NULL) {
-            return 0;
+            return;
         }
 
         ivPtr.reset(new unsigned char[ivBytes.size()]);
         memcpy(ivPtr.get(), ivBytes.get(), ivBytes.size());
     }
 
-    Unique_EVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new());
-    if (ctx.get() == NULL) {
-        jniThrowOutOfMemoryError(env, "Unable to allocate cipher context");
-        JNI_TRACE("ctx=%p EVP_CipherInit_ex => context allocation error", evp_cipher);
-        return 0;
-    }
-
-    const unsigned char* key = reinterpret_cast<const unsigned char*>(keyBytes.get());
-    if (!EVP_CipherInit_ex(ctx.get(), evp_cipher, NULL, key, ivPtr.get(), encrypting ? 1 : 0)) {
+    if (!EVP_CipherInit_ex(ctx, evpCipher, NULL, keyPtr.get(), ivPtr.get(), encrypting ? 1 : 0)) {
         throwExceptionIfNecessary(env, "EVP_CipherInit_ex");
         JNI_TRACE("EVP_CipherInit_ex => error initializing cipher");
-        return 0;
+        return;
     }
 
-    JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %d) => %p", evp_cipher, keyArray, ivArray,
-            encrypting ? 1 : 0, ctx.get());
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(ctx.release()));
+    JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %p, %d) => success", ctx, evpCipher, keyArray, ivArray,
+            encrypting ? 1 : 0);
 }
 
 /*
  *  public static native int EVP_CipherUpdate(int ctx, byte[] out, int outOffset, byte[] in,
  *          int inOffset);
  */
-static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jint ctxRef, jbyteArray outArray,
-        jint outOffset, jbyteArray inArray, jint inOffset) {
+static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jlong ctxRef, jbyteArray outArray,
+        jint outOffset, jbyteArray inArray, jint inOffset, jint inLength) {
     EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
     JNI_TRACE("EVP_CipherUpdate(%p, %p, %d, %p, %d)", ctx, outArray, outOffset, inArray, inOffset);
 
@@ -1915,8 +3411,9 @@
         return 0;
     }
     const size_t inSize = inBytes.size();
-    if (size_t(inOffset + EVP_CIPHER_CTX_block_size(ctx)) > inSize) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL);
+    if (size_t(inOffset + inLength) > inSize) {
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException",
+                "in.length < (inSize + inOffset)");
         return 0;
     }
 
@@ -1925,17 +3422,21 @@
         return 0;
     }
     const size_t outSize = outBytes.size();
-    if (size_t(outOffset + EVP_CIPHER_CTX_block_size(ctx)) > outSize) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL);
+    if (size_t(outOffset + inLength) > outSize) {
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException",
+                "out.length < inSize + outOffset + blockSize - 1");
         return 0;
     }
 
+    JNI_TRACE("ctx=%p EVP_CipherUpdate in=%p in.length=%d inOffset=%d inLength=%d out=%p out.length=%d outOffset=%d",
+            ctx, inBytes.get(), inBytes.size(), inOffset, inLength, outBytes.get(), outBytes.size(), outOffset);
+
     unsigned char* out = reinterpret_cast<unsigned char*>(outBytes.get());
     const unsigned char* in = reinterpret_cast<const unsigned char*>(inBytes.get());
 
     int outl;
-    if (!EVP_CipherUpdate(ctx, out + outOffset, &outl, in+inOffset, inSize - inOffset)) {
-        throwExceptionIfNecessary(env, "EVP_CipherInit_ex");
+    if (!EVP_CipherUpdate(ctx, out + outOffset, &outl, in + inOffset, inLength)) {
+        throwExceptionIfNecessary(env, "EVP_CipherUpdate");
         JNI_TRACE("ctx=%p EVP_CipherUpdate => threw error", ctx);
         return 0;
     }
@@ -1945,10 +3446,7 @@
     return outl;
 }
 
-/*
- *  public static native int EVP_CipherFinal(int ctx, byte[] out, int outOffset);
- */
-static jint NativeCrypto_EVP_CipherFinal_ex(JNIEnv* env, jclass, jint ctxRef, jbyteArray outArray,
+static jint NativeCrypto_EVP_CipherFinal_ex(JNIEnv* env, jclass, jlong ctxRef, jbyteArray outArray,
         jint outOffset) {
     EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
     JNI_TRACE("EVP_CipherFinal_ex(%p, %p, %d)", ctx, outArray, outOffset);
@@ -1968,8 +3466,8 @@
 
     int outl;
     if (!EVP_CipherFinal_ex(ctx, out + outOffset, &outl)) {
-        throwExceptionIfNecessary(env, "EVP_CipherInit_ex");
-        JNI_TRACE("ctx=%p EVP_CipherUpdate => threw error", ctx);
+        throwExceptionIfNecessary(env, "EVP_CipherFinal_ex");
+        JNI_TRACE("ctx=%p EVP_CipherFinal_ex => threw error", ctx);
         return 0;
     }
 
@@ -1977,15 +3475,103 @@
     return outl;
 }
 
-/*
- * public static native void EVP_CIPHER_CTX_cleanup(int ctx);
- */
-static void NativeCrypto_EVP_CIPHER_CTX_cleanup(JNIEnv*, jclass, jint ctxRef) {
+static jint NativeCrypto_EVP_CIPHER_iv_length(JNIEnv* env, jclass, jlong evpCipherRef) {
+    const EVP_CIPHER* evpCipher = reinterpret_cast<const EVP_CIPHER*>(evpCipherRef);
+    JNI_TRACE("EVP_CIPHER_iv_length(%p)", evpCipher);
+
+    if (evpCipher == NULL) {
+        jniThrowNullPointerException(env, "evpCipher == null");
+        JNI_TRACE("EVP_CIPHER_iv_length => evpCipher == null");
+        return 0;
+    }
+
+    const int ivLength = EVP_CIPHER_iv_length(evpCipher);
+    JNI_TRACE("EVP_CIPHER_iv_length(%p) => %d", evpCipher, ivLength);
+    return ivLength;
+}
+
+static jlong NativeCrypto_EVP_CIPHER_CTX_new(JNIEnv* env, jclass) {
+    JNI_TRACE("EVP_CIPHER_CTX_new()");
+
+    Unique_EVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new());
+    if (ctx.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable to allocate cipher context");
+        JNI_TRACE("EVP_CipherInit_ex => context allocation error");
+        return 0;
+    }
+
+    JNI_TRACE("EVP_CIPHER_CTX_new() => %p", ctx.get());
+    return reinterpret_cast<uintptr_t>(ctx.release());
+}
+
+static jint NativeCrypto_EVP_CIPHER_CTX_block_size(JNIEnv* env, jclass, jlong ctxRef) {
     EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
+    JNI_TRACE("EVP_CIPHER_CTX_block_size(%p)", ctx);
+
+    if (ctx == NULL) {
+        jniThrowNullPointerException(env, "ctx == null");
+        JNI_TRACE("ctx=%p EVP_CIPHER_CTX_block_size => ctx == null", ctx);
+        return 0;
+    }
+
+    int blockSize = EVP_CIPHER_CTX_block_size(ctx);
+    JNI_TRACE("EVP_CIPHER_CTX_block_size(%p) => %d", ctx, blockSize);
+    return blockSize;
+}
+
+static jint NativeCrypto_get_EVP_CIPHER_CTX_buf_len(JNIEnv* env, jclass, jlong ctxRef) {
+    EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
+    JNI_TRACE("get_EVP_CIPHER_CTX_buf_len(%p)", ctx);
+
+    if (ctx == NULL) {
+        jniThrowNullPointerException(env, "ctx == null");
+        JNI_TRACE("ctx=%p get_EVP_CIPHER_CTX_buf_len => ctx == null", ctx);
+        return 0;
+    }
+
+    int buf_len = ctx->buf_len;
+    JNI_TRACE("get_EVP_CIPHER_CTX_buf_len(%p) => %d", ctx, buf_len);
+    return buf_len;
+}
+
+static void NativeCrypto_EVP_CIPHER_CTX_set_padding(JNIEnv* env, jclass, jlong ctxRef, jboolean enablePaddingBool) {
+    EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
+    jint enablePadding = enablePaddingBool ? 1 : 0;
+    JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d)", ctx, enablePadding);
+
+    if (ctx == NULL) {
+        jniThrowNullPointerException(env, "ctx == null");
+        JNI_TRACE("ctx=%p EVP_CIPHER_CTX_set_padding => ctx == null", ctx);
+        return;
+    }
+
+    EVP_CIPHER_CTX_set_padding(ctx, enablePadding);
+    JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d) => success", ctx, enablePadding);
+}
+
+static void NativeCrypto_EVP_CIPHER_CTX_set_key_length(JNIEnv* env, jclass, jlong ctxRef,
+        jint keySizeBits) {
+    EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
+    JNI_TRACE("EVP_CIPHER_CTX_set_key_length(%p, %d)", ctx, keySizeBits);
+
+    if (ctx == NULL) {
+        jniThrowNullPointerException(env, "ctx == null");
+        JNI_TRACE("ctx=%p EVP_CIPHER_CTX_set_key_length => ctx == null", ctx);
+        return;
+    }
+
+    EVP_CIPHER_CTX_set_key_length(ctx, keySizeBits);
+    JNI_TRACE("EVP_CIPHER_CTX_set_key_length(%p, %d) => success", ctx, keySizeBits);
+}
+
+static void NativeCrypto_EVP_CIPHER_CTX_cleanup(JNIEnv*, jclass, jlong ctxRef) {
+    EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxRef);
+    JNI_TRACE("EVP_CIPHER_CTX_cleanup(%p)", ctx);
 
     if (ctx != NULL) {
         EVP_CIPHER_CTX_cleanup(ctx);
     }
+    JNI_TRACE("EVP_CIPHER_CTX_cleanup(%p) => success", ctx);
 }
 
 /**
@@ -2029,6 +3615,1664 @@
     JNI_TRACE("NativeCrypto_RAND_bytes(%p) => success", output);
 }
 
+static jint NativeCrypto_OBJ_txt2nid(JNIEnv* env, jclass, jstring oidStr) {
+    JNI_TRACE("OBJ_txt2nid(%p)", oidStr);
+
+    ScopedUtfChars oid(env, oidStr);
+    if (oid.c_str() == NULL) {
+        return 0;
+    }
+
+    int nid = OBJ_txt2nid(oid.c_str());
+    JNI_TRACE("OBJ_txt2nid(%s) => %d", oid.c_str(), nid);
+    return nid;
+}
+
+static jstring NativeCrypto_OBJ_txt2nid_longName(JNIEnv* env, jclass, jstring oidStr) {
+    JNI_TRACE("OBJ_txt2nid_longName(%p)", oidStr);
+
+    ScopedUtfChars oid(env, oidStr);
+    if (oid.c_str() == NULL) {
+        return NULL;
+    }
+
+    JNI_TRACE("OBJ_txt2nid_longName(%s)", oid.c_str());
+
+    int nid = OBJ_txt2nid(oid.c_str());
+    if (nid == NID_undef) {
+        JNI_TRACE("OBJ_txt2nid_longName(%s) => NID_undef", oid.c_str());
+        freeOpenSslErrorState();
+        return NULL;
+    }
+
+    const char* longName = OBJ_nid2ln(nid);
+    JNI_TRACE("OBJ_txt2nid_longName(%s) => %s", oid.c_str(), longName);
+    return env->NewStringUTF(longName);
+}
+
+static jstring ASN1_OBJECT_to_OID_string(JNIEnv* env, ASN1_OBJECT* obj) {
+    /*
+     * The OBJ_obj2txt API doesn't "measure" if you pass in NULL as the buffer.
+     * Just make a buffer that's large enough here. The documentation recommends
+     * 80 characters.
+     */
+    char output[128];
+    int ret = OBJ_obj2txt(output, sizeof(output), obj, 1);
+    if (ret < 0) {
+        throwExceptionIfNecessary(env, "ASN1_OBJECT_to_OID_string");
+        return NULL;
+    } else if (size_t(ret) >= sizeof(output)) {
+        jniThrowRuntimeException(env, "ASN1_OBJECT_to_OID_string buffer too small");
+        return NULL;
+    }
+
+    JNI_TRACE("ASN1_OBJECT_to_OID_string(%p) => %s", obj, output);
+    return env->NewStringUTF(output);
+}
+
+static jlong NativeCrypto_create_BIO_InputStream(JNIEnv* env, jclass, jobject streamObj) {
+    JNI_TRACE("create_BIO_InputStream(%p)", streamObj);
+
+    if (streamObj == NULL) {
+        jniThrowNullPointerException(env, "stream == null");
+        return 0;
+    }
+
+    Unique_BIO bio(BIO_new(&stream_bio_method));
+    if (bio.get() == NULL) {
+        return 0;
+    }
+
+    bio_stream_assign(bio.get(), new BIO_InputStream(streamObj));
+
+    JNI_TRACE("create_BIO_InputStream(%p) => %p", streamObj, bio.get());
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(bio.release()));
+}
+
+static jlong NativeCrypto_create_BIO_OutputStream(JNIEnv* env, jclass, jobject streamObj) {
+    JNI_TRACE("create_BIO_OutputStream(%p)", streamObj);
+
+    if (streamObj == NULL) {
+        jniThrowNullPointerException(env, "stream == null");
+        return 0;
+    }
+
+    Unique_BIO bio(BIO_new(&stream_bio_method));
+    if (bio.get() == NULL) {
+        return 0;
+    }
+
+    bio_stream_assign(bio.get(), new BIO_OutputStream(streamObj));
+
+    JNI_TRACE("create_BIO_OutputStream(%p) => %p", streamObj, bio.get());
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(bio.release()));
+}
+
+static int NativeCrypto_BIO_read(JNIEnv* env, jclass, jlong bioRef, jbyteArray outputJavaBytes) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("BIO_read(%p, %p)", bio, outputJavaBytes);
+
+    if (outputJavaBytes == NULL) {
+        jniThrowNullPointerException(env, "output == null");
+        JNI_TRACE("BIO_read(%p, %p) => output == null", bio, outputJavaBytes);
+        return 0;
+    }
+
+    int outputSize = env->GetArrayLength(outputJavaBytes);
+
+    UniquePtr<unsigned char[]> buffer(new unsigned char[outputSize]);
+    if (buffer.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable to allocate buffer for read");
+        return 0;
+    }
+
+    int read = BIO_read(bio, buffer.get(), outputSize);
+    if (read <= 0) {
+        jniThrowException(env, "java/io/IOException", "BIO_read");
+        JNI_TRACE("BIO_read(%p, %p) => threw IO exception", bio, outputJavaBytes);
+        return 0;
+    }
+
+    env->SetByteArrayRegion(outputJavaBytes, 0, read, reinterpret_cast<jbyte*>(buffer.get()));
+    JNI_TRACE("BIO_read(%p, %p) => %d", bio, outputJavaBytes, read);
+    return read;
+}
+
+static void NativeCrypto_BIO_write(JNIEnv* env, jclass, jlong bioRef, jbyteArray inputJavaBytes,
+        jint offset, jint length) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("BIO_write(%p, %p, %d, %d)", bio, inputJavaBytes, offset, length);
+
+    if (inputJavaBytes == NULL) {
+        jniThrowNullPointerException(env, "input == null");
+        return;
+    }
+
+    if (offset < 0 || length < 0) {
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "offset < 0 || length < 0");
+        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IOOB", bio, inputJavaBytes, offset, length);
+        return;
+    }
+
+    int inputSize = env->GetArrayLength(inputJavaBytes);
+    if (inputSize < offset + length) {
+        jniThrowException(env, "java/lang/IndexOutOfBoundsException",
+                "input.length < offset + length");
+        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IOOB", bio, inputJavaBytes, offset, length);
+        return;
+    }
+
+    UniquePtr<unsigned char[]> buffer(new unsigned char[length]);
+    if (buffer.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable to allocate buffer for write");
+        return;
+    }
+
+    env->GetByteArrayRegion(inputJavaBytes, offset, length, reinterpret_cast<jbyte*>(buffer.get()));
+    if (BIO_write(bio, buffer.get(), length) != 1) {
+        freeOpenSslErrorState();
+        jniThrowException(env, "java/io/IOException", "BIO_write");
+        JNI_TRACE("BIO_write(%p, %p, %d, %d) => IO error", bio, inputJavaBytes, offset, length);
+        return;
+    }
+
+    JNI_TRACE("BIO_write(%p, %p, %d, %d) => success", bio, inputJavaBytes, offset, length);
+}
+
+static void NativeCrypto_BIO_free(JNIEnv* env, jclass, jlong bioRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("BIO_free(%p)", bio);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        return;
+    }
+
+    BIO_free(bio);
+}
+
+static jstring X509_NAME_to_jstring(JNIEnv* env, X509_NAME* name, unsigned long flags) {
+    JNI_TRACE("X509_NAME_to_jstring(%p)", name);
+
+    Unique_BIO buffer(BIO_new(BIO_s_mem()));
+    if (buffer.get() == NULL) {
+        jniThrowOutOfMemoryError(env, "Unable to allocate BIO");
+        JNI_TRACE("X509_NAME_to_jstring(%p) => threw error", name);
+        return NULL;
+    }
+
+    /* Don't interpret the string. */
+    flags &= ~(ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_ESC_MSB);
+
+    /* Write in given format and null terminate. */
+    X509_NAME_print_ex(buffer.get(), name, 0, flags);
+    BIO_write(buffer.get(), "\0", 1);
+
+    char *tmp;
+    BIO_get_mem_data(buffer.get(), &tmp);
+    JNI_TRACE("X509_NAME_to_jstring(%p) => \"%s\"", name, tmp);
+    return env->NewStringUTF(tmp);
+}
+
+
+/**
+ * Converts GENERAL_NAME items to the output format expected in
+ * X509Certificate#getSubjectAlternativeNames and
+ * X509Certificate#getIssuerAlternativeNames return.
+ */
+static jobject GENERAL_NAME_to_jobject(JNIEnv* env, GENERAL_NAME* gen) {
+    switch (gen->type) {
+    case GEN_EMAIL:
+    case GEN_DNS:
+    case GEN_URI: {
+        // This must not be a T61String and must not contain NULLs.
+        const char* data = reinterpret_cast<const char*>(ASN1_STRING_data(gen->d.ia5));
+        ssize_t len = ASN1_STRING_length(gen->d.ia5);
+        if ((len == static_cast<ssize_t>(strlen(data)))
+                && (ASN1_PRINTABLE_type(ASN1_STRING_data(gen->d.ia5), len) != V_ASN1_T61STRING)) {
+            JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI \"%s\"", gen, data);
+            return env->NewStringUTF(data);
+        } else {
+            jniThrowException(env, "java/security/cert/CertificateParsingException",
+                    "Invalid dNSName encoding");
+            JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI invalid", gen);
+            return NULL;
+        }
+    }
+    case GEN_DIRNAME:
+        /* Write in RFC 2253 format */
+        return X509_NAME_to_jstring(env, gen->d.directoryName, XN_FLAG_RFC2253);
+    case GEN_IPADD: {
+        const void *ip = reinterpret_cast<const void *>(gen->d.ip->data);
+        if (gen->d.ip->length == 4) {
+            // IPv4
+            UniquePtr<char[]> buffer(new char[INET_ADDRSTRLEN]);
+            if (inet_ntop(AF_INET, ip, buffer.get(), INET_ADDRSTRLEN) != NULL) {
+                JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv4 %s", gen, buffer.get());
+                return env->NewStringUTF(buffer.get());
+            } else {
+                JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv4 failed %s", gen, strerror(errno));
+            }
+        } else if (gen->d.ip->length == 16) {
+            // IPv6
+            UniquePtr<char[]> buffer(new char[INET6_ADDRSTRLEN]);
+            if (inet_ntop(AF_INET6, ip, buffer.get(), INET6_ADDRSTRLEN) != NULL) {
+                JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv6 %s", gen, buffer.get());
+                return env->NewStringUTF(buffer.get());
+            } else {
+                JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv6 failed %s", gen, strerror(errno));
+            }
+        }
+
+        /* Invalid IP encodings are pruned out without throwing an exception. */
+        return NULL;
+    }
+    case GEN_RID:
+        return ASN1_OBJECT_to_OID_string(env, gen->d.registeredID);
+    case GEN_OTHERNAME:
+    case GEN_X400:
+    default:
+        return ASN1ToByteArray<GENERAL_NAME, i2d_GENERAL_NAME>(env, gen);
+    }
+
+    return NULL;
+}
+
+#define GN_STACK_SUBJECT_ALT_NAME 1
+#define GN_STACK_ISSUER_ALT_NAME 2
+
+static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass, jlong x509Ref,
+        jint type) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d)", x509, type);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => x509 == null", x509, type);
+        return NULL;
+    }
+
+    X509_check_ca(x509);
+
+    STACK_OF(GENERAL_NAME)* gn_stack;
+    Unique_sk_GENERAL_NAME stackHolder;
+    if (type == GN_STACK_SUBJECT_ALT_NAME) {
+        gn_stack = x509->altname;
+    } else if (type == GN_STACK_ISSUER_ALT_NAME) {
+        stackHolder.reset(
+                static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(x509, NID_issuer_alt_name,
+                        NULL, NULL)));
+        gn_stack = stackHolder.get();
+    } else {
+        JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => unknown type", x509, type);
+        return NULL;
+    }
+
+    int count = sk_GENERAL_NAME_num(gn_stack);
+    if (count <= 0) {
+        JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no entries)", x509, type);
+        return NULL;
+    }
+
+    /*
+     * Keep track of how many originally so we can ignore any invalid
+     * values later.
+     */
+    const int origCount = count;
+
+    ScopedLocalRef<jobjectArray> joa(env, env->NewObjectArray(count,
+            JniConstants::objectArrayClass, NULL));
+    for (int i = 0, j = 0; i < origCount; i++, j++) {
+        GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack, i);
+        ScopedLocalRef<jobject> val(env, GENERAL_NAME_to_jobject(env, gen));
+        if (env->ExceptionCheck()) {
+            JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen name",
+                    x509, type);
+            return NULL;
+        }
+
+        /*
+         * If it's NULL, we'll have to skip this, reduce the number of total
+         * entries, and fix up the array later.
+         */
+        if (val.get() == NULL) {
+            j--;
+            count--;
+            continue;
+        }
+
+        ScopedLocalRef<jobjectArray> item(env, env->NewObjectArray(2, JniConstants::objectClass,
+                NULL));
+
+        ScopedLocalRef<jobject> type(env, env->CallStaticObjectMethod(JniConstants::integerClass,
+                integer_valueOfMethod, gen->type));
+        env->SetObjectArrayElement(item.get(), 0, type.get());
+        env->SetObjectArrayElement(item.get(), 1, val.get());
+
+        env->SetObjectArrayElement(joa.get(), j, item.get());
+    }
+
+    if (count == 0) {
+        JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning NULL",
+                x509, type, origCount);
+        joa.reset(NULL);
+    } else if (origCount != count) {
+        JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to %d", x509, type,
+                origCount, count);
+
+        ScopedLocalRef<jobjectArray> joa_copy(env, env->NewObjectArray(count,
+                JniConstants::objectArrayClass, NULL));
+
+        for (int i = 0; i < count; i++) {
+            ScopedLocalRef<jobject> item(env, env->GetObjectArrayElement(joa.get(), i));
+            env->SetObjectArrayElement(joa_copy.get(), i, item.get());
+        }
+
+        joa.reset(joa_copy.release());
+    }
+
+    JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => %d entries", x509, type, count);
+    return joa.release();
+}
+
+static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_notBefore(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_notBefore(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    ASN1_TIME* notBefore = X509_get_notBefore(x509);
+    JNI_TRACE("X509_get_notBefore(%p) => %p", x509, notBefore);
+    return reinterpret_cast<uintptr_t>(notBefore);
+}
+
+static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_notAfter(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_notAfter(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    ASN1_TIME* notAfter = X509_get_notAfter(x509);
+    JNI_TRACE("X509_get_notAfter(%p) => %p", x509, notAfter);
+    return reinterpret_cast<uintptr_t>(notAfter);
+}
+
+static long NativeCrypto_X509_get_version(JNIEnv*, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_version(%p)", x509);
+
+    long version = X509_get_version(x509);
+    JNI_TRACE("X509_get_version(%p) => %ld", x509, version);
+    return version;
+}
+
+template<typename T>
+static jbyteArray get_X509Type_serialNumber(JNIEnv* env, T* x509Type, ASN1_INTEGER* (*get_serial_func)(T*)) {
+    JNI_TRACE("get_X509Type_serialNumber(%p)", x509Type);
+
+    if (x509Type == NULL) {
+        jniThrowNullPointerException(env, "x509Type == null");
+        JNI_TRACE("get_X509Type_serialNumber(%p) => x509Type == null", x509Type);
+        return NULL;
+    }
+
+    ASN1_INTEGER* serialNumber = get_serial_func(x509Type);
+    Unique_BIGNUM serialBn(ASN1_INTEGER_to_BN(serialNumber, NULL));
+    if (serialBn.get() == NULL) {
+        JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type);
+        return NULL;
+    }
+
+    ScopedLocalRef<jbyteArray> serialArray(env, bignumToArray(env, serialBn.get(), "serialBn"));
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type);
+        return NULL;
+    }
+
+    JNI_TRACE("X509_get_serialNumber(%p) => %p", x509Type, serialArray.get());
+    return serialArray.release();
+}
+
+/* OpenSSL includes set_serialNumber but not get. */
+#if !defined(X509_REVOKED_get_serialNumber)
+static ASN1_INTEGER* X509_REVOKED_get_serialNumber(X509_REVOKED* x) {
+    return x->serialNumber;
+}
+#endif
+
+static jbyteArray NativeCrypto_X509_get_serialNumber(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_serialNumber(%p)", x509);
+    return get_X509Type_serialNumber<X509>(env, x509, X509_get_serialNumber);
+}
+
+static jbyteArray NativeCrypto_X509_REVOKED_get_serialNumber(JNIEnv* env, jclass, jlong x509RevokedRef) {
+    X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("X509_REVOKED_get_serialNumber(%p)", revoked);
+    return get_X509Type_serialNumber<X509_REVOKED>(env, revoked, X509_REVOKED_get_serialNumber);
+}
+
+static void NativeCrypto_X509_verify(JNIEnv* env, jclass, jlong x509Ref, jlong pkeyRef) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("X509_verify(%p, %p)", x509, pkey);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_verify(%p, %p) => x509 == null", x509, pkey);
+        return;
+    }
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        JNI_TRACE("X509_verify(%p, %p) => pkey == null", x509, pkey);
+        return;
+    }
+
+    if (X509_verify(x509, pkey) != 1) {
+        throwExceptionIfNecessary(env, "X509_verify");
+        JNI_TRACE("X509_verify(%p, %p) => verify failure", x509, pkey);
+    } else {
+        JNI_TRACE("X509_verify(%p, %p) => verify success", x509, pkey);
+    }
+}
+
+static jbyteArray NativeCrypto_get_X509_cert_info_enc(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_cert_info_enc(%p)", x509);
+    return ASN1ToByteArray<X509_CINF, i2d_X509_CINF>(env, x509->cert_info);
+}
+
+static jint NativeCrypto_get_X509_ex_flags(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_ex_flags(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_ex_flags(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    X509_check_ca(x509);
+
+    return x509->ex_flags;
+}
+
+static void get_X509_signature(X509 *x509, ASN1_BIT_STRING** signature) {
+    *signature = x509->signature;
+}
+
+static void get_X509_CRL_signature(X509_CRL *crl, ASN1_BIT_STRING** signature) {
+    *signature = crl->signature;
+}
+
+template<typename T>
+static jbyteArray get_X509Type_signature(JNIEnv* env, T* x509Type, void (*get_signature_func)(T*, ASN1_BIT_STRING**)) {
+    JNI_TRACE("get_X509Type_signature(%p)", x509Type);
+
+    if (x509Type == NULL) {
+        jniThrowNullPointerException(env, "x509Type == null");
+        JNI_TRACE("get_X509Type_signature(%p) => x509Type == null", x509Type);
+        return NULL;
+    }
+
+    ASN1_BIT_STRING* signature;
+    get_signature_func(x509Type, &signature);
+
+    ScopedLocalRef<jbyteArray> signatureArray(env, env->NewByteArray(signature->length));
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("get_X509Type_signature(%p) => threw exception", x509Type);
+        return NULL;
+    }
+
+    ScopedByteArrayRW signatureBytes(env, signatureArray.get());
+    if (signatureBytes.get() == NULL) {
+        JNI_TRACE("get_X509Type_signature(%p) => using byte array failed", x509Type);
+        return NULL;
+    }
+
+    memcpy(signatureBytes.get(), signature->data, signature->length);
+
+    JNI_TRACE("get_X509Type_signature(%p) => %p (%d bytes)", x509Type, signatureArray.get(),
+            signature->length);
+    return signatureArray.release();
+}
+
+static jbyteArray NativeCrypto_get_X509_signature(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_signature(%p)", x509);
+    return get_X509Type_signature<X509>(env, x509, get_X509_signature);
+}
+
+static jbyteArray NativeCrypto_get_X509_CRL_signature(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("get_X509_CRL_signature(%p)", crl);
+    return get_X509Type_signature<X509_CRL>(env, crl, get_X509_CRL_signature);
+}
+
+static jlong NativeCrypto_X509_CRL_get0_by_cert(JNIEnv* env, jclass, jlong x509crlRef, jlong x509Ref) {
+    X509_CRL* x509crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509crlRef));
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_CRL_get0_by_cert(%p, %p)", x509crl, x509);
+
+    if (x509crl == NULL) {
+        jniThrowNullPointerException(env, "x509crl == null");
+        JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => x509crl == null", x509crl, x509);
+        return 0;
+    } else if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => x509 == null", x509crl, x509);
+        return 0;
+    }
+
+    X509_REVOKED* revoked = NULL;
+    int ret = X509_CRL_get0_by_cert(x509crl, &revoked, x509);
+    if (ret == 0) {
+        JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => none", x509crl, x509);
+        return 0;
+    }
+
+    JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => %p", x509crl, x509, revoked);
+    return reinterpret_cast<uintptr_t>(revoked);
+}
+
+static jlong NativeCrypto_X509_CRL_get0_by_serial(JNIEnv* env, jclass, jlong x509crlRef, jbyteArray serialArray) {
+    X509_CRL* x509crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509crlRef));
+    JNI_TRACE("X509_CRL_get0_by_serial(%p, %p)", x509crl, serialArray);
+
+    if (x509crl == NULL) {
+        jniThrowNullPointerException(env, "x509crl == null");
+        JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => crl == null", x509crl, serialArray);
+        return 0;
+    }
+
+    Unique_BIGNUM serialBn(BN_new());
+    if (serialBn.get() == NULL) {
+        JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN allocation failed", x509crl, serialArray);
+        return 0;
+    }
+
+    BIGNUM* serialBare = serialBn.get();
+    if (!arrayToBignum(env, serialArray, &serialBare)) {
+        if (!env->ExceptionCheck()) {
+            jniThrowNullPointerException(env, "serial == null");
+        }
+        JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN conversion failed", x509crl, serialArray);
+        return 0;
+    }
+
+    Unique_ASN1_INTEGER serialInteger(BN_to_ASN1_INTEGER(serialBn.get(), NULL));
+    if (serialInteger.get() == NULL) {
+        JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN conversion failed", x509crl, serialArray);
+        return 0;
+    }
+
+    X509_REVOKED* revoked = NULL;
+    int ret = X509_CRL_get0_by_serial(x509crl, &revoked, serialInteger.get());
+    if (ret == 0) {
+        JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => none", x509crl, serialArray);
+        return 0;
+    }
+
+    JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => %p", x509crl, serialArray, revoked);
+    return reinterpret_cast<uintptr_t>(revoked);
+}
+
+
+/* This appears to be missing from OpenSSL. */
+#if !defined(X509_REVOKED_dup)
+X509_REVOKED* X509_REVOKED_dup(X509_REVOKED* x) {
+    return reinterpret_cast<X509_REVOKED*>(ASN1_item_dup(ASN1_ITEM_rptr(X509_REVOKED), x));
+}
+#endif
+
+static jlongArray NativeCrypto_X509_CRL_get_REVOKED(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_REVOKED(%p)", crl);
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        return NULL;
+    }
+
+    STACK_OF(X509_REVOKED)* stack = X509_CRL_get_REVOKED(crl);
+    if (stack == NULL) {
+        JNI_TRACE("X509_CRL_get_REVOKED(%p) => stack is null", crl);
+        return NULL;
+    }
+
+    size_t size = sk_X509_REVOKED_num(stack);
+
+    ScopedLocalRef<jlongArray> revokedArray(env, env->NewLongArray(size));
+    ScopedLongArrayRW revoked(env, revokedArray.get());
+    for (size_t i = 0; i < size; i++) {
+        X509_REVOKED* item = reinterpret_cast<X509_REVOKED*>(sk_X509_REVOKED_value(stack, i));
+        revoked[i] = reinterpret_cast<uintptr_t>(X509_REVOKED_dup(item));
+    }
+
+    JNI_TRACE("X509_CRL_get_REVOKED(%p) => %p [size=%d]", stack, revokedArray.get(), size);
+    return revokedArray.release();
+}
+
+static jbyteArray NativeCrypto_i2d_X509_CRL(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("i2d_X509_CRL(%p)", crl);
+    return ASN1ToByteArray<X509_CRL, i2d_X509_CRL>(env, crl);
+}
+
+static void NativeCrypto_X509_CRL_free(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_free(%p)", crl);
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_free(%p) => crl == null", crl);
+        return;
+    }
+
+    X509_CRL_free(crl);
+}
+
+static void NativeCrypto_X509_CRL_print(JNIEnv* env, jclass, jlong bioRef, jlong x509CrlRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_print(%p, %p)", bio, crl);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        JNI_TRACE("X509_CRL_print(%p, %p) => bio == null", bio, crl);
+        return;
+    }
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_print(%p, %p) => crl == null", bio, crl);
+        return;
+    }
+
+    X509_CRL_print(bio, crl);
+    JNI_TRACE("X509_CRL_print(%p, %p) => success", bio, crl);
+}
+
+static jstring NativeCrypto_get_X509_CRL_sig_alg_oid(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("get_X509_CRL_sig_alg_oid(%p)", crl);
+
+    if (crl == NULL || crl->sig_alg == NULL) {
+        jniThrowNullPointerException(env, "crl == NULL || crl->sig_alg == NULL");
+        JNI_TRACE("get_X509_CRL_sig_alg_oid(%p) => crl == NULL", crl);
+        return NULL;
+    }
+
+    return ASN1_OBJECT_to_OID_string(env, crl->sig_alg->algorithm);
+}
+
+static jbyteArray NativeCrypto_get_X509_CRL_sig_alg_parameter(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p)", crl);
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p) => crl == null", crl);
+        return NULL;
+    }
+
+    if (crl->sig_alg->parameter == NULL) {
+        JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p) => null", crl);
+        return NULL;
+    }
+
+    return ASN1ToByteArray<ASN1_TYPE, i2d_ASN1_TYPE>(env, crl->sig_alg->parameter);
+}
+
+static jbyteArray NativeCrypto_X509_CRL_get_issuer_name(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_issuer_name(%p)", crl);
+    return ASN1ToByteArray<X509_NAME, i2d_X509_NAME>(env, X509_CRL_get_issuer(crl));
+}
+
+static long NativeCrypto_X509_CRL_get_version(JNIEnv*, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_version(%p)", crl);
+
+    long version = X509_CRL_get_version(crl);
+    JNI_TRACE("X509_CRL_get_version(%p) => %ld", crl, version);
+    return version;
+}
+
+template<typename T, int (*get_ext_by_OBJ_func)(T*, ASN1_OBJECT*, int),
+        X509_EXTENSION* (*get_ext_func)(T*, int)>
+static X509_EXTENSION *X509Type_get_ext(JNIEnv* env, T* x509Type, jstring oidString) {
+    JNI_TRACE("X509Type_get_ext(%p)", x509Type);
+
+    if (x509Type == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        return NULL;
+    }
+
+    ScopedUtfChars oid(env, oidString);
+    if (oid.c_str() == NULL) {
+        return NULL;
+    }
+
+    Unique_ASN1_OBJECT asn1(OBJ_txt2obj(oid.c_str(), 1));
+    if (asn1.get() == NULL) {
+        JNI_TRACE("X509Type_get_ext(%p, %s) => oid conversion failed", x509Type, oid.c_str());
+        freeOpenSslErrorState();
+        return NULL;
+    }
+
+    int extIndex = get_ext_by_OBJ_func(x509Type, asn1.get(), -1);
+    if (extIndex == -1) {
+        JNI_TRACE("X509Type_get_ext(%p, %s) => ext not found", x509Type, oid.c_str());
+        return NULL;
+    }
+
+    X509_EXTENSION* ext = get_ext_func(x509Type, extIndex);
+    JNI_TRACE("X509Type_get_ext(%p, %s) => %p", x509Type, oid.c_str(), ext);
+    return ext;
+}
+
+template<typename T, int (*get_ext_by_OBJ_func)(T*, ASN1_OBJECT*, int),
+        X509_EXTENSION* (*get_ext_func)(T*, int)>
+static jbyteArray X509Type_get_ext_oid(JNIEnv* env, T* x509Type, jstring oidString) {
+    X509_EXTENSION* ext = X509Type_get_ext<T, get_ext_by_OBJ_func, get_ext_func>(env, x509Type,
+            oidString);
+    if (ext == NULL) {
+        JNI_TRACE("X509Type_get_ext_oid(%p, %p) => fetching extension failed", x509Type, oidString);
+        return NULL;
+    }
+
+    JNI_TRACE("X509Type_get_ext_oid(%p, %p) => %p", x509Type, oidString, ext->value);
+    return ASN1ToByteArray<ASN1_OCTET_STRING, i2d_ASN1_OCTET_STRING>(env, ext->value);
+}
+
+static jint NativeCrypto_X509_CRL_get_ext(JNIEnv* env, jclass, jlong x509CrlRef, jstring oid) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_ext(%p, %p)", crl, oid);
+    X509_EXTENSION* ext = X509Type_get_ext<X509_CRL, X509_CRL_get_ext_by_OBJ, X509_CRL_get_ext>(
+            env, crl, oid);
+    JNI_TRACE("X509_CRL_get_ext(%p, %p) => %p", crl, oid, ext);
+    return reinterpret_cast<uintptr_t>(ext);
+}
+
+static jint NativeCrypto_X509_REVOKED_get_ext(JNIEnv* env, jclass, jlong x509RevokedRef,
+        jstring oid) {
+    X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("X509_REVOKED_get_ext(%p, %p)", revoked, oid);
+    X509_EXTENSION* ext = X509Type_get_ext<X509_REVOKED, X509_REVOKED_get_ext_by_OBJ,
+            X509_REVOKED_get_ext>(env, revoked, oid);
+    JNI_TRACE("X509_REVOKED_get_ext(%p, %p) => %p", revoked, oid, ext);
+    return reinterpret_cast<uintptr_t>(ext);
+}
+
+static jlong NativeCrypto_X509_REVOKED_dup(JNIEnv* env, jclass, jlong x509RevokedRef) {
+    X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("X509_REVOKED_dup(%p)", revoked);
+
+    if (revoked == NULL) {
+        jniThrowNullPointerException(env, "revoked == null");
+        JNI_TRACE("X509_REVOKED_dup(%p) => revoked == null", revoked);
+        return 0;
+    }
+
+    X509_REVOKED* dup = X509_REVOKED_dup(revoked);
+    JNI_TRACE("X509_REVOKED_dup(%p) => %p", revoked, dup);
+    return reinterpret_cast<uintptr_t>(dup);
+}
+
+static jlong NativeCrypto_get_X509_REVOKED_revocationDate(JNIEnv* env, jclass, jlong x509RevokedRef) {
+    X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("get_X509_REVOKED_revocationDate(%p)", revoked);
+
+    if (revoked == NULL) {
+        jniThrowNullPointerException(env, "revoked == null");
+        JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => revoked == null", revoked);
+        return 0;
+    }
+
+    JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked, revoked->revocationDate);
+    return reinterpret_cast<uintptr_t>(revoked->revocationDate);
+}
+
+static void NativeCrypto_X509_REVOKED_print(JNIEnv* env, jclass, jlong bioRef, jlong x509RevokedRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("X509_REVOKED_print(%p, %p)", bio, revoked);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        JNI_TRACE("X509_REVOKED_print(%p, %p) => bio == null", bio, revoked);
+        return;
+    }
+
+    if (revoked == NULL) {
+        jniThrowNullPointerException(env, "revoked == null");
+        JNI_TRACE("X509_REVOKED_print(%p, %p) => revoked == null", bio, revoked);
+        return;
+    }
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wwrite-strings"
+    BIO_printf(bio, "Serial Number: ");
+    i2a_ASN1_INTEGER(bio, revoked->serialNumber);
+    BIO_printf(bio, "\nRevocation Date: ");
+    ASN1_TIME_print(bio, revoked->revocationDate);
+    BIO_printf(bio, "\n");
+    X509V3_extensions_print(bio, "CRL entry extensions", revoked->extensions, 0, 0);
+#pragma GCC diagnostic pop
+}
+
+static jbyteArray NativeCrypto_get_X509_CRL_crl_enc(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("get_X509_CRL_crl_enc(%p)", crl);
+    return ASN1ToByteArray<X509_CRL_INFO, i2d_X509_CRL_INFO>(env, crl->crl);
+}
+
+static void NativeCrypto_X509_CRL_verify(JNIEnv* env, jclass, jlong x509CrlRef, jlong pkeyRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("X509_CRL_verify(%p, %p)", crl, pkey);
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_verify(%p, %p) => crl == null", crl, pkey);
+        return;
+    }
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        JNI_TRACE("X509_CRL_verify(%p, %p) => pkey == null", crl, pkey);
+        return;
+    }
+
+    if (X509_CRL_verify(crl, pkey) != 1) {
+        throwExceptionIfNecessary(env, "X509_CRL_verify");
+        JNI_TRACE("X509_CRL_verify(%p, %p) => verify failure", crl, pkey);
+    } else {
+        JNI_TRACE("X509_CRL_verify(%p, %p) => verify success", crl, pkey);
+    }
+}
+
+static jlong NativeCrypto_X509_CRL_get_lastUpdate(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_lastUpdate(%p)", crl);
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_lastUpdate(%p) => crl == null", crl);
+        return 0;
+    }
+
+    ASN1_TIME* lastUpdate = X509_CRL_get_lastUpdate(crl);
+    JNI_TRACE("X509_CRL_get_lastUpdate(%p) => %p", crl, lastUpdate);
+    return reinterpret_cast<uintptr_t>(lastUpdate);
+}
+
+static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x509CrlRef) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_nextUpdate(%p)", crl);
+
+    if (crl == NULL) {
+        jniThrowNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_nextUpdate(%p) => crl == null", crl);
+        return 0;
+    }
+
+    ASN1_TIME* nextUpdate = X509_CRL_get_nextUpdate(crl);
+    JNI_TRACE("X509_CRL_get_nextUpdate(%p) => %p", crl, nextUpdate);
+    return reinterpret_cast<uintptr_t>(nextUpdate);
+}
+
+static jbyteArray NativeCrypto_i2d_X509_REVOKED(JNIEnv* env, jclass, jlong x509RevokedRef) {
+    X509_REVOKED* x509Revoked =
+            reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("i2d_X509_REVOKED(%p)", x509Revoked);
+    return ASN1ToByteArray<X509_REVOKED, i2d_X509_REVOKED>(env, x509Revoked);
+}
+
+static jint NativeCrypto_X509_supported_extension(JNIEnv* env, jclass, jlong x509ExtensionRef) {
+    X509_EXTENSION* ext = reinterpret_cast<X509_EXTENSION*>(static_cast<uintptr_t>(x509ExtensionRef));
+
+    if (ext == NULL) {
+        jniThrowNullPointerException(env, "ext == NULL");
+        return 0;
+    }
+
+    return X509_supported_extension(ext);
+}
+
+static inline void get_ASN1_TIME_data(char **data, int* output, size_t len) {
+    char c = **data;
+    **data = '\0';
+    *data -= len;
+    *output = atoi(*data);
+    *(*data + len) = c;
+}
+
+static void NativeCrypto_ASN1_TIME_to_Calendar(JNIEnv* env, jclass, jlong asn1TimeRef, jobject calendar) {
+    ASN1_TIME* asn1Time = reinterpret_cast<ASN1_TIME*>(static_cast<uintptr_t>(asn1TimeRef));
+    JNI_TRACE("ASN1_TIME_to_Calendar(%p, %p)", asn1Time, calendar);
+
+    if (asn1Time == NULL) {
+        jniThrowNullPointerException(env, "asn1Time == null");
+        return;
+    }
+
+    Unique_ASN1_GENERALIZEDTIME gen(ASN1_TIME_to_generalizedtime(asn1Time, NULL));
+    if (gen.get() == NULL) {
+        jniThrowNullPointerException(env, "asn1Time == null");
+        return;
+    }
+
+    if (gen->length < 14 || gen->data == NULL) {
+        jniThrowNullPointerException(env, "gen->length < 14 || gen->data == NULL");
+        return;
+    }
+
+    int sec, min, hour, mday, mon, year;
+
+    char *p = (char*) &gen->data[14];
+
+    get_ASN1_TIME_data(&p, &sec, 2);
+    get_ASN1_TIME_data(&p, &min, 2);
+    get_ASN1_TIME_data(&p, &hour, 2);
+    get_ASN1_TIME_data(&p, &mday, 2);
+    get_ASN1_TIME_data(&p, &mon, 2);
+    get_ASN1_TIME_data(&p, &year, 4);
+
+    env->CallVoidMethod(calendar, calendar_setMethod, year, mon - 1, mday, hour, min, sec);
+}
+
+static jstring NativeCrypto_OBJ_txt2nid_oid(JNIEnv* env, jclass, jstring oidStr) {
+    JNI_TRACE("OBJ_txt2nid_oid(%p)", oidStr);
+
+    ScopedUtfChars oid(env, oidStr);
+    if (oid.c_str() == NULL) {
+        return NULL;
+    }
+
+    JNI_TRACE("OBJ_txt2nid_oid(%s)", oid.c_str());
+
+    int nid = OBJ_txt2nid(oid.c_str());
+    if (nid == NID_undef) {
+        JNI_TRACE("OBJ_txt2nid_oid(%s) => NID_undef", oid.c_str());
+        freeOpenSslErrorState();
+        return NULL;
+    }
+
+    Unique_ASN1_OBJECT obj(OBJ_nid2obj(nid));
+    if (obj.get() == NULL) {
+        throwExceptionIfNecessary(env, "OBJ_nid2obj");
+        return NULL;
+    }
+
+    ScopedLocalRef<jstring> ouputStr(env, ASN1_OBJECT_to_OID_string(env, obj.get()));
+    JNI_TRACE("OBJ_txt2nid_oid(%s) => %p", oid.c_str(), ouputStr.get());
+    return ouputStr.release();
+}
+
+static jstring NativeCrypto_X509_NAME_print_ex(JNIEnv* env, jclass, jlong x509NameRef, jlong jflags) {
+    X509_NAME* x509name = reinterpret_cast<X509_NAME*>(static_cast<uintptr_t>(x509NameRef));
+    unsigned long flags = static_cast<unsigned long>(jflags);
+    JNI_TRACE("X509_NAME_print_ex(%p, %ld)", x509name, flags);
+
+    if (x509name == NULL) {
+        jniThrowNullPointerException(env, "x509name == null");
+        JNI_TRACE("X509_NAME_print_ex(%p, %ld) => x509name == null", x509name, flags);
+        return NULL;
+    }
+
+    return X509_NAME_to_jstring(env, x509name, flags);
+}
+
+template <typename T, T* (*d2i_func)(BIO*, T**)>
+static jlong d2i_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("d2i_ASN1Object_to_jlong(%p)", bio);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        return 0;
+    }
+
+    T* x = d2i_func(bio, NULL);
+    if (x == NULL) {
+        throwExceptionIfNecessary(env, "d2i_ASN1Object_to_jlong");
+        return 0;
+    }
+
+    return reinterpret_cast<uintptr_t>(x);
+}
+
+static jlong NativeCrypto_d2i_X509_CRL_bio(JNIEnv* env, jclass, jlong bioRef) {
+    return d2i_ASN1Object_to_jlong<X509_CRL, d2i_X509_CRL_bio>(env, bioRef);
+}
+
+static jlong NativeCrypto_d2i_X509_bio(JNIEnv* env, jclass, jlong bioRef) {
+    return d2i_ASN1Object_to_jlong<X509, d2i_X509_bio>(env, bioRef);
+}
+
+static jlong NativeCrypto_d2i_X509(JNIEnv* env, jclass, jbyteArray certBytes) {
+    X509* x = ByteArrayToASN1<X509, d2i_X509>(env, certBytes);
+    return reinterpret_cast<uintptr_t>(x);
+}
+
+static jbyteArray NativeCrypto_i2d_X509(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("i2d_X509(%p)", x509);
+    return ASN1ToByteArray<X509, i2d_X509>(env, x509);
+}
+
+template<typename T, T* (*PEM_read_func)(BIO*, T**, pem_password_cb*, void*)>
+static jlong PEM_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("PEM_ASN1Object_to_jlong(%p)", bio);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => bio == null", bio);
+        return 0;
+    }
+
+    T* x = PEM_read_func(bio, NULL, NULL, NULL);
+    if (x == NULL) {
+        throwExceptionIfNecessary(env, "PEM_ASN1Object_to_jlong");
+        JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => threw exception", bio);
+        return 0;
+    }
+
+    JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => %p", bio, x);
+    return reinterpret_cast<uintptr_t>(x);
+}
+
+static jlong NativeCrypto_PEM_read_bio_X509(JNIEnv* env, jclass, jlong bioRef) {
+    JNI_TRACE("PEM_read_bio_X509(0x%llx)", bioRef);
+    return PEM_ASN1Object_to_jlong<X509, PEM_read_bio_X509>(env, bioRef);
+}
+
+static jlong NativeCrypto_PEM_read_bio_X509_CRL(JNIEnv* env, jclass, jlong bioRef) {
+    JNI_TRACE("PEM_read_bio_X509_CRL(0x%llx)", bioRef);
+    return PEM_ASN1Object_to_jlong<X509_CRL, PEM_read_bio_X509_CRL>(env, bioRef);
+}
+
+static STACK_OF(X509)* PKCS7_get_certs(PKCS7* pkcs7) {
+    if (PKCS7_type_is_signed(pkcs7)) {
+        return pkcs7->d.sign->cert;
+    } else if (PKCS7_type_is_signedAndEnveloped(pkcs7)) {
+        return pkcs7->d.signed_and_enveloped->cert;
+    } else {
+        JNI_TRACE("PKCS7_get_certs(%p) => unknown PKCS7 type", pkcs7);
+        return NULL;
+    }
+}
+
+static STACK_OF(X509_CRL)* PKCS7_get_CRLs(PKCS7* pkcs7) {
+    if (PKCS7_type_is_signed(pkcs7)) {
+        return pkcs7->d.sign->crl;
+    } else if (PKCS7_type_is_signedAndEnveloped(pkcs7)) {
+        return pkcs7->d.signed_and_enveloped->crl;
+    } else {
+        JNI_TRACE("PKCS7_get_CRLs(%p) => unknown PKCS7 type", pkcs7);
+        return NULL;
+    }
+}
+
+template <typename T, typename T_stack>
+static jlongArray PKCS7_to_ItemArray(JNIEnv* env, T_stack* stack, T* (*dup_func)(T*))
+{
+    if (stack == NULL) {
+        return NULL;
+    }
+
+    ScopedLocalRef<jlongArray> ref_array(env, NULL);
+    size_t size = sk_num(reinterpret_cast<_STACK*>(stack));
+    ref_array.reset(env->NewLongArray(size));
+    ScopedLongArrayRW items(env, ref_array.get());
+    for (size_t i = 0; i < size; i++) {
+        T* item = reinterpret_cast<T*>(sk_value(reinterpret_cast<_STACK*>(stack), i));
+        items[i] = reinterpret_cast<uintptr_t>(dup_func(item));
+    }
+
+    JNI_TRACE("PKCS7_to_ItemArray(%p) => %p [size=%d]", stack, ref_array.get(), size);
+    return ref_array.release();
+}
+
+#define PKCS7_CERTS 1
+#define PKCS7_CRLS 2
+
+static jlongArray NativeCrypto_PEM_read_bio_PKCS7(JNIEnv* env, jclass, jlong bioRef, jint which) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p)", bio);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p) => bio == null", bio);
+        return 0;
+    }
+
+    Unique_PKCS7 pkcs7(PEM_read_bio_PKCS7(bio, NULL, NULL, NULL));
+    if (pkcs7.get() == NULL) {
+        throwExceptionIfNecessary(env, "PEM_read_bio_PKCS7_CRLs");
+        JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p) => threw exception", bio);
+        return 0;
+    }
+
+    switch (which) {
+    case PKCS7_CERTS:
+        return PKCS7_to_ItemArray<X509, STACK_OF(X509)>(env, PKCS7_get_certs(pkcs7.get()), X509_dup);
+    case PKCS7_CRLS:
+        return PKCS7_to_ItemArray<X509_CRL, STACK_OF(X509_CRL)>(env, PKCS7_get_CRLs(pkcs7.get()),
+                X509_CRL_dup);
+    default:
+        jniThrowRuntimeException(env, "unknown PKCS7 field");
+        return NULL;
+    }
+}
+
+static jlongArray NativeCrypto_d2i_PKCS7_bio(JNIEnv* env, jclass, jlong bioRef, jint which) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("d2i_PKCS7_bio(%p, %d)", bio, which);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        JNI_TRACE("d2i_PKCS7_bio(%p, %d) => bio == null", bio, which);
+        return 0;
+    }
+
+    Unique_PKCS7 pkcs7(d2i_PKCS7_bio(bio, NULL));
+    if (pkcs7.get() == NULL) {
+        throwExceptionIfNecessary(env, "d2i_PKCS7_bio");
+        JNI_TRACE("d2i_PKCS7_bio(%p, %d) => threw exception", bio, which);
+        return 0;
+    }
+
+    switch (which) {
+    case PKCS7_CERTS:
+        return PKCS7_to_ItemArray<X509, STACK_OF(X509)>(env, PKCS7_get_certs(pkcs7.get()), X509_dup);
+    case PKCS7_CRLS:
+        return PKCS7_to_ItemArray<X509_CRL, STACK_OF(X509_CRL)>(env, PKCS7_get_CRLs(pkcs7.get()),
+                X509_CRL_dup);
+    default:
+        jniThrowRuntimeException(env, "unknown PKCS7 field");
+        return NULL;
+    }
+}
+
+static jbyteArray NativeCrypto_i2d_PKCS7(JNIEnv* env, jclass, jlongArray certsArray) {
+    JNI_TRACE("i2d_PKCS7(%p)", certsArray);
+
+    Unique_PKCS7 pkcs7(PKCS7_new());
+    if (pkcs7.get() == NULL) {
+        jniThrowNullPointerException(env, "pkcs7 == null");
+        JNI_TRACE("i2d_PKCS7(%p) => pkcs7 == null", certsArray);
+        return NULL;
+    }
+
+    if (PKCS7_set_type(pkcs7.get(), NID_pkcs7_signed) != 1) {
+        throwExceptionIfNecessary(env, "PKCS7_set_type");
+        return NULL;
+    }
+
+    ScopedLongArrayRO certs(env, certsArray);
+    for (size_t i = 0; i < certs.size(); i++) {
+        X509* item = reinterpret_cast<X509*>(certs[i]);
+        if (PKCS7_add_certificate(pkcs7.get(), item) != 1) {
+            throwExceptionIfNecessary(env, "i2d_PKCS7");
+            return NULL;
+        }
+    }
+
+    JNI_TRACE("i2d_PKCS7(%p) => %d certs", certsArray, certs.size());
+    return ASN1ToByteArray<PKCS7, i2d_PKCS7>(env, pkcs7.get());
+}
+
+typedef STACK_OF(X509) PKIPATH;
+
+ASN1_ITEM_TEMPLATE(PKIPATH) =
+    ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, PkiPath, X509)
+ASN1_ITEM_TEMPLATE_END(PKIPATH)
+
+static jlongArray NativeCrypto_ASN1_seq_unpack_X509_bio(JNIEnv* env, jclass, jlong bioRef) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    JNI_TRACE("ASN1_seq_unpack_X509_bio(%p)", bio);
+
+    Unique_sk_X509 path((PKIPATH*) ASN1_item_d2i_bio(ASN1_ITEM_rptr(PKIPATH), bio, NULL));
+    if (path.get() == NULL) {
+        throwExceptionIfNecessary(env, "ASN1_seq_unpack_X509_bio");
+        return NULL;
+    }
+
+    size_t size = sk_X509_num(path.get());
+
+    ScopedLocalRef<jlongArray> certArray(env, env->NewLongArray(size));
+    ScopedLongArrayRW certs(env, certArray.get());
+    for (size_t i = 0; i < size; i++) {
+        X509* item = reinterpret_cast<X509*>(sk_X509_value(path.get(), i));
+        certs[i] = reinterpret_cast<uintptr_t>(item);
+    }
+
+    JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => returns %d items", bio, size);
+    return certArray.release();
+}
+
+static jbyteArray NativeCrypto_ASN1_seq_pack_X509(JNIEnv* env, jclass, jlongArray certs) {
+    JNI_TRACE("ASN1_seq_pack_X509(%p)", certs);
+    ScopedLongArrayRO certsArray(env, certs);
+    if (certsArray.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => failed to get certs array", certs);
+        return NULL;
+    }
+
+    Unique_sk_X509 certStack(sk_X509_new_null());
+    if (certStack.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => failed to make cert stack", certs);
+        return NULL;
+    }
+
+    for (size_t i = 0; i < certsArray.size(); i++) {
+        X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(certsArray[i]));
+        sk_X509_push(certStack.get(), X509_dup(x509));
+    }
+
+    int len;
+    Unique_OPENSSL_str encoded(ASN1_seq_pack(
+                    reinterpret_cast<STACK_OF(OPENSSL_BLOCK)*>(
+                            reinterpret_cast<uintptr_t>(certStack.get())),
+                    reinterpret_cast<int (*)(void*, unsigned char**)>(i2d_X509), NULL, &len));
+    if (encoded.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => trouble encoding", certs);
+        return NULL;
+    }
+
+    ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(len));
+    if (byteArray.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => creating byte array failed", certs);
+        return NULL;
+    }
+
+    ScopedByteArrayRW bytes(env, byteArray.get());
+    if (bytes.get() == NULL) {
+        JNI_TRACE("ASN1_seq_pack_X509(%p) => using byte array failed", certs);
+        return NULL;
+    }
+
+    unsigned char* p = reinterpret_cast<unsigned char*>(bytes.get());
+    memcpy(p, encoded.get(), len);
+
+    return byteArray.release();
+}
+
+static void NativeCrypto_X509_free(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_free(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_free(%p) => x509 == null", x509);
+        return;
+    }
+
+    X509_free(x509);
+}
+
+static jint NativeCrypto_X509_cmp(JNIEnv* env, jclass, jlong x509Ref1, jlong x509Ref2) {
+    X509* x509_1 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref1));
+    X509* x509_2 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref2));
+    JNI_TRACE("X509_cmp(%p, %p)", x509_1, x509_2);
+
+    if (x509_1 == NULL) {
+        jniThrowNullPointerException(env, "x509_1 == null");
+        JNI_TRACE("X509_cmp(%p, %p) => x509_1 == null", x509_1, x509_2);
+        return -1;
+    }
+
+    if (x509_2 == NULL) {
+        jniThrowNullPointerException(env, "x509_2 == null");
+        JNI_TRACE("X509_cmp(%p, %p) => x509_2 == null", x509_1, x509_2);
+        return -1;
+    }
+
+    int ret = X509_cmp(x509_1, x509_2);
+    JNI_TRACE("X509_cmp(%p, %p) => %d", x509_1, x509_2, ret);
+    return ret;
+}
+
+static jint NativeCrypto_get_X509_hashCode(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_hashCode(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    // Force caching extensions.
+    X509_check_ca(x509);
+
+    jint hashCode = 0L;
+    for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {
+        hashCode = 31 * hashCode + x509->sha1_hash[i];
+    }
+    return hashCode;
+}
+
+static void NativeCrypto_X509_print_ex(JNIEnv* env, jclass, jlong bioRef, jlong x509Ref,
+        jlong nmflagJava, jlong certflagJava) {
+    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    long nmflag = static_cast<long>(nmflagJava);
+    long certflag = static_cast<long>(certflagJava);
+    JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld)", bio, x509, nmflag, certflag);
+
+    if (bio == NULL) {
+        jniThrowNullPointerException(env, "bio == null");
+        JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => bio == null", bio, x509, nmflag, certflag);
+        return;
+    }
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => x509 == null", bio, x509, nmflag, certflag);
+        return;
+    }
+
+    X509_print_ex(bio, x509, nmflag, certflag);
+    JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => success", bio, x509, nmflag, certflag);
+}
+
+static jlong NativeCrypto_X509_get_pubkey(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_pubkey(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_pubkey(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    Unique_EVP_PKEY pkey(X509_get_pubkey(x509));
+    if (pkey.get() == NULL) {
+        throwExceptionIfNecessary(env, "X509_get_pubkey");
+        return 0;
+    }
+
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static jbyteArray NativeCrypto_X509_get_issuer_name(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_issuer_name(%p)", x509);
+    return ASN1ToByteArray<X509_NAME, i2d_X509_NAME>(env, X509_get_issuer_name(x509));
+}
+
+static jbyteArray NativeCrypto_X509_get_subject_name(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_subject_name(%p)", x509);
+    return ASN1ToByteArray<X509_NAME, i2d_X509_NAME>(env, X509_get_subject_name(x509));
+}
+
+static jstring NativeCrypto_get_X509_pubkey_oid(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_pubkey_oid(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_pubkey_oid(%p) => x509 == null", x509);
+        return NULL;
+    }
+
+    X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(x509);
+    return ASN1_OBJECT_to_OID_string(env, pubkey->algor->algorithm);
+}
+
+static jstring NativeCrypto_get_X509_sig_alg_oid(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_sig_alg_oid(%p)", x509);
+
+    if (x509 == NULL || x509->sig_alg == NULL) {
+        jniThrowNullPointerException(env, "x509 == NULL || x509->sig_alg == NULL");
+        JNI_TRACE("get_X509_sig_alg_oid(%p) => x509 == NULL", x509);
+        return NULL;
+    }
+
+    return ASN1_OBJECT_to_OID_string(env, x509->sig_alg->algorithm);
+}
+
+static jbyteArray NativeCrypto_get_X509_sig_alg_parameter(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_sig_alg_parameter(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_sig_alg_parameter(%p) => x509 == null", x509);
+        return NULL;
+    }
+
+    if (x509->sig_alg->parameter == NULL) {
+        JNI_TRACE("get_X509_sig_alg_parameter(%p) => null", x509);
+        return NULL;
+    }
+
+    return ASN1ToByteArray<ASN1_TYPE, i2d_ASN1_TYPE>(env, x509->sig_alg->parameter);
+}
+
+static jbooleanArray NativeCrypto_get_X509_issuerUID(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_issuerUID(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_issuerUID(%p) => x509 == null", x509);
+        return NULL;
+    }
+
+    if (x509->cert_info->issuerUID == NULL) {
+        JNI_TRACE("get_X509_issuerUID(%p) => null", x509);
+        return NULL;
+    }
+
+    return ASN1BitStringToBooleanArray(env, x509->cert_info->issuerUID);
+}
+static jbooleanArray NativeCrypto_get_X509_subjectUID(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_subjectUID(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_subjectUID(%p) => x509 == null", x509);
+        return NULL;
+    }
+
+    if (x509->cert_info->subjectUID == NULL) {
+        JNI_TRACE("get_X509_subjectUID(%p) => null", x509);
+        return NULL;
+    }
+
+    return ASN1BitStringToBooleanArray(env, x509->cert_info->subjectUID);
+}
+
+static jbooleanArray NativeCrypto_get_X509_ex_kusage(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_ex_kusage(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_ex_kusage(%p) => x509 == null", x509);
+        return NULL;
+    }
+
+    Unique_ASN1_BIT_STRING bitStr(static_cast<ASN1_BIT_STRING*>(
+            X509_get_ext_d2i(x509, NID_key_usage, NULL, NULL)));
+    if (bitStr.get() == NULL) {
+        JNI_TRACE("get_X509_ex_kusage(%p) => null", x509);
+        return NULL;
+    }
+
+    return ASN1BitStringToBooleanArray(env, bitStr.get());
+}
+
+static jobjectArray NativeCrypto_get_X509_ex_xkusage(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_ex_xkusage(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_ex_xkusage(%p) => x509 == null", x509);
+        return NULL;
+    }
+
+    Unique_sk_ASN1_OBJECT objArray(static_cast<STACK_OF(ASN1_OBJECT)*>(
+            X509_get_ext_d2i(x509, NID_ext_key_usage, NULL, NULL)));
+    if (objArray.get() == NULL) {
+        JNI_TRACE("get_X509_ex_xkusage(%p) => null", x509);
+        return NULL;
+    }
+
+    size_t size = sk_ASN1_OBJECT_num(objArray.get());
+    ScopedLocalRef<jobjectArray> exKeyUsage(env, env->NewObjectArray(size,
+            JniConstants::stringClass, NULL));
+    if (exKeyUsage.get() == NULL) {
+        return NULL;
+    }
+
+    for (size_t i = 0; i < size; i++) {
+        ScopedLocalRef<jstring> oidStr(env, ASN1_OBJECT_to_OID_string(env,
+                sk_ASN1_OBJECT_value(objArray.get(), i)));
+        env->SetObjectArrayElement(exKeyUsage.get(), i, oidStr.get());
+    }
+
+    JNI_TRACE("get_X509_ex_xkusage(%p) => success (%d entries)", x509, size);
+    return exKeyUsage.release();
+}
+
+static jint NativeCrypto_X509_check_ca(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_check_ca(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_check_ca(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    int ret = X509_check_ca(x509);
+    JNI_TRACE("X509_check_ca(%p) => %d", x509, ret);
+    return ret;
+}
+
+static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_ex_pathlen(%p)", x509);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_ex_pathlen(%p) => x509 == null", x509);
+        return 0;
+    }
+
+    /* Just need to do this to cache the ex_* values. */
+    X509_check_ca(x509);
+
+    JNI_TRACE("get_X509_ex_pathlen(%p) => %ld", x509, x509->ex_pathlen);
+    return x509->ex_pathlen;
+}
+
+static jbyteArray NativeCrypto_X509_get_ext_oid(JNIEnv* env, jclass, jlong x509Ref,
+        jstring oidString) {
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("X509_get_ext_oid(%p, %p)", x509, oidString);
+    return X509Type_get_ext_oid<X509, X509_get_ext_by_OBJ, X509_get_ext>(env, x509, oidString);
+}
+
+static jbyteArray NativeCrypto_X509_CRL_get_ext_oid(JNIEnv* env, jclass, jlong x509CrlRef,
+        jstring oidString) {
+    X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+    JNI_TRACE("X509_CRL_get_ext_oid(%p, %p)", crl, oidString);
+    return X509Type_get_ext_oid<X509_CRL, X509_CRL_get_ext_by_OBJ, X509_CRL_get_ext>(env, crl,
+            oidString);
+}
+
+static jbyteArray NativeCrypto_X509_REVOKED_get_ext_oid(JNIEnv* env, jclass, jlong x509RevokedRef,
+        jstring oidString) {
+    X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
+    JNI_TRACE("X509_REVOKED_get_ext_oid(%p, %p)", revoked, oidString);
+    return X509Type_get_ext_oid<X509_REVOKED, X509_REVOKED_get_ext_by_OBJ, X509_REVOKED_get_ext>(
+            env, revoked, oidString);
+}
+
+template<typename T, int (*get_ext_by_critical_func)(T*, int, int), X509_EXTENSION* (*get_ext_func)(T*, int)>
+static jobjectArray get_X509Type_ext_oids(JNIEnv* env, jlong x509Ref, jint critical) {
+    T* x509 = reinterpret_cast<T*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509Type_ext_oids(%p, %d)", x509, critical);
+
+    if (x509 == NULL) {
+        jniThrowNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509Type_ext_oids(%p, %d) => x509 == null", x509, critical);
+        return NULL;
+    }
+
+    int lastPos = -1;
+    int count = 0;
+    while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) {
+        count++;
+    }
+
+    JNI_TRACE("get_X509Type_ext_oids(%p, %d) has %d entries", x509, critical, count);
+
+    ScopedLocalRef<jobjectArray> joa(env, env->NewObjectArray(count, JniConstants::stringClass, NULL));
+    if (joa.get() == NULL) {
+        JNI_TRACE("get_X509Type_ext_oids(%p, %d) => fail to allocate result array", x509, critical);
+        return NULL;
+    }
+
+    lastPos = -1;
+    count = 0;
+    while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) {
+        X509_EXTENSION* ext = get_ext_func(x509, lastPos);
+
+        ScopedLocalRef<jstring> extOid(env, ASN1_OBJECT_to_OID_string(env, ext->object));
+        if (extOid.get() == NULL) {
+            JNI_TRACE("get_X509Type_ext_oids(%p) => couldn't get OID", x509);
+            return NULL;
+        }
+
+        env->SetObjectArrayElement(joa.get(), count++, extOid.get());
+    }
+
+    JNI_TRACE("get_X509Type_ext_oids(%p, %d) => success", x509, critical);
+    return joa.release();
+}
+
+static jobjectArray NativeCrypto_get_X509_ext_oids(JNIEnv* env, jclass, jlong x509Ref,
+        jint critical) {
+    JNI_TRACE("get_X509_ext_oids(0x%llx, %d)", x509Ref, critical);
+    return get_X509Type_ext_oids<X509, X509_get_ext_by_critical, X509_get_ext>(env, x509Ref,
+            critical);
+}
+
+static jobjectArray NativeCrypto_get_X509_CRL_ext_oids(JNIEnv* env, jclass, jlong x509CrlRef,
+        jint critical) {
+    JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", x509CrlRef, critical);
+    return get_X509Type_ext_oids<X509_CRL, X509_CRL_get_ext_by_critical, X509_CRL_get_ext>(env,
+            x509CrlRef, critical);
+}
+
+static jobjectArray NativeCrypto_get_X509_REVOKED_ext_oids(JNIEnv* env, jclass, jlong x509RevokedRef,
+        jint critical) {
+    JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", x509RevokedRef, critical);
+    return get_X509Type_ext_oids<X509_REVOKED, X509_REVOKED_get_ext_by_critical,
+            X509_REVOKED_get_ext>(env, x509RevokedRef, critical);
+}
+
 #ifdef WITH_JNI_TRACE
 /**
  * Based on example logging call back from SSL_CTX_set_info_callback man page
@@ -2106,23 +5350,10 @@
     for (int i = 0; i < count; i++) {
         X509* cert = sk_X509_value(chain, i);
 
-        int len = i2d_X509(cert, NULL);
-        if (len < 0) {
-            return NULL;
-        }
-        ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(len));
+        ScopedLocalRef<jbyteArray> byteArray(env, ASN1ToByteArray<X509, i2d_X509>(env, cert));
         if (byteArray.get() == NULL) {
             return NULL;
         }
-        ScopedByteArrayRW bytes(env, byteArray.get());
-        if (bytes.get() == NULL) {
-            return NULL;
-        }
-        unsigned char* p = reinterpret_cast<unsigned char*>(bytes.get());
-        int n = i2d_X509(cert, &p);
-        if (n < 0) {
-            return NULL;
-        }
         env->SetObjectArrayElement(joa, i, byteArray.get());
     }
 
@@ -2143,35 +5374,24 @@
         return NULL;
     }
 
-    jobjectArray joa = env->NewObjectArray(count, JniConstants::byteArrayClass, NULL);
-    if (joa == NULL) {
+    ScopedLocalRef<jobjectArray> joa(env, env->NewObjectArray(count, JniConstants::byteArrayClass,
+            NULL));
+    if (joa.get() == NULL) {
         return NULL;
     }
 
     for (int i = 0; i < count; i++) {
         X509_NAME* principal = sk_X509_NAME_value(names, i);
 
-        int len = i2d_X509_NAME(principal, NULL);
-        if (len < 0) {
-            return NULL;
-        }
-        ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(len));
+        ScopedLocalRef<jbyteArray> byteArray(env, ASN1ToByteArray<X509_NAME, i2d_X509_NAME>(env,
+                principal));
         if (byteArray.get() == NULL) {
             return NULL;
         }
-        ScopedByteArrayRW bytes(env, byteArray.get());
-        if (bytes.get() == NULL) {
-            return NULL;
-        }
-        unsigned char* p = reinterpret_cast<unsigned char*>(bytes.get());
-        int n = i2d_X509_NAME(principal, &p);
-        if (n < 0) {
-            return NULL;
-        }
-        env->SetObjectArrayElement(joa, i, byteArray.get());
+        env->SetObjectArrayElement(joa.get(), i, byteArray.get());
     }
 
-    return joa;
+    return joa.release();
 }
 
 /**
@@ -2762,10 +5982,10 @@
 /*
  * public static native int SSL_CTX_new();
  */
-static int NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) {
+static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) {
     Unique_SSL_CTX sslCtx(SSL_CTX_new(SSLv23_method()));
     if (sslCtx.get() == NULL) {
-        jniThrowRuntimeException(env, "SSL_CTX_new");
+        throwExceptionIfNecessary(env, "SSL_CTX_new");
         return 0;
     }
     SSL_CTX_set_options(sslCtx.get(),
@@ -2792,10 +6012,10 @@
      * and undesirable.)
      */
     mode |= SSL_MODE_ENABLE_PARTIAL_WRITE;
-#if defined(SSL_MODE_SMALL_BUFFERS) /* not all SSL versions have this */
-    mode |= SSL_MODE_SMALL_BUFFERS;  /* lazily allocate record buffers; usually saves
-                                      * 44k over the default */
-#endif
+
+    // Reuse empty buffers within the SSL_CTX to save memory
+    mode |= SSL_MODE_RELEASE_BUFFERS;
+
     SSL_CTX_set_mode(sslCtx.get(), mode);
 
     SSL_CTX_set_cert_verify_callback(sslCtx.get(), cert_verify_callback, NULL);
@@ -2806,14 +6026,14 @@
     SSL_CTX_set_tmp_ecdh_callback(sslCtx.get(), tmp_ecdh_callback);
 
     JNI_TRACE("NativeCrypto_SSL_CTX_new => %p", sslCtx.get());
-    return (jint) sslCtx.release();
+    return (jlong) sslCtx.release();
 }
 
 /**
  * public static native void SSL_CTX_free(int ssl_ctx)
  */
 static void NativeCrypto_SSL_CTX_free(JNIEnv* env,
-        jclass, jint ssl_ctx_address)
+        jclass, jlong ssl_ctx_address)
 {
     SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
     JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_free", ssl_ctx);
@@ -2824,7 +6044,7 @@
 }
 
 static void NativeCrypto_SSL_CTX_set_session_id_context(JNIEnv* env, jclass,
-                                                        jint ssl_ctx_address, jbyteArray sid_ctx)
+                                                        jlong ssl_ctx_address, jbyteArray sid_ctx)
 {
     SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
     JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context sid_ctx=%p", ssl_ctx, sid_ctx);
@@ -2857,7 +6077,7 @@
 /**
  * public static native int SSL_new(int ssl_ctx) throws SSLException;
  */
-static jint NativeCrypto_SSL_new(JNIEnv* env, jclass, jint ssl_ctx_address)
+static jlong NativeCrypto_SSL_new(JNIEnv* env, jclass, jlong ssl_ctx_address)
 {
     SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
     JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new", ssl_ctx);
@@ -2883,10 +6103,164 @@
     SSL_set_verify(ssl.get(), SSL_VERIFY_NONE, NULL);
 
     JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => ssl=%p", ssl_ctx, ssl.get());
-    return (jint) ssl.release();
+    return (jlong) ssl.release();
 }
 
-static void NativeCrypto_SSL_use_OpenSSL_PrivateKey(JNIEnv* env, jclass, jint ssl_address, jint pkeyRef) {
+
+static void NativeCrypto_SSL_enable_tls_channel_id(JNIEnv* env, jclass, jlong ssl_address)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_NativeCrypto_SSL_enable_tls_channel_id", ssl);
+    if (ssl == NULL) {
+        return;
+    }
+
+    long ret = SSL_enable_tls_channel_id(ssl);
+    if (ret != 1L) {
+        ALOGE("%s", ERR_error_string(ERR_peek_error(), NULL));
+        throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, "Error enabling Channel ID");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_tls_channel_id => error", ssl);
+        return;
+    }
+}
+
+static jbyteArray NativeCrypto_SSL_get_tls_channel_id(JNIEnv* env, jclass, jlong ssl_address)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_NativeCrypto_SSL_get_tls_channel_id", ssl);
+    if (ssl == NULL) {
+        return NULL;
+    }
+
+    // Channel ID is 64 bytes long. Unfortunately, OpenSSL doesn't declare this length
+    // as a constant anywhere.
+    jbyteArray javaBytes = env->NewByteArray(64);
+    ScopedByteArrayRW bytes(env, javaBytes);
+    if (bytes.get() == NULL) {
+        JNI_TRACE("NativeCrypto_SSL_get_tls_channel_id(%p) => NULL", ssl);
+        return NULL;
+    }
+
+    unsigned char* tmp = reinterpret_cast<unsigned char*>(bytes.get());
+    // Unfortunately, the SSL_get_tls_channel_id method below always returns 64 (upon success)
+    // regardless of the number of bytes copied into the output buffer "tmp". Thus, the correctness
+    // of this code currently relies on the "tmp" buffer being exactly 64 bytes long.
+    long ret = SSL_get_tls_channel_id(ssl, tmp, 64);
+    if (ret == 0) {
+        // Channel ID either not set or did not verify
+        JNI_TRACE("NativeCrypto_SSL_get_tls_channel_id(%p) => not available", ssl);
+        return NULL;
+    } else if (ret != 64) {
+        ALOGE("%s", ERR_error_string(ERR_peek_error(), NULL));
+        throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, "Error getting Channel ID");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_channel_id => error, returned %ld", ssl, ret);
+        return NULL;
+    }
+
+    JNI_TRACE("ssl=%p NativeCrypto_NativeCrypto_SSL_get_tls_channel_id() => %p", ssl, javaBytes);
+    return javaBytes;
+}
+
+static void NativeCrypto_SSL_use_OpenSSL_PrivateKey_for_tls_channel_id(
+        JNIEnv* env, jclass, jlong ssl_address, jlong pkeyRef)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_use_OpenSSL_PrivateKey_for_tls_channel_id privatekey=%p",
+            ssl, pkey);
+    if (ssl == NULL) {
+        return;
+    }
+
+    if (pkey == NULL) {
+        return;
+    }
+
+    // SSL_set1_tls_channel_id requires ssl->server to be set to 0.
+    // Unfortunately, the default value is 1 and it's only changed to 0 just
+    // before the handshake starts (see NativeCrypto_SSL_do_handshake).
+    ssl->server = 0;
+    long ret = SSL_set1_tls_channel_id(ssl, pkey);
+
+    if (ret != 1L) {
+        ALOGE("%s", ERR_error_string(ERR_peek_error(), NULL));
+        throwSSLExceptionWithSslErrors(
+                env, ssl, SSL_ERROR_NONE, "Error setting private key for Channel ID");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_use_OpenSSL_PrivateKey_for_tls_channel_id => error", ssl);
+        return;
+    }
+    // SSL_use_PrivateKey expects to take ownership of the EVP_PKEY,
+    // but we have an external reference from the caller such as an
+    // OpenSSLKey, so we manually increment the reference count here.
+    CRYPTO_add(&pkey->references,+1,CRYPTO_LOCK_EVP_PKEY);
+
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_use_OpenSSL_PrivateKey_for_tls_channel_id => ok", ssl);
+}
+
+static void NativeCrypto_SSL_use_PKCS8_PrivateKey_for_tls_channel_id(
+        JNIEnv* env, jclass, jlong ssl_address, jbyteArray privatekey)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey_for_tls_channel_id privatekey=%p", ssl,
+            privatekey);
+    if (ssl == NULL) {
+        return;
+    }
+
+    ScopedByteArrayRO buf(env, privatekey);
+    if (buf.get() == NULL) {
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey_for_tls_channel_id => threw exception",
+                ssl);
+        return;
+    }
+    const unsigned char* tmp = reinterpret_cast<const unsigned char*>(buf.get());
+    Unique_PKCS8_PRIV_KEY_INFO pkcs8(d2i_PKCS8_PRIV_KEY_INFO(NULL, &tmp, buf.size()));
+    if (pkcs8.get() == NULL) {
+        ALOGE("%s", ERR_error_string(ERR_peek_error(), NULL));
+        throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE,
+                                       "Error parsing private key from DER to PKCS8");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey => error from DER to PKCS8", ssl);
+        return;
+    }
+
+    Unique_EVP_PKEY privatekeyevp(EVP_PKCS82PKEY(pkcs8.get()));
+    if (privatekeyevp.get() == NULL) {
+        ALOGE("%s", ERR_error_string(ERR_peek_error(), NULL));
+        throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE,
+                                       "Error creating private key from PKCS8");
+        SSL_clear(ssl);
+        JNI_TRACE(
+                "ssl=%p NativeCrypto_SSL_use_PrivateKey_for_tls_channel_id => error from PKCS8 to key",
+                ssl);
+        return;
+    }
+
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey_for_tls_channel_id EVP_PKEY_type=%d",
+              ssl, EVP_PKEY_type(privatekeyevp.get()->type));
+
+    // SSL_set1_tls_channel_id requires ssl->server to be set to 0.
+    // Unfortunately, the default value is 1 and it's only changed to 0 just
+    // before the handshake starts (see NativeCrypto_SSL_do_handshake).
+    ssl->server = 0;
+    long ret = SSL_set1_tls_channel_id(ssl, privatekeyevp.get());
+    if (ret == 1L) {
+        OWNERSHIP_TRANSFERRED(privatekeyevp);
+    } else {
+        ALOGE("%s", ERR_error_string(ERR_peek_error(), NULL));
+        throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, "Error setting private key");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey_for_tls_channel_id => error", ssl);
+        return;
+    }
+
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey_for_tls_channel_id => ok", ssl);
+}
+
+static void NativeCrypto_SSL_use_OpenSSL_PrivateKey(JNIEnv* env, jclass, jlong ssl_address, jlong pkeyRef) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("ssl=%p SSL_use_OpenSSL_PrivateKey privatekey=%p", ssl, pkey);
@@ -2915,7 +6289,7 @@
 }
 
 static void NativeCrypto_SSL_use_PrivateKey(JNIEnv* env, jclass,
-                                            jint ssl_address, jbyteArray privatekey)
+                                            jlong ssl_address, jbyteArray privatekey)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_use_PrivateKey privatekey=%p", ssl, privatekey);
@@ -2966,7 +6340,7 @@
 }
 
 static void NativeCrypto_SSL_use_certificate(JNIEnv* env, jclass,
-                                             jint ssl_address, jobjectArray certificates)
+                                             jlong ssl_address, jobjectArray certificates)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_use_certificate certificates=%p", ssl, certificates);
@@ -3051,7 +6425,7 @@
     JNI_TRACE("ssl=%p NativeCrypto_SSL_use_certificate => ok", ssl);
 }
 
-static void NativeCrypto_SSL_check_private_key(JNIEnv* env, jclass, jint ssl_address)
+static void NativeCrypto_SSL_check_private_key(JNIEnv* env, jclass, jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_check_private_key", ssl);
@@ -3069,7 +6443,7 @@
 }
 
 static void NativeCrypto_SSL_set_client_CA_list(JNIEnv* env, jclass,
-                                                jint ssl_address, jobjectArray principals)
+                                                jlong ssl_address, jobjectArray principals)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list principals=%p", ssl, principals);
@@ -3136,7 +6510,7 @@
 /**
  * public static native long SSL_get_mode(int ssl);
  */
-static jlong NativeCrypto_SSL_get_mode(JNIEnv* env, jclass, jint ssl_address) {
+static jlong NativeCrypto_SSL_get_mode(JNIEnv* env, jclass, jlong ssl_address) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode", ssl);
     if (ssl == NULL) {
@@ -3151,7 +6525,7 @@
  * public static native long SSL_set_mode(int ssl, long mode);
  */
 static jlong NativeCrypto_SSL_set_mode(JNIEnv* env, jclass,
-        jint ssl_address, jlong mode) {
+        jlong ssl_address, jlong mode) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode mode=0x%llx", ssl, mode);
     if (ssl == NULL) {
@@ -3166,7 +6540,7 @@
  * public static native long SSL_clear_mode(int ssl, long mode);
  */
 static jlong NativeCrypto_SSL_clear_mode(JNIEnv* env, jclass,
-        jint ssl_address, jlong mode) {
+        jlong ssl_address, jlong mode) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode mode=0x%llx", ssl, mode);
     if (ssl == NULL) {
@@ -3181,7 +6555,7 @@
  * public static native long SSL_get_options(int ssl);
  */
 static jlong NativeCrypto_SSL_get_options(JNIEnv* env, jclass,
-        jint ssl_address) {
+        jlong ssl_address) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options", ssl);
     if (ssl == NULL) {
@@ -3196,7 +6570,7 @@
  * public static native long SSL_set_options(int ssl, long options);
  */
 static jlong NativeCrypto_SSL_set_options(JNIEnv* env, jclass,
-        jint ssl_address, jlong options) {
+        jlong ssl_address, jlong options) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options options=0x%llx", ssl, options);
     if (ssl == NULL) {
@@ -3211,7 +6585,7 @@
  * public static native long SSL_clear_options(int ssl, long options);
  */
 static jlong NativeCrypto_SSL_clear_options(JNIEnv* env, jclass,
-        jint ssl_address, jlong options) {
+        jlong ssl_address, jlong options) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options options=0x%llx", ssl, options);
     if (ssl == NULL) {
@@ -3226,7 +6600,7 @@
  * Sets the ciphers suites that are enabled in the SSL
  */
 static void NativeCrypto_SSL_set_cipher_lists(JNIEnv* env, jclass,
-        jint ssl_address, jobjectArray cipherSuites)
+        jlong ssl_address, jobjectArray cipherSuites)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=%p", ssl, cipherSuites);
@@ -3291,7 +6665,7 @@
  * Sets certificate expectations, especially for server to request client auth
  */
 static void NativeCrypto_SSL_set_verify(JNIEnv* env,
-        jclass, jint ssl_address, jint mode)
+        jclass, jlong ssl_address, jint mode)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_verify mode=%x", ssl, mode);
@@ -3305,7 +6679,7 @@
  * Sets the ciphers suites that are enabled in the SSL
  */
 static void NativeCrypto_SSL_set_session(JNIEnv* env, jclass,
-        jint ssl_address, jint ssl_session_address)
+        jlong ssl_address, jlong ssl_session_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, false);
@@ -3332,7 +6706,7 @@
  * Sets the ciphers suites that are enabled in the SSL
  */
 static void NativeCrypto_SSL_set_session_creation_enabled(JNIEnv* env, jclass,
-        jint ssl_address, jboolean creation_enabled)
+        jlong ssl_address, jboolean creation_enabled)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session_creation_enabled creation_enabled=%d",
@@ -3344,7 +6718,7 @@
 }
 
 static void NativeCrypto_SSL_set_tlsext_host_name(JNIEnv* env, jclass,
-        jint ssl_address, jstring hostname)
+        jlong ssl_address, jstring hostname)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name hostname=%p",
@@ -3370,7 +6744,7 @@
     JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name => ok", ssl);
 }
 
-static jstring NativeCrypto_SSL_get_servername(JNIEnv* env, jclass, jint ssl_address) {
+static jstring NativeCrypto_SSL_get_servername(JNIEnv* env, jclass, jlong ssl_address) {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_get_servername", ssl);
     if (ssl == NULL) {
@@ -3430,7 +6804,7 @@
     return SSL_TLSEXT_ERR_OK;
 }
 
-static void NativeCrypto_SSL_CTX_enable_npn(JNIEnv* env, jclass, jint ssl_ctx_address)
+static void NativeCrypto_SSL_CTX_enable_npn(JNIEnv* env, jclass, jlong ssl_ctx_address)
 {
     SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
     if (ssl_ctx == NULL) {
@@ -3440,7 +6814,7 @@
     SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_protos_advertised_callback, NULL); // server
 }
 
-static void NativeCrypto_SSL_CTX_disable_npn(JNIEnv* env, jclass, jint ssl_ctx_address)
+static void NativeCrypto_SSL_CTX_disable_npn(JNIEnv* env, jclass, jlong ssl_ctx_address)
 {
     SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
     if (ssl_ctx == NULL) {
@@ -3451,7 +6825,7 @@
 }
 
 static jbyteArray NativeCrypto_SSL_get_npn_negotiated_protocol(JNIEnv* env, jclass,
-        jint ssl_address)
+        jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_get_npn_negotiated_protocol", ssl);
@@ -3474,7 +6848,7 @@
 /**
  * Perform SSL handshake
  */
-static jint NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jint ssl_address,
+static jlong NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address,
         jobject fdObject, jobject shc, jint timeout_millis, jboolean client_mode,
         jbyteArray npnProtocols)
 {
@@ -3645,13 +7019,13 @@
     }
     SSL_SESSION* ssl_session = SSL_get1_session(ssl);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => ssl_session=%p", ssl, ssl_session);
-    return (jint) ssl_session;
+    return (jlong) ssl_session;
 }
 
 /**
  * Perform SSL renegotiation
  */
-static void NativeCrypto_SSL_renegotiate(JNIEnv* env, jclass, jint ssl_address)
+static void NativeCrypto_SSL_renegotiate(JNIEnv* env, jclass, jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_renegotiate", ssl);
@@ -3680,7 +7054,7 @@
 /**
  * public static native byte[][] SSL_get_certificate(int ssl);
  */
-static jobjectArray NativeCrypto_SSL_get_certificate(JNIEnv* env, jclass, jint ssl_address)
+static jobjectArray NativeCrypto_SSL_get_certificate(JNIEnv* env, jclass, jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_get_certificate", ssl);
@@ -3719,7 +7093,7 @@
 }
 
 // Fills a byte[][] with the peer certificates in the chain.
-static jobjectArray NativeCrypto_SSL_get_peer_cert_chain(JNIEnv* env, jclass, jint ssl_address)
+static jobjectArray NativeCrypto_SSL_get_peer_cert_chain(JNIEnv* env, jclass, jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_get_peer_cert_chain", ssl);
@@ -3894,7 +7268,7 @@
  * OpenSSL read function (2): read into buffer at offset n chunks.
  * Returns 1 (success) or value <= 0 (failure).
  */
-static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jint ssl_address, jobject fdObject,
+static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jlong ssl_address, jobject fdObject,
                                   jobject shc, jbyteArray b, jint offset, jint len,
                                   jint read_timeout_millis)
 {
@@ -3982,7 +7356,7 @@
 
     int count = len;
 
-    while (appData->aliveAndKicking && len > 0) {
+    while (appData->aliveAndKicking && ((len > 0) || (ssl->s3->wbuf.left > 0))) {
         errno = 0;
 
         if (MUTEX_LOCK(appData->mutex) == -1) {
@@ -3991,11 +7365,11 @@
 
         unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio);
 
-        // ALOGD("Doing SSL_write() with %d bytes to go", len);
         if (!appData->setCallbackState(env, shc, fdObject, NULL)) {
             MUTEX_UNLOCK(appData->mutex);
             return THROWN_EXCEPTION;
         }
+        JNI_TRACE("ssl=%p sslWrite SSL_write len=%d left=%d", ssl, len, ssl->s3->wbuf.left);
         int result = SSL_write(ssl, buf, len);
         appData->clearCallbackState();
         // callbacks can happen if server requests renegotiation
@@ -4009,7 +7383,8 @@
             sslError = SSL_get_error(ssl, result);
             freeOpenSslErrorState();
         }
-        JNI_TRACE("ssl=%p sslWrite SSL_write result=%d sslError=%d", ssl, result, sslError);
+        JNI_TRACE("ssl=%p sslWrite SSL_write result=%d sslError=%d left=%d",
+                  ssl, result, sslError, ssl->s3->wbuf.left);
 #ifdef WITH_JNI_TRACE_DATA
         for (int i = 0; i < result; i+= WITH_JNI_TRACE_DATA_CHUNK_SIZE) {
             int n = std::min(result - i, WITH_JNI_TRACE_DATA_CHUNK_SIZE);
@@ -4101,7 +7476,7 @@
 /**
  * OpenSSL write function (2): write into buffer at offset n chunks.
  */
-static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jint ssl_address, jobject fdObject,
+static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jlong ssl_address, jobject fdObject,
                                    jobject shc, jbyteArray b, jint offset, jint len, jint write_timeout_millis)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
@@ -4151,7 +7526,7 @@
  * Interrupt any pending I/O before closing the socket.
  */
 static void NativeCrypto_SSL_interrupt(
-        JNIEnv* env, jclass, jint ssl_address) {
+        JNIEnv* env, jclass, jlong ssl_address) {
     SSL* ssl = to_SSL(env, ssl_address, false);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_interrupt", ssl);
     if (ssl == NULL) {
@@ -4175,7 +7550,7 @@
 /**
  * OpenSSL close SSL socket function.
  */
-static void NativeCrypto_SSL_shutdown(JNIEnv* env, jclass, jint ssl_address,
+static void NativeCrypto_SSL_shutdown(JNIEnv* env, jclass, jlong ssl_address,
                                       jobject fdObject, jobject shc) {
     SSL* ssl = to_SSL(env, ssl_address, false);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown fd=%p shc=%p", ssl, fdObject, shc);
@@ -4255,7 +7630,7 @@
 /**
  * public static native void SSL_free(int ssl);
  */
-static void NativeCrypto_SSL_free(JNIEnv* env, jclass, jint ssl_address)
+static void NativeCrypto_SSL_free(JNIEnv* env, jclass, jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_free", ssl);
@@ -4273,7 +7648,7 @@
  * Gets and returns in a byte array the ID of the actual SSL session.
  */
 static jbyteArray NativeCrypto_SSL_SESSION_session_id(JNIEnv* env, jclass,
-                                                      jint ssl_session_address) {
+                                                      jlong ssl_session_address) {
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
     JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_session_id", ssl_session);
     if (ssl_session == NULL) {
@@ -4293,7 +7668,7 @@
  * Gets and returns in a long integer the creation's time of the
  * actual SSL session.
  */
-static jlong NativeCrypto_SSL_SESSION_get_time(JNIEnv* env, jclass, jint ssl_session_address) {
+static jlong NativeCrypto_SSL_SESSION_get_time(JNIEnv* env, jclass, jlong ssl_session_address) {
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
     JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_get_time", ssl_session);
     if (ssl_session == NULL) {
@@ -4310,7 +7685,7 @@
  * Gets and returns in a string the version of the SSL protocol. If it
  * returns the string "unknown" it means that no connection is established.
  */
-static jstring NativeCrypto_SSL_SESSION_get_version(JNIEnv* env, jclass, jint ssl_session_address) {
+static jstring NativeCrypto_SSL_SESSION_get_version(JNIEnv* env, jclass, jlong ssl_session_address) {
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
     JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_get_version", ssl_session);
     if (ssl_session == NULL) {
@@ -4324,7 +7699,7 @@
 /**
  * Gets and returns in a string the cipher negotiated for the SSL session.
  */
-static jstring NativeCrypto_SSL_SESSION_cipher(JNIEnv* env, jclass, jint ssl_session_address) {
+static jstring NativeCrypto_SSL_SESSION_cipher(JNIEnv* env, jclass, jlong ssl_session_address) {
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
     JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_cipher", ssl_session);
     if (ssl_session == NULL) {
@@ -4339,7 +7714,7 @@
 /**
  * Frees the SSL session.
  */
-static void NativeCrypto_SSL_SESSION_free(JNIEnv* env, jclass, jint ssl_session_address) {
+static void NativeCrypto_SSL_SESSION_free(JNIEnv* env, jclass, jlong ssl_session_address) {
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
     JNI_TRACE("ssl_session=%p NativeCrypto_SSL_SESSION_free", ssl_session);
     if (ssl_session == NULL) {
@@ -4354,40 +7729,16 @@
  * not certificates). Returns a byte[] containing the DER-encoded state.
  * See apache mod_ssl.
  */
-static jbyteArray NativeCrypto_i2d_SSL_SESSION(JNIEnv* env, jclass, jint ssl_session_address) {
+static jbyteArray NativeCrypto_i2d_SSL_SESSION(JNIEnv* env, jclass, jlong ssl_session_address) {
     SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, true);
     JNI_TRACE("ssl_session=%p NativeCrypto_i2d_SSL_SESSION", ssl_session);
-    if (ssl_session == NULL) {
-        return NULL;
-    }
-
-    // Compute the size of the DER data
-    int size = i2d_SSL_SESSION(ssl_session, NULL);
-    if (size == 0) {
-        JNI_TRACE("ssl_session=%p NativeCrypto_i2d_SSL_SESSION => NULL", ssl_session);
-        return NULL;
-    }
-
-    jbyteArray javaBytes = env->NewByteArray(size);
-    if (javaBytes != NULL) {
-        ScopedByteArrayRW bytes(env, javaBytes);
-        if (bytes.get() == NULL) {
-            JNI_TRACE("ssl_session=%p NativeCrypto_i2d_SSL_SESSION => threw exception",
-                      ssl_session);
-            return NULL;
-        }
-        unsigned char* ucp = reinterpret_cast<unsigned char*>(bytes.get());
-        i2d_SSL_SESSION(ssl_session, &ucp);
-    }
-
-    JNI_TRACE("ssl_session=%p NativeCrypto_i2d_SSL_SESSION => size=%d", ssl_session, size);
-    return javaBytes;
+    return ASN1ToByteArray<SSL_SESSION, i2d_SSL_SESSION>(env, ssl_session);
 }
 
 /**
  * Deserialize the session.
  */
-static jint NativeCrypto_d2i_SSL_SESSION(JNIEnv* env, jclass, jbyteArray javaBytes) {
+static jlong NativeCrypto_d2i_SSL_SESSION(JNIEnv* env, jclass, jbyteArray javaBytes) {
     JNI_TRACE("NativeCrypto_d2i_SSL_SESSION bytes=%p", javaBytes);
 
     ScopedByteArrayRO bytes(env, javaBytes);
@@ -4413,10 +7764,16 @@
                   ssl_session->cipher_id, cipher_id_network_order,
                   cipher_id_byte_pointer[0], cipher_id_byte_pointer[1],
                   SSL_CIPHER_get_name(ssl_session->cipher));
+    } else {
+        freeOpenSslErrorState();
     }
 
     JNI_TRACE("NativeCrypto_d2i_SSL_SESSION => %p", ssl_session);
-    return static_cast<jint>(reinterpret_cast<uintptr_t>(ssl_session));
+    return reinterpret_cast<uintptr_t>(ssl_session);
+}
+
+static jlong NativeCrypto_ERR_peek_last_error(JNIEnv*, jclass) {
+    return ERR_peek_last_error();
 }
 
 #define FILE_DESCRIPTOR "Ljava/io/FileDescriptor;"
@@ -4424,98 +7781,238 @@
 static JNINativeMethod sNativeCryptoMethods[] = {
     NATIVE_METHOD(NativeCrypto, clinit, "()V"),
     NATIVE_METHOD(NativeCrypto, ENGINE_load_dynamic, "()V"),
-    NATIVE_METHOD(NativeCrypto, ENGINE_by_id, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeCrypto, ENGINE_add, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, ENGINE_init, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, ENGINE_finish, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, ENGINE_free, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, ENGINE_load_private_key, "(ILjava/lang/String;)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_DSA, "([B[B[B[B[B)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_RSA, "([B[B[B[B[B[B[B[B)I"),
-    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"),
-    NATIVE_METHOD(NativeCrypto, d2i_PUBKEY, "([B)I"),
-    NATIVE_METHOD(NativeCrypto, RSA_generate_key_ex, "(I[B)I"),
-    NATIVE_METHOD(NativeCrypto, RSA_size, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, RSA_private_encrypt, "(I[B[BII)I"),
-    NATIVE_METHOD(NativeCrypto, RSA_public_decrypt, "(I[B[BII)I"),
-    NATIVE_METHOD(NativeCrypto, RSA_public_encrypt, "(I[B[BII)I"),
-    NATIVE_METHOD(NativeCrypto, RSA_private_decrypt, "(I[B[BII)I"),
-    NATIVE_METHOD(NativeCrypto, get_RSA_private_params, "(I)[[B"),
-    NATIVE_METHOD(NativeCrypto, get_RSA_public_params, "(I)[[B"),
-    NATIVE_METHOD(NativeCrypto, DSA_generate_key, "(I[B[B[B[B)I"),
-    NATIVE_METHOD(NativeCrypto, get_DSA_params, "(I)[[B"),
-    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_destroy, "(I)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_copy, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestFinal, "(I[BI)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestInit, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_get_digestbyname, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_MD_block_size, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_MD_size, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestUpdate, "(I[BII)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_SignInit, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_SignUpdate, "(I[BII)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_SignFinal, "(I[BII)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_VerifyInit, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_VerifyUpdate, "(I[BII)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_VerifyFinal, "(I[BIII)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_get_cipherbyname, "(Ljava/lang/String;)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_CipherInit_ex, "(I[B[BZ)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_CipherUpdate, "(I[BI[BI)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_CipherFinal_ex, "(I[BI)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_cleanup, "(I)V"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_by_id, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_add, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_init, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_finish, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_free, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_load_private_key, "(JLjava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_get_id, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, ENGINE_ctrl_cmd_string, "(JLjava/lang/String;Ljava/lang/String;I)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_DSA, "([B[B[B[B[B)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_RSA, "([B[B[B[B[B[B[B[B)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_EC_KEY, "(JJ[B)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_mac_key, "(I[B)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_type, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_size, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_print_public, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_print_private, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_cmp, "(JJ)I"),
+    NATIVE_METHOD(NativeCrypto, i2d_PKCS8_PRIV_KEY_INFO, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, d2i_PKCS8_PRIV_KEY_INFO, "([B)J"),
+    NATIVE_METHOD(NativeCrypto, i2d_PUBKEY, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, d2i_PUBKEY, "([B)J"),
+    NATIVE_METHOD(NativeCrypto, RSA_generate_key_ex, "(I[B)J"),
+    NATIVE_METHOD(NativeCrypto, RSA_size, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, RSA_private_encrypt, "(I[B[BJI)I"),
+    NATIVE_METHOD(NativeCrypto, RSA_public_decrypt, "(I[B[BJI)I"),
+    NATIVE_METHOD(NativeCrypto, RSA_public_encrypt, "(I[B[BJI)I"),
+    NATIVE_METHOD(NativeCrypto, RSA_private_decrypt, "(I[B[BJI)I"),
+    NATIVE_METHOD(NativeCrypto, get_RSA_private_params, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, get_RSA_public_params, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, DSA_generate_key, "(I[B[B[B[B)J"),
+    NATIVE_METHOD(NativeCrypto, get_DSA_params, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_new_by_curve_name, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_new_curve, "(I[B[B[B)J"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_dup, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_set_asn1_flag, "(JI)V"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_set_point_conversion_form, "(JI)V"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_get_curve_name, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_get_curve, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_get_order, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_get_cofactor, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_clear_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_cmp, "(JJ)Z"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_get_generator, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EC_GROUP_set_generator, "(JJ[B[B)V"),
+    NATIVE_METHOD(NativeCrypto, get_EC_GROUP_type, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EC_POINT_new, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EC_POINT_clear_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, EC_POINT_cmp, "(JJJ)Z"),
+    NATIVE_METHOD(NativeCrypto, EC_POINT_set_affine_coordinates, "(JJ[B[B)V"),
+    NATIVE_METHOD(NativeCrypto, EC_POINT_get_affine_coordinates, "(JJ)[[B"),
+    NATIVE_METHOD(NativeCrypto, EC_KEY_generate_key, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EC_KEY_get0_group, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EC_KEY_get_private_key, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, EC_KEY_get_public_key, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, ECDH_compute_key, "([BIJJ)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_create, "()J"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_init, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_destroy, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_copy, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestFinal, "(J[BI)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestInit, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_get_digestbyname, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_block_size, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_size, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestUpdate, "(J[BII)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_SignInit, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_SignUpdate, "(J[BII)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_SignFinal, "(J[BIJ)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_VerifyInit, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_VerifyUpdate, "(J[BII)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_VerifyFinal, "(J[BIIJ)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestSignInit, "(JJJ)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestSignUpdate, "(J[B)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestSignFinal, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, EVP_get_cipherbyname, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_CipherInit_ex, "(JJ[B[BZ)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_CipherUpdate, "(J[BI[BII)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_CipherFinal_ex, "(J[BI)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_iv_length, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_new, "()J"),
+    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_block_size, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, get_EVP_CIPHER_CTX_buf_len, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_set_padding, "(JZ)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_set_key_length, "(JI)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_CIPHER_CTX_cleanup, "(J)V"),
     NATIVE_METHOD(NativeCrypto, RAND_seed, "([B)V"),
     NATIVE_METHOD(NativeCrypto, RAND_load_file, "(Ljava/lang/String;J)I"),
     NATIVE_METHOD(NativeCrypto, RAND_bytes, "([B)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_CTX_new, "()I"),
-    NATIVE_METHOD(NativeCrypto, SSL_CTX_free, "(I)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_CTX_set_session_id_context, "(I[B)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_new, "(I)I"),
-    NATIVE_METHOD(NativeCrypto, SSL_use_OpenSSL_PrivateKey, "(II)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_use_PrivateKey, "(I[B)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_use_certificate, "(I[[B)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_check_private_key, "(I)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_client_CA_list, "(I[[B)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_get_mode, "(I)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_mode, "(IJ)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_clear_mode, "(IJ)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_get_options, "(I)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_options, "(IJ)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_clear_options, "(IJ)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_cipher_lists, "(I[Ljava/lang/String;)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_verify, "(II)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_session, "(II)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_session_creation_enabled, "(IZ)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_set_tlsext_host_name, "(ILjava/lang/String;)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_get_servername, "(I)Ljava/lang/String;"),
-    NATIVE_METHOD(NativeCrypto, SSL_do_handshake, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "IZ[B)I"),
-    NATIVE_METHOD(NativeCrypto, SSL_renegotiate, "(I)V"),
-    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 "[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"),
-    NATIVE_METHOD(NativeCrypto, SSL_SESSION_session_id, "(I)[B"),
-    NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_time, "(I)J"),
-    NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_version, "(I)Ljava/lang/String;"),
-    NATIVE_METHOD(NativeCrypto, SSL_SESSION_cipher, "(I)Ljava/lang/String;"),
-    NATIVE_METHOD(NativeCrypto, SSL_SESSION_free, "(I)V"),
-    NATIVE_METHOD(NativeCrypto, i2d_SSL_SESSION, "(I)[B"),
-    NATIVE_METHOD(NativeCrypto, d2i_SSL_SESSION, "([B)I"),
-    NATIVE_METHOD(NativeCrypto, SSL_CTX_enable_npn, "(I)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_CTX_disable_npn, "(I)V"),
-    NATIVE_METHOD(NativeCrypto, SSL_get_npn_negotiated_protocol, "(I)[B"),
+    NATIVE_METHOD(NativeCrypto, OBJ_txt2nid, "(Ljava/lang/String;)I"),
+    NATIVE_METHOD(NativeCrypto, OBJ_txt2nid_longName, "(Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, OBJ_txt2nid_oid, "(Ljava/lang/String;)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, create_BIO_InputStream, "(Lorg/apache/harmony/xnet/provider/jsse/OpenSSLBIOInputStream;)J"),
+    NATIVE_METHOD(NativeCrypto, create_BIO_OutputStream, "(Ljava/io/OutputStream;)J"),
+    NATIVE_METHOD(NativeCrypto, BIO_read, "(J[B)I"),
+    NATIVE_METHOD(NativeCrypto, BIO_write, "(J[BII)V"),
+    NATIVE_METHOD(NativeCrypto, BIO_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, X509_NAME_print_ex, "(JJ)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, d2i_X509_bio, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, d2i_X509, "([B)J"),
+    NATIVE_METHOD(NativeCrypto, i2d_X509, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, PEM_read_bio_X509, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, PEM_read_bio_PKCS7, "(JI)[J"),
+    NATIVE_METHOD(NativeCrypto, d2i_PKCS7_bio, "(JI)[J"),
+    NATIVE_METHOD(NativeCrypto, i2d_PKCS7, "([J)[B"),
+    NATIVE_METHOD(NativeCrypto, ASN1_seq_unpack_X509_bio, "(J)[J"),
+    NATIVE_METHOD(NativeCrypto, ASN1_seq_pack_X509, "([J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, X509_cmp, "(JJ)I"),
+    NATIVE_METHOD(NativeCrypto, get_X509_hashCode, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, X509_print_ex, "(JJJJ)V"),
+    NATIVE_METHOD(NativeCrypto, X509_get_pubkey, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_get_issuer_name, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_get_subject_name, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, get_X509_pubkey_oid, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, get_X509_sig_alg_oid, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, get_X509_sig_alg_parameter, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, get_X509_issuerUID, "(J)[Z"),
+    NATIVE_METHOD(NativeCrypto, get_X509_subjectUID, "(J)[Z"),
+    NATIVE_METHOD(NativeCrypto, get_X509_ex_kusage, "(J)[Z"),
+    NATIVE_METHOD(NativeCrypto, get_X509_ex_xkusage, "(J)[Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, X509_check_ca, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, get_X509_ex_pathlen, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, X509_get_ext_oid, "(JLjava/lang/String;)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_ext_oid, "(JLjava/lang/String;)[B"),
+    NATIVE_METHOD(NativeCrypto, get_X509_CRL_crl_enc, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_verify, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_lastUpdate, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_nextUpdate, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_REVOKED_get_ext_oid, "(JLjava/lang/String;)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_REVOKED_get_serialNumber, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_REVOKED_print, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, get_X509_REVOKED_revocationDate, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, get_X509_ext_oids, "(JI)[Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, get_X509_CRL_ext_oids, "(JI)[Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, get_X509_REVOKED_ext_oids, "(JI)[Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, get_X509_GENERAL_NAME_stack, "(JI)[[Ljava/lang/Object;"),
+    NATIVE_METHOD(NativeCrypto, X509_get_notBefore, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_get_notAfter, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_get_version, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_get_serialNumber, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_verify, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, get_X509_cert_info_enc, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, get_X509_signature, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, get_X509_CRL_signature, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, get_X509_ex_flags, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, d2i_X509_CRL_bio, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, PEM_read_bio_X509_CRL, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get0_by_cert, "(JJ)J"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get0_by_serial, "(J[B)J"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_REVOKED, "(J)[J"),
+    NATIVE_METHOD(NativeCrypto, i2d_X509_CRL, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_print, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, get_X509_CRL_sig_alg_oid, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, get_X509_CRL_sig_alg_parameter, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_issuer_name, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_version, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, X509_CRL_get_ext, "(JLjava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, X509_REVOKED_get_ext, "(JLjava/lang/String;)J"),
+    NATIVE_METHOD(NativeCrypto, X509_REVOKED_dup, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, i2d_X509_REVOKED, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, X509_supported_extension, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, ASN1_TIME_to_Calendar, "(JLjava/util/Calendar;)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_CTX_new, "()J"),
+    NATIVE_METHOD(NativeCrypto, SSL_CTX_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_CTX_set_session_id_context, "(J[B)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_new, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_enable_tls_channel_id, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_tls_channel_id, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, SSL_use_OpenSSL_PrivateKey_for_tls_channel_id, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_use_PKCS8_PrivateKey_for_tls_channel_id, "(J[B)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_use_OpenSSL_PrivateKey, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_use_PrivateKey, "(J[B)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_use_certificate, "(J[[B)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_check_private_key, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_client_CA_list, "(J[[B)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_mode, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_mode, "(JJ)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_clear_mode, "(JJ)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_options, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_options, "(JJ)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_clear_options, "(JJ)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_cipher_lists, "(J[Ljava/lang/String;)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_verify, "(JI)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_session, "(JJ)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_session_creation_enabled, "(JZ)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_tlsext_host_name, "(JLjava/lang/String;)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_servername, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, SSL_do_handshake, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "IZ[B)I"),
+    NATIVE_METHOD(NativeCrypto, SSL_renegotiate, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_certificate, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_peer_cert_chain, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, SSL_read, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)I"),
+    NATIVE_METHOD(NativeCrypto, SSL_write, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_interrupt, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_shutdown, "(J" FILE_DESCRIPTOR SSL_CALLBACKS ")V"),
+    NATIVE_METHOD(NativeCrypto, SSL_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_SESSION_session_id, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_time, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_version, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, SSL_SESSION_cipher, "(J)Ljava/lang/String;"),
+    NATIVE_METHOD(NativeCrypto, SSL_SESSION_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, i2d_SSL_SESSION, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, d2i_SSL_SESSION, "([B)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_CTX_enable_npn, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_CTX_disable_npn, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_npn_negotiated_protocol, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, ERR_peek_last_error, "()J"),
 };
 
 void register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env) {
     JNI_TRACE("register_org_apache_harmony_xnet_provider_jsse_NativeCrypto");
-    // Register org.apache.harmony.xnet.provider.jsse.NativeCrypto methods
-    jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/NativeCrypto", sNativeCryptoMethods, NELEM(sNativeCryptoMethods));
+    jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/NativeCrypto",
+                             sNativeCryptoMethods, NELEM(sNativeCryptoMethods));
+
+    env->GetJavaVM(&gJavaVM);
+
+    ScopedLocalRef<jclass> localClass(env,
+            env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLBIOInputStream"));
+    openSslOutputStreamClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
+    if (openSslOutputStreamClass == NULL) {
+        ALOGE("failed to find class OpenSSLBIOInputStream");
+        abort();
+    }
+
+    calendar_setMethod = env->GetMethodID(JniConstants::calendarClass, "set", "(IIIIII)V");
+    inputStream_readMethod = env->GetMethodID(JniConstants::inputStreamClass, "read", "([B)I");
+    integer_valueOfMethod = env->GetStaticMethodID(JniConstants::integerClass, "valueOf",
+            "(I)Ljava/lang/Integer;");
+    openSslInputStream_readLineMethod = env->GetMethodID(openSslOutputStreamClass, "gets",
+            "([B)I");
+    outputStream_writeMethod = env->GetMethodID(JniConstants::outputStreamClass, "write", "([B)V");
+    outputStream_flushMethod = env->GetMethodID(JniConstants::outputStreamClass, "flush", "()V");
 }
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index d5705a6..7ea5033 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -5,6 +5,7 @@
 
 LOCAL_SRC_FILES := \
 	AsynchronousSocketCloseMonitor.cpp \
+	IcuUtilities.cpp \
 	JniConstants.cpp \
 	JniException.cpp \
 	NetworkUtilities.cpp \
@@ -30,6 +31,7 @@
 	java_util_zip_CRC32.cpp \
 	java_util_zip_Deflater.cpp \
 	java_util_zip_Inflater.cpp \
+	libcore_icu_AlphabeticIndex.cpp \
 	libcore_icu_ICU.cpp \
 	libcore_icu_NativeBreakIterator.cpp \
 	libcore_icu_NativeCollation.cpp \
@@ -38,7 +40,8 @@
 	libcore_icu_NativeIDN.cpp \
 	libcore_icu_NativeNormalizer.cpp \
 	libcore_icu_NativePluralRules.cpp \
-	libcore_icu_TimeZones.cpp \
+	libcore_icu_TimeZoneNames.cpp \
+	libcore_icu_Transliterator.cpp \
 	libcore_io_AsynchronousCloseMonitor.cpp \
 	libcore_io_Memory.cpp \
 	libcore_io_OsConstants.cpp \
@@ -48,6 +51,7 @@
 	org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp \
 	readlink.cpp \
 	realpath.cpp \
+	sun_misc_Unsafe.cpp \
 	toStringArray.cpp \
 	valueOf.cpp
 
diff --git a/luni/src/main/native/sun_misc_Unsafe.cpp b/luni/src/main/native/sun_misc_Unsafe.cpp
new file mode 100644
index 0000000..d0a23be
--- /dev/null
+++ b/luni/src/main/native/sun_misc_Unsafe.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Unsafe"
+
+#include "JNIHelp.h"
+#include "JniConstants.h"
+
+static jobject Unsafe_allocateInstance(JNIEnv* env, jclass, jclass c) {
+  return env->AllocObject(c);
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(Unsafe, allocateInstance, "(Ljava/lang/Class;)Ljava/lang/Object;"),
+};
+void register_sun_misc_Unsafe(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "sun/misc/Unsafe", gMethods, NELEM(gMethods));
+}
diff --git a/luni/src/main/native/toStringArray.cpp b/luni/src/main/native/toStringArray.cpp
index affd238..9640d22 100644
--- a/luni/src/main/native/toStringArray.cpp
+++ b/luni/src/main/native/toStringArray.cpp
@@ -66,3 +66,37 @@
     ArrayGetter getter(strings);
     return toStringArray(env, &counter, &getter);
 }
+
+/** Converts a Java String[] to a 0-terminated char**. */
+char** convertStrings(JNIEnv* env, jobjectArray javaArray) {
+    if (javaArray == NULL) {
+        return NULL;
+    }
+
+    jsize length = env->GetArrayLength(javaArray);
+    char** array = new char*[length + 1];
+    array[length] = 0;
+    for (jsize i = 0; i < length; ++i) {
+        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
+        // We need to pass these strings to const-unfriendly code.
+        char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry.get(), NULL));
+        array[i] = entry;
+    }
+
+    return array;
+}
+
+/** Frees a char** which was converted from a Java String[]. */
+void freeStrings(JNIEnv* env, jobjectArray javaArray, char** array) {
+    if (javaArray == NULL) {
+        return;
+    }
+
+    jsize length = env->GetArrayLength(javaArray);
+    for (jsize i = 0; i < length; ++i) {
+        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
+        env->ReleaseStringUTFChars(javaEntry.get(), array[i]);
+    }
+
+    delete[] array;
+}
diff --git a/luni/src/main/native/toStringArray.h b/luni/src/main/native/toStringArray.h
index 85efcb0..c65b8ad 100644
--- a/luni/src/main/native/toStringArray.h
+++ b/luni/src/main/native/toStringArray.h
@@ -64,3 +64,6 @@
 
 jobjectArray toStringArray(JNIEnv* env, const std::vector<std::string>& strings);
 jobjectArray toStringArray(JNIEnv* env, const char* const* strings);
+
+char** convertStrings(JNIEnv* env, jobjectArray javaArray);
+void freeStrings(JNIEnv* env, jobjectArray javaArray, char** array);
diff --git a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
new file mode 100644
index 0000000..5f34f3e
--- /dev/null
+++ b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2013 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.icu;
+
+import java.util.Locale;
+
+public class AlphabeticIndexTest extends junit.framework.TestCase {
+  private static AlphabeticIndex.ImmutableIndex createIndex(Locale locale) {
+    return new AlphabeticIndex(locale).addLabels(Locale.US)
+        .getImmutableIndex();
+  }
+
+  private static void assertHasLabel(AlphabeticIndex.ImmutableIndex ii, String string, String expectedLabel) {
+    int index = ii.getBucketIndex(string);
+    String label = ii.getBucketLabel(index);
+    assertEquals(expectedLabel, label);
+  }
+
+  public void test_en() throws Exception {
+    // English [A-Z]
+    AlphabeticIndex.ImmutableIndex en = createIndex(Locale.ENGLISH);
+    assertHasLabel(en, "Allen", "A");
+    assertHasLabel(en, "allen", "A");
+  }
+
+  public void test_ja() throws Exception {
+    AlphabeticIndex.ImmutableIndex ja = createIndex(Locale.JAPANESE);
+
+    // Japanese
+    //   sorts hiragana/katakana, Kanji/Chinese, English, other
+    // …, あ, か, さ, た, な, は, ま, や, ら, わ, …
+    // hiragana "a"
+    assertHasLabel(ja, "Allen", "A");
+    assertHasLabel(ja, "\u3041", "\u3042");
+    // katakana "a"
+    assertHasLabel(ja, "\u30a1", "\u3042");
+
+    // Kanji (sorts to inflow section)
+    assertHasLabel(ja, "\u65e5", "");
+
+    // English
+    assertHasLabel(ja, "Smith", "S");
+
+    // Chinese (sorts to inflow section)
+    assertHasLabel(ja, "\u6c88" /* Shen/Chen */, "");
+
+    // Korean Hangul (sorts to overflow section)
+    assertHasLabel(ja, "\u1100", "");
+  }
+
+  public void test_ko() throws Exception {
+    // Korean (sorts Korean, then English)
+    // …, ᄀ, ᄂ, ᄃ, ᄅ, ᄆ, ᄇ, ᄉ, ᄋ, ᄌ, ᄎ, ᄏ, ᄐ, ᄑ, ᄒ, …
+    AlphabeticIndex.ImmutableIndex ko = createIndex(Locale.KOREAN);
+    assertHasLabel(ko, "\u1100", "\u3131");
+    assertHasLabel(ko, "\u3131", "\u3131");
+    assertHasLabel(ko, "\u1101", "\u3131");
+    assertHasLabel(ko, "\u1161", "\u314e");
+  }
+
+  public void test_cs() throws Exception {
+    // Czech
+    // …, [A-C], Č,[D-H], CH, [I-R], Ř, S, Š, [T-Z], Ž, …
+    AlphabeticIndex.ImmutableIndex cs = createIndex(new Locale("cs"));
+    assertHasLabel(cs, "Cena", "C");
+    assertHasLabel(cs, "Čáp", "\u010c");
+    assertHasLabel(cs, "Ruda", "R");
+    assertHasLabel(cs, "Řada", "\u0158");
+    assertHasLabel(cs, "Selka", "S");
+    assertHasLabel(cs, "Šála", "\u0160");
+    assertHasLabel(cs, "Zebra", "Z");
+    assertHasLabel(cs, "Žába", "\u017d");
+    assertHasLabel(cs, "Chata", "CH");
+  }
+
+  public void test_fr() throws Exception {
+    // French: [A-Z] (no accented chars)
+    AlphabeticIndex.ImmutableIndex fr = createIndex(Locale.FRENCH);
+    assertHasLabel(fr, "Øfer", "O");
+    assertHasLabel(fr, "Œster", "O");
+  }
+
+  public void test_da() throws Exception {
+    // Danish: [A-Z], Æ, Ø, Å
+    AlphabeticIndex.ImmutableIndex da = createIndex(new Locale("da"));
+    assertHasLabel(da, "Ænes", "\u00c6");
+    assertHasLabel(da, "Øfer", "\u00d8");
+    assertHasLabel(da, "Œster", "\u00d8");
+    assertHasLabel(da, "Ågård", "\u00c5");
+  }
+
+  public void test_de() throws Exception {
+    // German: [A-S,Sch,St,T-Z] (no ß or umlauted characters in standard alphabet)
+    AlphabeticIndex.ImmutableIndex de = createIndex(Locale.GERMAN);
+    assertHasLabel(de, "ßind", "S");
+    assertHasLabel(de, "Sacher", "S");
+    assertHasLabel(de, "Schiller", "Sch");
+    assertHasLabel(de, "Steiff", "St");
+  }
+
+  public void test_th() throws Exception {
+    // Thai (sorts English then Thai)
+    // …, ก, ข, ฃ, ค, ฅ, ฆ, ง, จ, ฉ, ช, ซ, ฌ, ญ, ฎ, ฏ, ฐ, ฑ, ฒ, ณ, ด, ต, ถ, ท, ธ, น, บ, ป, ผ, ฝ, พ, ฟ, ภ, ม, ย, ร, ฤ, ล, ฦ, ว, ศ, ษ, ส, ห, ฬ, อ, ฮ, …,
+    AlphabeticIndex.ImmutableIndex th = createIndex(new Locale("th"));
+    assertHasLabel(th, "\u0e2d\u0e07\u0e04\u0e4c\u0e40\u0e25\u0e47\u0e01", "\u0e2d");
+    assertHasLabel(th, "\u0e2a\u0e34\u0e07\u0e2b\u0e40\u0e2a\u0e19\u0e35", "\u0e2a");
+  }
+
+  public void test_ar() throws Exception {
+    // Arabic (sorts English then Arabic)
+    // …, ا, ب, ت, ث, ج, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ك, ل, م, ن, ه, و, ي, …
+    AlphabeticIndex.ImmutableIndex ar = createIndex(new Locale("ar"));
+    assertHasLabel(ar, "\u0646\u0648\u0631", /* Noor */ "\u0646");
+  }
+
+  public void test_he() throws Exception {
+    // Hebrew (sorts English then Hebrew)
+    // …, א, ב, ג, ד, ה, ו, ז, ח, ט, י, כ, ל, מ, נ, ס, ע, פ, צ, ק, ר, ש, ת, …
+    AlphabeticIndex.ImmutableIndex he = createIndex(new Locale("he"));
+    assertHasLabel(he, "\u05e4\u05e8\u05d9\u05d3\u05de\u05df", "\u05e4");
+  }
+
+  public void test_zh_CN() throws Exception {
+    // Simplified Chinese (default collator Pinyin): [A-Z]
+    // Shen/Chen (simplified): should be, usually, 'S' for name collator and 'C' for apps/other
+    AlphabeticIndex.ImmutableIndex zh_CN = createIndex(new Locale("zh", "CN"));
+
+    // Jia/Gu: should be, usually, 'J' for name collator and 'G' for apps/other
+    assertHasLabel(zh_CN, "\u8d3e", "J");
+
+    // Shen/Chen
+    assertHasLabel(zh_CN, "\u6c88", "C"); // icu4c 50 does not specialize for names.
+    // Shen/Chen (traditional)
+    assertHasLabel(zh_CN, "\u700b", "S");
+  }
+
+  public void test_zh_TW() throws Exception {
+    // Traditional Chinese
+    // …, [1-33, 35, 36, 39, 48]劃, …
+    // Shen/Chen
+    AlphabeticIndex.ImmutableIndex zh_TW = createIndex(new Locale("zh", "TW"));
+    assertHasLabel(zh_TW, "\u6c88", "7\u5283");
+    assertHasLabel(zh_TW, "\u700b", "18\u5283");
+    // Jia/Gu
+    assertHasLabel(zh_TW, "\u8d3e", "10\u5283");
+  }
+
+  public void test_constructor_NPE() throws Exception {
+    try {
+      new AlphabeticIndex(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void test_addLabels_NPE() throws Exception {
+    AlphabeticIndex ai = new AlphabeticIndex(Locale.US);
+    try {
+      ai.addLabels(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void test_getBucketIndex_NPE() throws Exception {
+    AlphabeticIndex.ImmutableIndex ii = createIndex(Locale.US);
+    try {
+      ii.getBucketIndex(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void test_getBucketLabel_invalid() throws Exception {
+    AlphabeticIndex.ImmutableIndex ii = createIndex(Locale.US);
+    try {
+      ii.getBucketLabel(-1);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      ii.getBucketLabel(123456);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+}
diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java
index a7e732c..9d9892c 100644
--- a/luni/src/test/java/libcore/icu/ICUTest.java
+++ b/luni/src/test/java/libcore/icu/ICUTest.java
@@ -40,6 +40,15 @@
         assertNotNull(ICU.getAvailableLocales()[0]);
     }
 
+    public void test_getBestDateTimePattern() throws Exception {
+        assertEquals("d MMMM", ICU.getBestDateTimePattern("MMMMd", "ca_ES"));
+        assertEquals("d 'de' MMMM", ICU.getBestDateTimePattern("MMMMd", "es_ES"));
+        assertEquals("d. MMMM", ICU.getBestDateTimePattern("MMMMd", "de_CH"));
+        assertEquals("MMMM d", ICU.getBestDateTimePattern("MMMMd", "en_US"));
+        assertEquals("d LLLL", ICU.getBestDateTimePattern("MMMMd", "fa_IR"));
+        assertEquals("M月d日", ICU.getBestDateTimePattern("MMMMd", "ja_JP"));
+    }
+
     public void test_localeFromString() throws Exception {
         // localeFromString is pretty lenient. Some of these can't be round-tripped
         // through Locale.toString.
diff --git a/luni/src/test/java/libcore/icu/LocaleDataTest.java b/luni/src/test/java/libcore/icu/LocaleDataTest.java
index e412a1d..27eda86 100644
--- a/luni/src/test/java/libcore/icu/LocaleDataTest.java
+++ b/luni/src/test/java/libcore/icu/LocaleDataTest.java
@@ -23,7 +23,7 @@
         // Test that we can get the locale data for all known locales.
         for (Locale l : Locale.getAvailableLocales()) {
             LocaleData d = LocaleData.get(l);
-            System.err.println(l + " : " + d.yesterday + " " + d.today + " " + d.tomorrow);
+            // System.err.format("%10s %10s %10s\n", l, d.timeFormat12, d.timeFormat24);
         }
     }
 
@@ -65,11 +65,11 @@
         LocaleData l = LocaleData.get(new Locale("cs", "CZ"));
 
         assertEquals("ledna", l.longMonthNames[0]);
-        assertEquals("Led", l.shortMonthNames[0]);
+        assertEquals("led", l.shortMonthNames[0]);
         assertEquals("1", l.tinyMonthNames[0]);
 
         assertEquals("leden", l.longStandAloneMonthNames[0]);
-        assertEquals("1.", l.shortStandAloneMonthNames[0]);
+        assertEquals("led", l.shortStandAloneMonthNames[0]);
         assertEquals("l", l.tinyStandAloneMonthNames[0]);
     }
 
@@ -78,11 +78,45 @@
 
         assertEquals("воскресенье", l.longWeekdayNames[1]);
         assertEquals("вс", l.shortWeekdayNames[1]);
-        assertEquals("В", l.tinyWeekdayNames[1]);
+        assertEquals("вс", l.tinyWeekdayNames[1]);
 
         // Russian stand-alone weekday names get an initial capital.
         assertEquals("Воскресенье", l.longStandAloneWeekdayNames[1]);
         assertEquals("Вс", l.shortStandAloneWeekdayNames[1]);
         assertEquals("В", l.tinyStandAloneWeekdayNames[1]);
     }
+
+    // http://code.google.com/p/android/issues/detail?id=38844
+    public void testDecimalFormatSymbols_es() throws Exception {
+        LocaleData es = LocaleData.get(new Locale("es"));
+        assertEquals(',', es.decimalSeparator);
+        assertEquals('.', es.groupingSeparator);
+
+        LocaleData es_419 = LocaleData.get(new Locale("es", "419"));
+        assertEquals('.', es_419.decimalSeparator);
+        assertEquals(',', es_419.groupingSeparator);
+
+        LocaleData es_US = LocaleData.get(new Locale("es", "US"));
+        assertEquals('.', es_US.decimalSeparator);
+        assertEquals(',', es_US.groupingSeparator);
+
+        LocaleData es_MX = LocaleData.get(new Locale("es", "MX"));
+        assertEquals('.', es_MX.decimalSeparator);
+        assertEquals(',', es_MX.groupingSeparator);
+
+        LocaleData es_AR = LocaleData.get(new Locale("es", "AR"));
+        assertEquals(',', es_AR.decimalSeparator);
+        assertEquals('.', es_AR.groupingSeparator);
+    }
+
+    // http://b/7924970
+    public void testTimeFormat12And24() throws Exception {
+        LocaleData en_US = LocaleData.get(Locale.US);
+        assertEquals("h:mm a", en_US.timeFormat12);
+        assertEquals("HH:mm", en_US.timeFormat24);
+
+        LocaleData ja_JP = LocaleData.get(Locale.JAPAN);
+        assertEquals("aK:mm", ja_JP.timeFormat12);
+        assertEquals("H:mm", ja_JP.timeFormat24);
+    }
 }
diff --git a/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java b/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
new file mode 100644
index 0000000..da8e035
--- /dev/null
+++ b/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 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.icu;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.HashSet;
+import java.util.TimeZone;
+
+public class TimeZoneNamesTest extends junit.framework.TestCase {
+  public void test_forLocale() throws Exception {
+    String[] ids = TimeZoneNames.forLocale(Locale.CANADA);
+    // Check that we got some ids.
+    assertTrue(ids.length > 0);
+    HashSet<String> allIds = new HashSet<String>(Arrays.asList(TimeZone.getAvailableIDs()));
+    // Check that they're all real.
+    for (String id : ids) {
+      assertTrue(allIds.contains(id));
+    }
+    // Check that Toronto comes before Atikokan. http://b/8391426.
+    int toronto = linearSearch(ids, "America/Toronto");
+    assertTrue(toronto >= 0);
+    int atikokan = linearSearch(ids, "America/Atikokan");
+    assertTrue(atikokan >= 0);
+    assertTrue(toronto < atikokan);
+  }
+
+  private int linearSearch(String[] xs, String x) {
+    for (int i = 0; i < xs.length; ++i) {
+      if (xs[i].equals(x)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+}
diff --git a/luni/src/test/java/libcore/icu/TransliteratorTest.java b/luni/src/test/java/libcore/icu/TransliteratorTest.java
new file mode 100644
index 0000000..e4f7981
--- /dev/null
+++ b/luni/src/test/java/libcore/icu/TransliteratorTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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.icu;
+
+public class TransliteratorTest extends junit.framework.TestCase {
+  public void testAll() throws Exception {
+    for (String id : Transliterator.getAvailableIDs()) {
+      System.err.println(id);
+      Transliterator t = new Transliterator(id);
+      t.transliterate("hello");
+    }
+  }
+
+  public void test_Unknown() throws Exception {
+    try {
+      Transliterator t = new Transliterator("Unknown");
+      fail();
+    } catch (RuntimeException expected) {
+    }
+  }
+
+  public void test_null_id() throws Exception {
+    try {
+      Transliterator t = new Transliterator(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void test_null_string() throws Exception {
+    try {
+      Transliterator t = new Transliterator("Any-Upper");
+      t.transliterate(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void test_Any_Upper() throws Exception {
+    Transliterator t = new Transliterator("Any-Upper");
+    assertEquals("HELLO WORLD!", t.transliterate("HeLlO WoRlD!"));
+    assertEquals("STRASSE", t.transliterate("Straße"));
+  }
+
+  public void test_Any_Lower() throws Exception {
+    Transliterator t = new Transliterator("Any-Lower");
+    assertEquals("hello world!", t.transliterate("HeLlO WoRlD!"));
+  }
+
+  public void test_Greek_Latin() throws Exception {
+    String greek = "Καλημέρα κόσμε!";
+
+    // Transliterate Greek to Latin, then to plain ASCII.
+    Transliterator t = new Transliterator("Greek-Latin");
+    String latin = t.transliterate(greek);
+    t = new Transliterator("Latin-Ascii");
+    String ascii = t.transliterate(latin);
+    assertEquals("Kalēméra kósme!", latin);
+    assertEquals("Kalemera kosme!", ascii);
+
+    // Use alternative transliteration variants.
+    t = new Transliterator("Greek-Latin/BGN");
+    assertEquals("Kaliméra kósme!", t.transliterate(greek));
+    t = new Transliterator("Greek-Latin/UNGEGN");
+    assertEquals("Kali̱méra kósme!",t.transliterate(greek));
+  }
+
+  public void test_Han_Latin() throws Exception {
+    Transliterator t = new Transliterator("Han-Latin");
+    assertEquals("hàn zì/hàn zì", t.transliterate("汉字/漢字"));
+
+    assertEquals("chén", t.transliterate("\u6c88"));
+    assertEquals("shěn", t.transliterate("\u700b"));
+    assertEquals("jiǎ", t.transliterate("\u8d3e"));
+
+    t = new Transliterator("Han-Latin/Names");
+    assertEquals("shěn", t.transliterate("\u6c88"));
+    assertEquals("shěn", t.transliterate("\u700b"));
+    assertEquals("jǐa", t.transliterate("\u8d3e"));
+
+    t = new Transliterator("Han-Latin/Names; Latin-Ascii; Any-Upper");
+    assertEquals("SHEN", t.transliterate("\u6c88"));
+    assertEquals("SHEN", t.transliterate("\u700b"));
+    assertEquals("JIA", t.transliterate("\u8d3e"));
+  }
+}
diff --git a/luni/src/test/java/libcore/io/DiskLruCacheTest.java b/luni/src/test/java/libcore/io/DiskLruCacheTest.java
index 2796b65..7a5bfa5 100644
--- a/luni/src/test/java/libcore/io/DiskLruCacheTest.java
+++ b/luni/src/test/java/libcore/io/DiskLruCacheTest.java
@@ -579,6 +579,44 @@
         assertValue("B", "b", "b");
     }
 
+    /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */
+    public void testRebuildJournalOnRepeatedReadsWithOpenAndClose() throws Exception {
+        set("a", "a", "a");
+        set("b", "b", "b");
+        long lastJournalLength = 0;
+        while (true) {
+            long journalLength = journalFile.length();
+            assertValue("a", "a", "a");
+            assertValue("b", "b", "b");
+            cache.close();
+            cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+            if (journalLength < lastJournalLength) {
+                System.out.printf("Journal compacted from %s bytes to %s bytes\n",
+                        lastJournalLength, journalLength);
+                break; // test passed!
+            }
+            lastJournalLength = journalLength;
+        }
+    }
+
+    /** @see <a href="https://github.com/JakeWharton/DiskLruCache/issues/28">Issue #28</a> */
+    public void testRebuildJournalOnRepeatedEditsWithOpenAndClose() throws Exception {
+        long lastJournalLength = 0;
+        while (true) {
+            long journalLength = journalFile.length();
+            set("a", "a", "a");
+            set("b", "b", "b");
+            cache.close();
+            cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE);
+            if (journalLength < lastJournalLength) {
+                System.out.printf("Journal compacted from %s bytes to %s bytes\n",
+                        lastJournalLength, journalLength);
+                break;
+            }
+            lastJournalLength = journalLength;
+        }
+    }
+
     public void testOpenCreatesDirectoryIfNecessary() throws Exception {
         cache.close();
         File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary");
diff --git a/luni/src/test/java/libcore/io/OsTest.java b/luni/src/test/java/libcore/io/OsTest.java
index e6446ae..5e0e8c7 100644
--- a/luni/src/test/java/libcore/io/OsTest.java
+++ b/luni/src/test/java/libcore/io/OsTest.java
@@ -17,21 +17,121 @@
 package libcore.io;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.InetUnixAddress;
 import java.net.ServerSocket;
+import java.net.SocketAddress;
 import junit.framework.TestCase;
 
 import static libcore.io.OsConstants.*;
 
 public class OsTest extends TestCase {
-    public void testIsSocket() throws Exception {
-        File f = new File("/dev/null");
-        FileInputStream fis = new FileInputStream(f);
-        assertFalse(S_ISSOCK(Libcore.os.fstat(fis.getFD()).st_mode));
-        fis.close();
+  public void testIsSocket() throws Exception {
+    File f = new File("/dev/null");
+    FileInputStream fis = new FileInputStream(f);
+    assertFalse(S_ISSOCK(Libcore.os.fstat(fis.getFD()).st_mode));
+    fis.close();
 
-        ServerSocket s = new ServerSocket();
-        assertTrue(S_ISSOCK(Libcore.os.fstat(s.getImpl$().getFD$()).st_mode));
-        s.close();
+    ServerSocket s = new ServerSocket();
+    assertTrue(S_ISSOCK(Libcore.os.fstat(s.getImpl$().getFD$()).st_mode));
+    s.close();
+  }
+
+  public void testUnixDomainSockets_in_file_system() throws Exception {
+    String path = System.getProperty("java.io.tmpdir") + "/test_unix_socket";
+    new File(path).delete();
+    checkUnixDomainSocket(new InetUnixAddress(path), false);
+  }
+
+  public void testUnixDomainSocket_abstract_name() throws Exception {
+    // Linux treats a sun_path starting with a NUL byte as an abstract name. See unix(7).
+    byte[] path = "/abstract_name_unix_socket".getBytes("UTF-8");
+    path[0] = 0;
+    checkUnixDomainSocket(new InetUnixAddress(path), true);
+  }
+
+  private void checkUnixDomainSocket(final InetUnixAddress address, final boolean isAbstract) throws Exception {
+    final FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
+    Libcore.os.bind(serverFd, address, 0);
+    Libcore.os.listen(serverFd, 5);
+
+    checkSockName(serverFd, isAbstract, address);
+
+    Thread server = new Thread(new Runnable() {
+      public void run() {
+        try {
+          InetSocketAddress peerAddress = new InetSocketAddress();
+          FileDescriptor clientFd = Libcore.os.accept(serverFd, peerAddress);
+          checkSockName(clientFd, isAbstract, address);
+          checkNoName(peerAddress);
+
+          checkNoPeerName(clientFd);
+
+          StructUcred credentials = Libcore.os.getsockoptUcred(clientFd, SOL_SOCKET, SO_PEERCRED);
+          assertEquals(Libcore.os.getpid(), credentials.pid);
+          assertEquals(Libcore.os.getuid(), credentials.uid);
+          assertEquals(Libcore.os.getgid(), credentials.gid);
+
+          byte[] request = new byte[256];
+          int requestLength = Libcore.os.read(clientFd, request, 0, request.length);
+
+          String s = new String(request, "UTF-8");
+          byte[] response = s.toUpperCase().getBytes("UTF-8");
+          Libcore.os.write(clientFd, response, 0, response.length);
+
+          Libcore.os.close(clientFd);
+        } catch (Exception ex) {
+          throw new RuntimeException(ex);
+        }
+      }
+    });
+    server.start();
+
+    FileDescriptor clientFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
+
+    Libcore.os.connect(clientFd, address, 0);
+    checkNoSockName(clientFd);
+
+    String string = "hello, world!";
+
+    byte[] request = string.getBytes("UTF-8");
+    assertEquals(request.length, Libcore.os.write(clientFd, request, 0, request.length));
+
+    byte[] response = new byte[request.length];
+    assertEquals(response.length, Libcore.os.read(clientFd, response, 0, response.length));
+
+    assertEquals(string.toUpperCase(), new String(response, "UTF-8"));
+
+    Libcore.os.close(clientFd);
+  }
+
+  private void checkSockName(FileDescriptor fd, boolean isAbstract, InetAddress address) throws Exception {
+    InetSocketAddress isa = (InetSocketAddress) Libcore.os.getsockname(fd);
+    if (isAbstract) {
+      checkNoName(isa);
+    } else {
+      assertEquals(address, isa.getAddress());
     }
+  }
+
+  private void checkNoName(SocketAddress sa) {
+    InetSocketAddress isa = (InetSocketAddress) sa;
+    assertEquals(0, isa.getAddress().getAddress().length);
+  }
+
+  private void checkNoPeerName(FileDescriptor fd) throws Exception {
+    checkNoName(Libcore.os.getpeername(fd));
+  }
+
+  private void checkNoSockName(FileDescriptor fd) throws Exception {
+    checkNoName(Libcore.os.getsockname(fd));
+  }
+
+  public void test_strsignal() throws Exception {
+    assertEquals("Killed", Libcore.os.strsignal(9));
+    assertEquals("Unknown signal -1", Libcore.os.strsignal(-1));
+  }
 }
diff --git a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
index e46df5d..39c0ad3 100755
--- a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
+++ b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
@@ -47,7 +47,9 @@
     private Socket[] sockets;
 
     @Override protected void setUp() throws Exception {
-        Thread.interrupted(); // clear interrupted bit
+        // Clear the interrupted bit to make sure an earlier test did
+        // not leave us in a bad state.
+        Thread.interrupted();
         super.tearDown();
     }
 
@@ -123,7 +125,7 @@
             fail();
         } catch (InterruptedIOException expected) {
         } finally {
-            waitForInterrupt(thread);
+            confirmInterrupted(thread);
         }
     }
 
@@ -134,7 +136,7 @@
             fail();
         } catch (InterruptedIOException expected) {
         } finally {
-            waitForInterrupt(thread);
+            confirmInterrupted(thread);
         }
     }
 
@@ -145,7 +147,7 @@
             fail();
         } catch (ClosedByInterruptException expected) {
         } finally {
-            waitForInterrupt(thread);
+            confirmInterrupted(thread);
         }
     }
 
@@ -158,7 +160,7 @@
             }
         } catch (InterruptedIOException expected) {
         } finally {
-            waitForInterrupt(thread);
+            confirmInterrupted(thread);
         }
     }
 
@@ -171,7 +173,7 @@
             }
         } catch (InterruptedIOException expected) {
         } finally {
-            waitForInterrupt(thread);
+            confirmInterrupted(thread);
         }
     }
 
@@ -185,7 +187,7 @@
         } catch (ClosedByInterruptException expected) {
         } catch (ClosedChannelException expected) {
         } finally {
-            waitForInterrupt(thread);
+            confirmInterrupted(thread);
         }
     }
 
@@ -204,15 +206,9 @@
         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
-        }
+    private static void confirmInterrupted(Thread thread) throws InterruptedException {
+        // validate and clear interrupted bit before join
+        assertTrue(Thread.interrupted());
+        thread.join();
     }
 }
diff --git a/luni/src/test/java/libcore/java/lang/CharacterTest.java b/luni/src/test/java/libcore/java/lang/CharacterTest.java
index 5dbc28e..9438f20 100644
--- a/luni/src/test/java/libcore/java/lang/CharacterTest.java
+++ b/luni/src/test/java/libcore/java/lang/CharacterTest.java
@@ -74,8 +74,9 @@
 
     public void test_getName() throws Exception {
         // Character.getName requires the corresponding ICU data.
-        assertEquals("NULL", Character.getName(0x0000));
-        assertEquals("BELL", Character.getName(0x0007));
+        // Changed from "NULL" and "BELL" by Unicode 49.2
+        assertEquals("<control-0000>", Character.getName(0x0000));
+        assertEquals("<control-0007>", Character.getName(0x0007));
         assertEquals("LATIN SMALL LETTER L", Character.getName('l'));
         // This changed name from Unicode 1.0. Used to be "OPENING...".
         assertEquals("LEFT CURLY BRACKET", Character.getName('{'));
diff --git a/luni/src/test/java/libcore/java/lang/OldClassTest.java b/luni/src/test/java/libcore/java/lang/OldClassTest.java
index c516467..0f7ce42 100644
--- a/luni/src/test/java/libcore/java/lang/OldClassTest.java
+++ b/luni/src/test/java/libcore/java/lang/OldClassTest.java
@@ -989,12 +989,12 @@
         Class clazz = getClass();
 
         InputStream stream = clazz.getResourceAsStream("HelloWorld.txt");
-        assert(stream != null);
+        assertNotNull(stream);
 
         byte[] buffer = new byte[20];
         int length = stream.read(buffer);
         String s = new String(buffer, 0, length);
-        assert("Hello, World.".equals(s));
+        assertEquals("Hello, World.\n",  s);
 
         stream.close();
     }
@@ -1003,12 +1003,12 @@
         Class clazz = getClass();
 
         InputStream stream = clazz.getResourceAsStream("/libcore/java/lang/HelloWorld.txt");
-        assert(stream != null);
+        assertNotNull(stream);
 
         byte[] buffer = new byte[20];
         int length = stream.read(buffer);
         String s = new String(buffer, 0, length);
-        assert("Hello, World.".equals(s));
+        assertEquals("Hello, World.\n", s);
 
         stream.close();
 
diff --git a/luni/src/test/java/libcore/java/lang/OldThreadTest.java b/luni/src/test/java/libcore/java/lang/OldThreadTest.java
index 2b304f2..03031ac 100644
--- a/luni/src/test/java/libcore/java/lang/OldThreadTest.java
+++ b/luni/src/test/java/libcore/java/lang/OldThreadTest.java
@@ -307,6 +307,7 @@
         assertNotNull(state);
         assertEquals(Thread.State.RUNNABLE, state);
 
+        run = true;
         final Semaphore sem = new Semaphore(0);
         final Object lock = new Object();
         Thread th = new Thread() {
@@ -369,7 +370,7 @@
         th.join(1000);
         assertEquals(Thread.State.TERMINATED, th.getState());
     }
-    volatile boolean run = true;
+    volatile boolean run;
 
     public void test_holdsLock() {
         MonitoredClass monitor = new MonitoredClass();
diff --git a/luni/src/test/java/libcore/java/lang/StringTest.java b/luni/src/test/java/libcore/java/lang/StringTest.java
index 99dba49..5aa596f 100644
--- a/luni/src/test/java/libcore/java/lang/StringTest.java
+++ b/luni/src/test/java/libcore/java/lang/StringTest.java
@@ -25,6 +25,7 @@
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
 import java.util.Arrays;
 import java.util.Locale;
 import junit.framework.TestCase;
@@ -228,6 +229,7 @@
         static String literal = "[5058, 9962, 1563, 5744]";
     }
 
+    private static final String COMBINING_DOT_ABOVE = "\u0307";
     private static final String LATIN_CAPITAL_I = "I";
     private static final String LATIN_CAPITAL_I_WITH_DOT_ABOVE = "\u0130";
     private static final String LATIN_SMALL_I = "i";
@@ -265,8 +267,9 @@
         assertEquals(LATIN_SMALL_DOTLESS_I, LATIN_SMALL_DOTLESS_I.toLowerCase(enUs));
 
         assertEquals(LATIN_CAPITAL_I, LATIN_SMALL_DOTLESS_I.toUpperCase(enUs));
-        // http://b/3325799: Android fails this with an extra combining "dot above".
-        assertEquals(LATIN_SMALL_I, LATIN_CAPITAL_I_WITH_DOT_ABOVE.toLowerCase(enUs));
+        // http://b/3325799: the RI fails this because it's using an obsolete version of the Unicode rules.
+        // Android correctly preserves canonical equivalence. (See the separate test for tr_TR.)
+        assertEquals(LATIN_SMALL_I + COMBINING_DOT_ABOVE, LATIN_CAPITAL_I_WITH_DOT_ABOVE.toLowerCase(enUs));
     }
 
     public void testEqualsIgnoreCase_tr_TR() {
@@ -324,4 +327,18 @@
     public void test_replaceAll() throws Exception {
         assertEquals("project_Id", "projectId".replaceAll("(?!^)(\\p{Upper})(?!$)", "_$1"));
     }
+
+    // https://code.google.com/p/android/issues/detail?id=23831
+    public void test_23831() throws Exception {
+        byte[] bytes = { (byte) 0xf5, (byte) 0xa9, (byte) 0xea, (byte) 0x21 };
+        String expected = "\ufffd\ufffd\u0021";
+
+        // Since we use icu4c for CharsetDecoder...
+        CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
+        decoder.onMalformedInput(CodingErrorAction.REPLACE);
+        assertEquals(expected, decoder.decode(ByteBuffer.wrap(bytes)).toString());
+
+        // Our fast-path code in String should behave the same...
+        assertEquals(expected, new String(bytes, "UTF-8"));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/lang/reflect/ProxyTest.java b/luni/src/test/java/libcore/java/lang/reflect/ProxyTest.java
index b6ae8b3..30d47fb 100644
--- a/luni/src/test/java/libcore/java/lang/reflect/ProxyTest.java
+++ b/luni/src/test/java/libcore/java/lang/reflect/ProxyTest.java
@@ -16,6 +16,7 @@
 
 package libcore.java.lang.reflect;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
@@ -63,4 +64,19 @@
             return args[0];
         }
     }
+
+    // https://code.google.com/p/android/issues/detail?id=24846
+    public void test24846() throws Exception {
+      ClassLoader cl = getClass().getClassLoader();
+      Class[] interfaces = { java.beans.PropertyChangeListener.class };
+      Object proxy = Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+          return null;
+        }
+      });
+      for (Field field : proxy.getClass().getDeclaredFields()) {
+        field.setAccessible(true);
+        assertFalse(field.isAnnotationPresent(Deprecated.class));
+      }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/math/BigDecimalTest.java b/luni/src/test/java/libcore/java/math/BigDecimalTest.java
index e771e9f..e9c2b0c 100644
--- a/luni/src/test/java/libcore/java/math/BigDecimalTest.java
+++ b/luni/src/test/java/libcore/java/math/BigDecimalTest.java
@@ -64,4 +64,28 @@
         BigDecimal rounded = bigDecimal.round(new MathContext(2, RoundingMode.FLOOR));
         assertEquals("0.99", rounded.toString());
     }
+
+    // https://code.google.com/p/android/issues/detail?id=43480
+    public void testPrecisionFromString() {
+      BigDecimal a = new BigDecimal("-0.011111111111111111111");
+      BigDecimal b = a.multiply(BigDecimal.ONE);
+
+      assertEquals("-0.011111111111111111111", a.toString());
+      assertEquals("-0.011111111111111111111", b.toString());
+
+      assertEquals(20, a.precision());
+      assertEquals(20, b.precision());
+
+      assertEquals(21, a.scale());
+      assertEquals(21, b.scale());
+
+      assertEquals("-11111111111111111111", a.unscaledValue().toString());
+      assertEquals("-11111111111111111111", b.unscaledValue().toString());
+
+      assertEquals(a, b);
+      assertEquals(b, a);
+
+      assertEquals(0, a.subtract(b).signum());
+      assertEquals(0, a.compareTo(b));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java b/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java
index 99a479c2..a629271 100644
--- a/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java
+++ b/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java
@@ -176,19 +176,29 @@
     }
 
     public void test_write() throws Exception {
-        final SilentServer ss = new SilentServer();
+        final SilentServer ss = new SilentServer(128); // Minimal receive buffer size.
         Socket s = new Socket();
+
+        // Set the send buffer size really small, to ensure we block.
+        int sendBufferSize = 1024;
+        s.setSendBufferSize(sendBufferSize);
+        sendBufferSize = s.getSendBufferSize(); // How big is the buffer really, Linux?
+
+        // Linux still seems to accept more than it should.
+        // How much seems to differ from device to device, but I've yet to see anything accept
+        // twice as much again.
+        sendBufferSize *= 2;
+
         s.connect(ss.getLocalSocketAddress());
         new Killer(s).start();
         try {
             System.err.println("write...");
-            // We just keep writing here until all the buffers are full and we block,
-            // waiting for the server to read (which it never will). If the asynchronous close
-            // fails, we'll see a test timeout here.
-            while (true) {
-                byte[] buf = new byte[256*1024];
-                s.getOutputStream().write(buf);
-            }
+            // Write too much so the buffer is full and we block,
+            // waiting for the server to read (which it never will).
+            // If the asynchronous close fails, we'll see a test timeout here.
+            byte[] buf = new byte[sendBufferSize];
+            s.getOutputStream().write(buf);
+            fail();
         } catch (SocketException expected) {
             // We throw "Connection reset by peer", which I don't _think_ is a problem.
             // assertEquals("Socket closed", expected.getMessage());
@@ -204,7 +214,14 @@
         private Socket client;
 
         public SilentServer() throws IOException {
+            this(0);
+        }
+
+        public SilentServer(int receiveBufferSize) throws IOException {
             ss = new ServerSocket(0);
+            if (receiveBufferSize != 0) {
+                ss.setReceiveBufferSize(receiveBufferSize);
+            }
             new Thread(new Runnable() {
                 public void run() {
                     try {
diff --git a/luni/src/test/java/libcore/java/net/InetAddressTest.java b/luni/src/test/java/libcore/java/net/InetAddressTest.java
index edca1a6..c7617ab 100644
--- a/luni/src/test/java/libcore/java/net/InetAddressTest.java
+++ b/luni/src/test/java/libcore/java/net/InetAddressTest.java
@@ -77,6 +77,8 @@
         assertEquals("/1.2.3.4", InetAddress.parseNumericAddress("1.2.3.4").toString());
         // Regular IPv6.
         assertEquals("/2001:4860:800d::68", InetAddress.parseNumericAddress("2001:4860:800d::68").toString());
+        // Mapped IPv4
+        assertEquals("/127.0.0.1", InetAddress.parseNumericAddress("::ffff:127.0.0.1").toString());
         // Optional square brackets around IPv6 addresses, including mapped IPv4.
         assertEquals("/2001:4860:800d::68", InetAddress.parseNumericAddress("[2001:4860:800d::68]").toString());
         assertEquals("/127.0.0.1", InetAddress.parseNumericAddress("[::ffff:127.0.0.1]").toString());
@@ -101,9 +103,22 @@
     }
 
     public void test_isNumeric() throws Exception {
+        // IPv4
         assertTrue(InetAddress.isNumeric("1.2.3.4"));
         assertTrue(InetAddress.isNumeric("127.0.0.1"));
 
+        // IPv6
+        assertTrue(InetAddress.isNumeric("::1"));
+        assertTrue(InetAddress.isNumeric("2001:4860:800d::68"));
+
+        // Mapped IPv4
+        assertTrue(InetAddress.isNumeric("::ffff:127.0.0.1"));
+
+        // Optional square brackets around IPv6 addresses, including mapped IPv4.
+        assertTrue(InetAddress.isNumeric("[2001:4860:800d::68]"));
+        assertTrue(InetAddress.isNumeric("[::ffff:127.0.0.1]"));
+
+        // Negative test
         assertFalse(InetAddress.isNumeric("example.com"));
 
         for (String invalid : INVALID_IPv4_NUMERIC_ADDRESSES) {
@@ -113,6 +128,7 @@
 
     public void test_isLinkLocalAddress() throws Exception {
         assertFalse(InetAddress.getByName("127.0.0.1").isLinkLocalAddress());
+        assertFalse(InetAddress.getByName("::ffff:127.0.0.1").isLinkLocalAddress());
         assertTrue(InetAddress.getByName("169.254.1.2").isLinkLocalAddress());
 
         assertFalse(InetAddress.getByName("fec0::").isLinkLocalAddress());
@@ -195,6 +211,9 @@
 
         assertEquals("127.0.0.1", Inet4Address.LOOPBACK.getHostAddress());
 
+        // IPv4 mapped address
+        assertEquals("127.0.0.1", InetAddress.getByName("::ffff:127.0.0.1").getHostAddress());
+
         InetAddress aAddr = InetAddress.getByName("224.0.0.0");
         assertEquals("224.0.0.0", aAddr.getHostAddress());
 
diff --git a/luni/src/test/java/libcore/java/net/URLTest.java b/luni/src/test/java/libcore/java/net/URLTest.java
index 962088e..a18816b 100644
--- a/luni/src/test/java/libcore/java/net/URLTest.java
+++ b/luni/src/test/java/libcore/java/net/URLTest.java
@@ -19,6 +19,8 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URI;
 import java.net.URL;
 import junit.framework.TestCase;
 import libcore.util.SerializationTester;
@@ -694,5 +696,35 @@
         assertEquals("a_b.c.d.net", url.getHost());
     }
 
+    // http://b/7369778
+    public void testToURILeniantThrowsURISyntaxExceptionWithPartialTrailingEscape()
+            throws Exception {
+        // make sure if there a partial trailing escape that we don't throw the wrong exception
+        URL[] badUrls = new URL[] {
+            new URL("http://example.com/?foo=%%bar"),
+            new URL("http://example.com/?foo=%%bar%"),
+            new URL("http://example.com/?foo=%%bar%2"),
+            new URL("http://example.com/?foo=%%bar%%"),
+            new URL("http://example.com/?foo=%%bar%%%"),
+            new URL("http://example.com/?foo=%%bar%%%%"),
+        };
+        for (URL badUrl : badUrls) {
+            try {
+                badUrl.toURILenient();
+                fail();
+            } catch (URISyntaxException expected) {
+            }
+        }
+
+        // make sure we properly handle an normal escape at the end of a string
+        String[] goodUrls = new String[] {
+            "http://example.com/?foo=bar",
+            "http://example.com/?foo=bar%20",
+        };
+        for (String goodUrl : goodUrls) {
+            assertEquals(new URI(goodUrl), new URL(goodUrl).toURILenient());
+        }
+    }
+
     // Adding a new test? Consider adding an equivalent test to URITest.java
 }
diff --git a/luni/src/test/java/libcore/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java
index 2a895fc..a4e5eaf 100644
--- a/luni/src/test/java/libcore/java/nio/BufferTest.java
+++ b/luni/src/test/java/libcore/java/nio/BufferTest.java
@@ -35,6 +35,62 @@
         return result;
     }
 
+    /**
+     * Try to create a {@link MappedByteBuffer} from /dev/zero, to see if
+     * we support mapping UNIX character devices.
+     */
+    public void testDevZeroMap() throws Exception {
+        RandomAccessFile raf = new RandomAccessFile("/dev/zero", "r");
+        try {
+            MappedByteBuffer mbb = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 65536);
+
+            // Create an array initialized to all "(byte) 1"
+            byte[] buf1 = new byte[65536];
+            Arrays.fill(buf1, (byte) 1);
+
+            // Read from mapped /dev/zero, and overwrite this array.
+            mbb.get(buf1);
+
+            // Verify that everything is zero
+            for (int i = 0; i < 65536; i++) {
+                assertEquals((byte) 0, buf1[i]);
+            }
+        } finally {
+            raf.close();
+        }
+    }
+
+    /**
+     * Same as {@link libcore.java.nio.BufferTest#testDevZeroMap()}, but try to see
+     * if we can write to the UNIX character device.
+     */
+    public void testDevZeroMapRW() throws Exception {
+        RandomAccessFile raf = new RandomAccessFile("/dev/zero", "rw");
+        try {
+            MappedByteBuffer mbb = raf.getChannel()
+                    .map(FileChannel.MapMode.READ_WRITE, 65536, 131072);
+
+            // Create an array initialized to all "(byte) 1"
+            byte[] buf1 = new byte[65536];
+            Arrays.fill(buf1, (byte) 1);
+
+            // Put all "(byte) 1"s into the /dev/zero MappedByteBuffer.
+            mbb.put(buf1);
+
+            mbb.position(0);
+
+            byte[] buf2 = new byte[65536];
+            mbb.get(buf2);
+
+            // Verify that everything is one
+            for (int i = 0; i < 65536; i++) {
+                assertEquals((byte) 1, buf2[i]);
+            }
+        } finally {
+            raf.close();
+        }
+    }
+
     public void testByteSwappedBulkGetDirect() throws Exception {
         testByteSwappedBulkGet(ByteBuffer.allocateDirect(10));
     }
@@ -658,7 +714,7 @@
 
     public void testHasArrayOnJniDirectByteBuffer() throws Exception {
         // Simulate a call to JNI's NewDirectByteBuffer.
-        Class<?> c = Class.forName("java.nio.ReadWriteDirectByteBuffer");
+        Class<?> c = Class.forName("java.nio.DirectByteBuffer");
         Constructor<?> ctor = c.getDeclaredConstructor(int.class, int.class);
         ctor.setAccessible(true);
         ByteBuffer bb = (ByteBuffer) ctor.newInstance(0, 0);
@@ -751,4 +807,52 @@
             assertTrue(expected.getMessage().contains("limit=0"));
         }
     }
+
+    public void testUsingDirectBufferAsMappedBuffer() throws Exception {
+        MappedByteBuffer notMapped = (MappedByteBuffer) ByteBuffer.allocateDirect(1);
+        try {
+            notMapped.force();
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+        try {
+            notMapped.isLoaded();
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+        try {
+            notMapped.load();
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+
+        MappedByteBuffer mapped = (MappedByteBuffer) allocateMapped(1);
+        mapped.force();
+        mapped.isLoaded();
+        mapped.load();
+    }
+
+    // https://code.google.com/p/android/issues/detail?id=53637
+    public void testBug53637() throws Exception {
+        MappedByteBuffer mapped = (MappedByteBuffer) allocateMapped(1);
+        mapped.get();
+        mapped.rewind();
+        mapped.get();
+
+        mapped.rewind();
+        mapped.mark();
+        mapped.get();
+        mapped.reset();
+        mapped.get();
+
+        mapped.rewind();
+        mapped.get();
+        mapped.clear();
+        mapped.get();
+
+        mapped.rewind();
+        mapped.get();
+        mapped.flip();
+        mapped.get();
+    }
 }
diff --git a/luni/src/test/java/libcore/java/nio/charset/CharsetTest.java b/luni/src/test/java/libcore/java/nio/charset/CharsetTest.java
index af9bc59..24daa52 100644
--- a/luni/src/test/java/libcore/java/nio/charset/CharsetTest.java
+++ b/luni/src/test/java/libcore/java/nio/charset/CharsetTest.java
@@ -21,6 +21,7 @@
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.util.Arrays;
+import java.util.ArrayList;
 
 public class CharsetTest extends junit.framework.TestCase {
     public void test_guaranteedCharsetsAvailable() throws Exception {
@@ -33,6 +34,28 @@
         assertNotNull(Charset.forName("UTF-8"));
     }
 
+    // http://code.google.com/p/android/issues/detail?id=42769
+    public void test_42769() throws Exception {
+        ArrayList<Thread> threads = new ArrayList<Thread>();
+        for (int i = 0; i < 10; ++i) {
+            Thread t = new Thread(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < 50; ++i) {
+                        Charset.availableCharsets();
+                    }
+                }
+            });
+            threads.add(t);
+        }
+
+        for (Thread t : threads) {
+            t.start();
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+    }
+
     public void test_allAvailableCharsets() throws Exception {
         // Check that we can instantiate every Charset, CharsetDecoder, and CharsetEncoder.
         for (String charsetName : Charset.availableCharsets().keySet()) {
diff --git a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
index cc90147..a86a884 100644
--- a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
@@ -16,6 +16,10 @@
 
 package libcore.java.security;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
 import java.security.Key;
 import java.security.KeyFactory;
@@ -30,7 +34,11 @@
 import java.security.interfaces.DSAParams;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.DSAParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
@@ -39,6 +47,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import javax.crypto.spec.DHParameterSpec;
 
 import junit.framework.TestCase;
 
@@ -54,16 +63,29 @@
                     continue;
                 }
                 String algorithm = service.getAlgorithm();
+                AlgorithmParameterSpec params = null;
+
+                // TODO: detect if we're running in vogar and run the full test
+                if ("DH".equals(algorithm)) {
+                    params = getDHParams();
+                }
+
                 try {
                     // KeyPairGenerator.getInstance(String)
                     KeyPairGenerator kpg1 = KeyPairGenerator.getInstance(algorithm);
                     assertEquals(algorithm, kpg1.getAlgorithm());
+                    if (params != null) {
+                        kpg1.initialize(params);
+                    }
                     test_KeyPairGenerator(kpg1);
 
                     // KeyPairGenerator.getInstance(String, Provider)
                     KeyPairGenerator kpg2 = KeyPairGenerator.getInstance(algorithm, provider);
                     assertEquals(algorithm, kpg2.getAlgorithm());
                     assertEquals(provider, kpg2.getProvider());
+                    if (params != null) {
+                        kpg2.initialize(params);
+                    }
                     test_KeyPairGenerator(kpg2);
 
                     // KeyPairGenerator.getInstance(String, String)
@@ -71,6 +93,9 @@
                                                                         provider.getName());
                     assertEquals(algorithm, kpg3.getAlgorithm());
                     assertEquals(provider, kpg3.getProvider());
+                    if (params != null) {
+                        kpg3.initialize(params);
+                    }
                     test_KeyPairGenerator(kpg3);
                 } catch (Exception e) {
                     throw new Exception("Problem testing KeyPairGenerator." + algorithm, e);
@@ -109,9 +134,21 @@
         putKeySize("DiffieHellman", 512);
         putKeySize("DiffieHellman", 512+64);
         putKeySize("DiffieHellman", 1024);
+        putKeySize("EC", 192);
+        putKeySize("EC", 224);
         putKeySize("EC", 256);
+        putKeySize("EC", 384);
+        putKeySize("EC", 521);
     }
 
+    /** Elliptic Curve Crypto named curves that should be supported. */
+    private static final String[] EC_NAMED_CURVES = {
+        // NIST P-192 aka SECG secp192r1 aka ANSI X9.62 prime192v1
+        "secp192r1", "prime192v1",
+        // NIST P-256 aka SECG secp256r1 aka ANSI X9.62 prime256v1
+        "secp256r1", "prime256v1",
+    };
+
     private void test_KeyPairGenerator(KeyPairGenerator kpg) throws Exception {
         // without a call to initialize
         test_KeyPair(kpg, kpg.genKeyPair());
@@ -132,6 +169,23 @@
             test_KeyPair(kpg, kpg.genKeyPair());
             test_KeyPair(kpg, kpg.generateKeyPair());
         }
+
+        if (("EC".equals(algorithm)) || ("ECDH".equals(algorithm))
+                || ("ECDSA".equals(algorithm))) {
+            for (String curveName : EC_NAMED_CURVES) {
+                kpg.initialize(new ECGenParameterSpec(curveName));
+                test_KeyPair(kpg, kpg.genKeyPair());
+                test_KeyPair(kpg, kpg.generateKeyPair());
+
+                kpg.initialize(new ECGenParameterSpec(curveName), (SecureRandom) null);
+                test_KeyPair(kpg, kpg.genKeyPair());
+                test_KeyPair(kpg, kpg.generateKeyPair());
+
+                kpg.initialize(new ECGenParameterSpec(curveName), new SecureRandom());
+                test_KeyPair(kpg, kpg.genKeyPair());
+                test_KeyPair(kpg, kpg.generateKeyPair());
+            }
+        }
     }
 
     private void test_KeyPair(KeyPairGenerator kpg, KeyPair kp) throws Exception {
@@ -149,6 +203,19 @@
         assertNotNull(k.getEncoded());
         assertNotNull(k.getFormat());
 
+        // Test serialization
+        {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(16384);
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(k);
+
+            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+            ObjectInputStream ois = new ObjectInputStream(bais);
+            Key inflatedKey = (Key) ois.readObject();
+
+            assertEquals(k, inflatedKey);
+        }
+
         test_KeyWithAllKeyFactories(k);
     }
 
@@ -172,8 +239,19 @@
                     PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
                     KeyFactory kf = KeyFactory.getInstance(k.getAlgorithm(), p);
                     PrivateKey privKey = kf.generatePrivate(spec);
-                    assertNotNull(privKey);
-                    assertTrue(Arrays.equals(privKey.getEncoded(), encoded));
+                    assertNotNull(k.getAlgorithm() + ", provider=" + p.getName(), privKey);
+
+                    /*
+                     * EC keys are unique because they can have explicit parameters or a curve
+                     * name. Check them specially so this test can continue to function.
+                     */
+                    if (k instanceof ECPrivateKey) {
+                        assertECPrivateKeyEquals((ECPrivateKey) k, (ECPrivateKey) privKey);
+                    } else {
+                        assertEquals(k.getAlgorithm() + ", provider=" + p.getName(),
+                                Arrays.toString(encoded),
+                                Arrays.toString(privKey.getEncoded()));
+                    }
                 } else if ("X.509".equals(k.getFormat())) {
                     X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
                     KeyFactory kf = KeyFactory.getInstance(k.getAlgorithm(), p);
@@ -185,6 +263,30 @@
         }
     }
 
+    private static void assertECPrivateKeyEquals(ECPrivateKey expected, ECPrivateKey actual) {
+        assertEquals(expected.getS(), actual.getS());
+        assertECParametersEquals(expected.getParams(), actual.getParams());
+    }
+
+    private static void assertECParametersEquals(ECParameterSpec expected, ECParameterSpec actual) {
+        assertEquals(expected.getCurve(), actual.getCurve());
+        assertEquals(expected.getGenerator(), actual.getGenerator());
+        assertEquals(expected.getOrder(), actual.getOrder());
+        assertEquals(expected.getCofactor(), actual.getCofactor());
+    }
+
+    /**
+     * DH parameters pre-generated so that the test doesn't take too long.
+     * These parameters were generated with:
+     *
+     * openssl gendh 512 | openssl dhparams -C
+     */
+    private static AlgorithmParameterSpec getDHParams() {
+        BigInteger p = new BigInteger("E7AB1768BD75CD24700960FFA32D3F1557344E587101237532CC641646ED7A7C104743377F6D46251698B665CE2A6CBAB6714C2569A7D2CA22C0CF03FA40AC93", 16);
+        BigInteger g = new BigInteger("02", 16);
+        return new DHParameterSpec(p, g, 512);
+    }
+
     private static final BigInteger DSA_P = new BigInteger(new byte[] {
         (byte) 0x00, (byte) 0x9e, (byte) 0x61, (byte) 0xc2, (byte) 0x89, (byte) 0xef, (byte) 0x77, (byte) 0xa9,
         (byte) 0x4e, (byte) 0x13, (byte) 0x67, (byte) 0x64, (byte) 0x1f, (byte) 0x09, (byte) 0x01, (byte) 0xfe,
diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java
index 78608d0..97a80a9 100644
--- a/luni/src/test/java/libcore/java/security/ProviderTest.java
+++ b/luni/src/test/java/libcore/java/security/ProviderTest.java
@@ -212,7 +212,7 @@
             assertEquals(1, position);
             SecureRandom sr = new SecureRandom();
             if (!sr.getAlgorithm().equals("SecureRandom1")) {
-                throw new IllegalStateException("Expected SecureRandom1");
+                throw new IllegalStateException("Expected SecureRandom1 was " + sr.getAlgorithm());
             }
         } finally {
             Security.removeProvider(srp.getName());
diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java
index 63daa88..4afc67d 100644
--- a/luni/src/test/java/libcore/java/security/SignatureTest.java
+++ b/luni/src/test/java/libcore/java/security/SignatureTest.java
@@ -16,6 +16,10 @@
 
 package libcore.java.security;
 
+import org.apache.harmony.xnet.provider.jsse.NativeCryptoTest;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLProvider;
+
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
@@ -27,6 +31,7 @@
 import java.security.Security;
 import java.security.Signature;
 import java.security.SignatureException;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.DSAPrivateKeySpec;
 import java.security.spec.DSAPublicKeySpec;
 import java.security.spec.InvalidKeySpecException;
@@ -38,6 +43,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+
 import junit.framework.TestCase;
 
 public class SignatureTest extends TestCase {
@@ -56,7 +62,7 @@
                 }
                 String algorithm = service.getAlgorithm();
                 try {
-                    KeyPair kp = keyPair(algorithm);
+                    KeyPair kp = keyPair(algorithm, provider.getName());
                     // Signature.getInstance(String)
                     Signature sig1 = Signature.getInstance(algorithm);
                     assertEquals(algorithm, sig1.getAlgorithm());
@@ -80,10 +86,82 @@
         }
     }
 
+    public void test_getInstance_OpenSSL_ENGINE() throws Exception {
+        final String pem_private = "-----BEGIN RSA PRIVATE KEY-----\n"
+                + "MIICXAIBAAKBgQDpm4KamxulJnycEzNONGM7p0CvAaoZxJEd5Dvio5b6BROdCtRN\n"
+                + "lEsB+9vtB5thkyDVC7N+IW0AjtyDE6h2QP+AWa+c4dh0RM2uNVXkUWPrA8C++GHv\n"
+                + "EDlxZzRGiQEMuippYfIyBVkO+4+GRvnkG4dKjzxrQYPqKUK3C4PgFW2FewIDAQAB\n"
+                + "AoGAGUTSBsk6X03fcr588TundD9uNr/2V1002Ufj1msdnKPJ8FXIiy+8QVWt/2Cw\n"
+                + "RQi2J3VhkAYrlUDex2rr8Qas3E9uuwKgg/MZ4EsJbnKKgkd7uBZfmZ2ogcNJ82u7\n"
+                + "teVijFpdsVLDa9aczEppt5sZzyTaBrovrRb+AIRDpMw3I0ECQQD3JkWeQUA9Is1V\n"
+                + "z0X/ly/kaQKQLlrwYNdiKF0qOpyTLAguI7asAS72Zj7fThk5bHLM+mmgYwkicIIb\n"
+                + "67J32GQbAkEA8fkXqEnwMFYSkRmT9M/qUkwWUsMW12/AoZFI5gwKNDHZYxytGGLw\n"
+                + "mC//0qKnyeUG00vz06vLApe4/Sq4ODe6IQJBALEGastF9ZtUuDsEciD2y8kRJlLb\n"
+                + "wSt4Ug3u13yN6uTHnzxdPFTLrDW1WsdcC1lEQp5rpwjIpxxR9f/FvVl2V40CQHOY\n"
+                + "F6EhkUjGFaCTo4b0PHCMQK3Q3PyWOmP0z+p2HfnJRpx+eoKH4YASjhfF9HoSmywd\n"
+                + "wKGCFD1s1ca7vb29gYECQH86GmYZsDoLNWurEVJbkmCr7X1+xwim6umdrNKR27P7\n"
+                + "F1y0Sa3YY+LiiRb+IRSWE/onlP+28LIzWGF4lcTfDMc=\n"
+                + "-----END RSA PRIVATE KEY-----";
+
+        final byte[] der_public = new byte[] {
+                (byte) 0x30, (byte) 0x81, (byte) 0x9F, (byte) 0x30, (byte) 0x0D, (byte) 0x06,
+                (byte) 0x09, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xF7,
+                (byte) 0x0D, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00,
+                (byte) 0x03, (byte) 0x81, (byte) 0x8D, (byte) 0x00, (byte) 0x30, (byte) 0x81,
+                (byte) 0x89, (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xE9,
+                (byte) 0x9B, (byte) 0x82, (byte) 0x9A, (byte) 0x9B, (byte) 0x1B, (byte) 0xA5,
+                (byte) 0x26, (byte) 0x7C, (byte) 0x9C, (byte) 0x13, (byte) 0x33, (byte) 0x4E,
+                (byte) 0x34, (byte) 0x63, (byte) 0x3B, (byte) 0xA7, (byte) 0x40, (byte) 0xAF,
+                (byte) 0x01, (byte) 0xAA, (byte) 0x19, (byte) 0xC4, (byte) 0x91, (byte) 0x1D,
+                (byte) 0xE4, (byte) 0x3B, (byte) 0xE2, (byte) 0xA3, (byte) 0x96, (byte) 0xFA,
+                (byte) 0x05, (byte) 0x13, (byte) 0x9D, (byte) 0x0A, (byte) 0xD4, (byte) 0x4D,
+                (byte) 0x94, (byte) 0x4B, (byte) 0x01, (byte) 0xFB, (byte) 0xDB, (byte) 0xED,
+                (byte) 0x07, (byte) 0x9B, (byte) 0x61, (byte) 0x93, (byte) 0x20, (byte) 0xD5,
+                (byte) 0x0B, (byte) 0xB3, (byte) 0x7E, (byte) 0x21, (byte) 0x6D, (byte) 0x00,
+                (byte) 0x8E, (byte) 0xDC, (byte) 0x83, (byte) 0x13, (byte) 0xA8, (byte) 0x76,
+                (byte) 0x40, (byte) 0xFF, (byte) 0x80, (byte) 0x59, (byte) 0xAF, (byte) 0x9C,
+                (byte) 0xE1, (byte) 0xD8, (byte) 0x74, (byte) 0x44, (byte) 0xCD, (byte) 0xAE,
+                (byte) 0x35, (byte) 0x55, (byte) 0xE4, (byte) 0x51, (byte) 0x63, (byte) 0xEB,
+                (byte) 0x03, (byte) 0xC0, (byte) 0xBE, (byte) 0xF8, (byte) 0x61, (byte) 0xEF,
+                (byte) 0x10, (byte) 0x39, (byte) 0x71, (byte) 0x67, (byte) 0x34, (byte) 0x46,
+                (byte) 0x89, (byte) 0x01, (byte) 0x0C, (byte) 0xBA, (byte) 0x2A, (byte) 0x69,
+                (byte) 0x61, (byte) 0xF2, (byte) 0x32, (byte) 0x05, (byte) 0x59, (byte) 0x0E,
+                (byte) 0xFB, (byte) 0x8F, (byte) 0x86, (byte) 0x46, (byte) 0xF9, (byte) 0xE4,
+                (byte) 0x1B, (byte) 0x87, (byte) 0x4A, (byte) 0x8F, (byte) 0x3C, (byte) 0x6B,
+                (byte) 0x41, (byte) 0x83, (byte) 0xEA, (byte) 0x29, (byte) 0x42, (byte) 0xB7,
+                (byte) 0x0B, (byte) 0x83, (byte) 0xE0, (byte) 0x15, (byte) 0x6D, (byte) 0x85,
+                (byte) 0x7B, (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01
+        };
+
+        // We only need to test this on the OpenSSL provider.
+        Provider p = Security.getProvider(OpenSSLProvider.PROVIDER_NAME);
+
+        /* ENGINE-based private key */
+        NativeCryptoTest.loadTestEngine();
+        OpenSSLEngine engine = OpenSSLEngine.getInstance(NativeCryptoTest.TEST_ENGINE_ID);
+        PrivateKey privKey = engine.getPrivateKeyById(pem_private);
+        assertTrue(privKey instanceof RSAPrivateKey);
+
+        /* Non-ENGINE-based public key */
+        KeyFactory kf = KeyFactory.getInstance("RSA", p);
+        PublicKey pubKey = kf.generatePublic(new X509EncodedKeySpec(der_public));
+
+        KeyPair kp = new KeyPair(pubKey, privKey);
+
+        Set<Provider.Service> services = p.getServices();
+        for (Provider.Service service : services) {
+            if ("Signature".equals(service.getType()) && service.getAlgorithm().contains("RSA")) {
+                Signature sig1 = Signature.getInstance(service.getAlgorithm(), p);
+                test_Signature(sig1, kp);
+            }
+        }
+
+    }
+
     private final Map<String, KeyPair> keypairAlgorithmToInstance
             = new HashMap<String, KeyPair>();
 
-    private KeyPair keyPair(String sigAlgorithm) throws Exception {
+    private KeyPair keyPair(String sigAlgorithm, String providerName) throws Exception {
         if (sigAlgorithm.endsWith("Encryption")) {
             sigAlgorithm = sigAlgorithm.substring(0, sigAlgorithm.length()-"Encryption".length());
         }
@@ -168,7 +246,7 @@
             + "34fdadc44326b9b3f3fa828652bab07f0362ac141c8c3784ebdec44e0b156a5e7bccdc81a56fe954"
             + "56ac8c0e4ae12d97");
 
-    private static byte[] hexToBytes(String s) {
+    public static byte[] hexToBytes(String s) {
         int len = s.length();
         byte[] data = new byte[len / 2];
         for (int i = 0; i < len; i += 2) {
diff --git a/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java b/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java
index e9a91dd..03482b4 100644
--- a/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java
+++ b/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java
@@ -16,11 +16,44 @@
 
 package libcore.java.security.cert;
 
+import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
+import com.android.org.bouncycastle.asn1.x509.X509Extensions;
+import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
+import com.android.org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
+import com.android.org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OptionalDataException;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertPath;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.security.auth.x500.X500Principal;
+
 import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
 
 public class CertificateFactoryTest extends TestCase {
 
@@ -84,10 +117,19 @@
             + "-----END CERTIFICATE-----";
 
     public void test_generateCertificate() throws Exception {
-        test_generateCertificate(CertificateFactory.getInstance("X509"));
-        test_generateCertificate(CertificateFactory.getInstance("X509", "BC"));
-        test_generateCertificate(CertificateFactory.getInstance("X509", "DRLCertFactory"));
+        Provider[] providers = Security.getProviders("CertificateFactory.X509");
+        for (Provider p : providers) {
+            CertificateFactory cf = CertificateFactory.getInstance("X509", p);
+            try {
+                test_generateCertificate(cf);
+                test_generateCertificate_InputStream_Offset_Correct(cf);
+                test_generateCertificate_InputStream_Empty(cf);
+            } catch (Exception e) {
+                throw new Exception("Problem testing " + p.getName(), e);
+            }
+        }
     }
+
     private void test_generateCertificate(CertificateFactory cf) throws Exception {
         byte[] valid = VALID_CERTIFICATE_PEM.getBytes();
         Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
@@ -101,4 +143,328 @@
         }
     }
 
+    private void test_generateCertificate_InputStream_Empty(CertificateFactory cf) throws Exception {
+        try {
+            Certificate c = cf.generateCertificate(new ByteArrayInputStream(new byte[0]));
+            if (!"BC".equals(cf.getProvider().getName())) {
+                fail("should throw CertificateException: " + cf.getProvider().getName());
+            }
+            assertNull(c);
+        } catch (CertificateException e) {
+            if ("BC".equals(cf.getProvider().getName())) {
+                fail("should return null: " + cf.getProvider().getName());
+            }
+        }
+    }
+
+    private void test_generateCertificate_InputStream_Offset_Correct(CertificateFactory cf)
+            throws Exception {
+        byte[] valid = VALID_CERTIFICATE_PEM.getBytes();
+
+        byte[] doubleCertificateData = new byte[valid.length * 2];
+        System.arraycopy(valid, 0, doubleCertificateData, 0, valid.length);
+        System.arraycopy(valid, 0, doubleCertificateData, valid.length, valid.length);
+        MeasuredInputStream certStream = new MeasuredInputStream(new ByteArrayInputStream(
+                doubleCertificateData));
+        Certificate certificate = cf.generateCertificate(certStream);
+        assertNotNull(certificate);
+        assertEquals(valid.length, certStream.getCount());
+    }
+
+    /**
+     * Proxy that counts the number of bytes read from an InputStream.
+     */
+    private static class MeasuredInputStream extends InputStream {
+        private long mCount = 0;
+
+        private long mMarked = 0;
+
+        private InputStream mStream;
+
+        public MeasuredInputStream(InputStream is) {
+            mStream = is;
+        }
+
+        public long getCount() {
+            return mCount;
+        }
+
+        @Override
+        public int read() throws IOException {
+            int nextByte = mStream.read();
+            mCount++;
+            return nextByte;
+        }
+
+        @Override
+        public int read(byte[] buffer) throws IOException {
+            int count = mStream.read(buffer);
+            mCount += count;
+            return count;
+        }
+
+        @Override
+        public int read(byte[] buffer, int offset, int length) throws IOException {
+            int count = mStream.read(buffer, offset, length);
+            mCount += count;
+            return count;
+        }
+
+        @Override
+        public long skip(long byteCount) throws IOException {
+            long count = mStream.skip(byteCount);
+            mCount += count;
+            return count;
+        }
+
+        @Override
+        public int available() throws IOException {
+            return mStream.available();
+        }
+
+        @Override
+        public void close() throws IOException {
+            mStream.close();
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            mMarked = mCount;
+            mStream.mark(readlimit);
+        }
+
+        @Override
+        public boolean markSupported() {
+            return mStream.markSupported();
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            mCount = mMarked;
+            mStream.reset();
+        }
+    }
+
+    /* CertPath tests */
+    public void testGenerateCertPath() throws Exception {
+        KeyHolder ca = generateCertificate(true, null);
+        KeyHolder cert1 = generateCertificate(true, ca);
+        KeyHolder cert2 = generateCertificate(false, cert1);
+        KeyHolder cert3 = generateCertificate(false, cert2);
+
+        List<X509Certificate> certs = new ArrayList<X509Certificate>();
+        certs.add(cert3.certificate);
+        certs.add(cert2.certificate);
+        certs.add(cert1.certificate);
+
+        List<X509Certificate> duplicatedCerts = new ArrayList<X509Certificate>(certs);
+        duplicatedCerts.add(cert2.certificate);
+
+        Provider[] providers = Security.getProviders("CertificateFactory.X509");
+        for (Provider p : providers) {
+            final CertificateFactory cf = CertificateFactory.getInstance("X.509", p);
+
+            // Duplicate certificates can cause an exception.
+            {
+                final CertPath duplicatedPath = cf.generateCertPath(duplicatedCerts);
+                try {
+                    duplicatedPath.getEncoded();
+                    if (StandardNames.IS_RI) {
+                        fail("duplicate certificates should cause failure: " + p.getName());
+                    }
+                } catch (CertificateEncodingException expected) {
+                    if (!StandardNames.IS_RI) {
+                        fail("duplicate certificates should pass: " + p.getName());
+                    }
+                }
+            }
+
+            testCertPathEncoding(cf, certs, null);
+
+            /* Make sure all encoding entries are the same. */
+            final Iterator<String> it1 = cf.getCertPathEncodings();
+            final Iterator<String> it2 = cf.generateCertPath(certs).getEncodings();
+            for (;;) {
+                assertEquals(p.getName(), it1.hasNext(), it2.hasNext());
+                if (!it1.hasNext()) {
+                    break;
+                }
+
+                String encoding = it1.next();
+                assertEquals(p.getName(), encoding, it2.next());
+
+                try {
+                    it1.remove();
+                    fail("Should not be able to remove from iterator");
+                } catch (UnsupportedOperationException expected) {
+                }
+
+                try {
+                    it2.remove();
+                    fail("Should not be able to remove from iterator");
+                } catch (UnsupportedOperationException expected) {
+                }
+
+                /* Now test using this encoding. */
+                testCertPathEncoding(cf, certs, encoding);
+            }
+        }
+    }
+
+    private void testCertPathEncoding(CertificateFactory cf, List<X509Certificate> expectedCerts,
+            String encoding) throws Exception {
+        final String providerName = cf.getProvider().getName() + "[" + encoding + "]";
+
+        final CertPath pathFromList = cf.generateCertPath(expectedCerts);
+
+        // Create a copy we can modify and discard.
+        final byte[] encodedCopy;
+        if (encoding == null) {
+            encodedCopy = pathFromList.getEncoded();
+            assertNotNull(providerName, encodedCopy);
+
+            // check idempotence
+            assertEquals(providerName, Arrays.toString(pathFromList.getEncoded()),
+                    Arrays.toString(encodedCopy));
+        } else {
+            encodedCopy = pathFromList.getEncoded(encoding);
+            assertNotNull(providerName, encodedCopy);
+
+            // check idempotence
+            assertEquals(providerName, Arrays.toString(pathFromList.getEncoded(encoding)),
+                    Arrays.toString(encodedCopy));
+        }
+
+        // Try to modify byte array.
+        encodedCopy[0] ^= (byte) 0xFF;
+
+        // Get a real copy we will use if the test proceeds.
+        final byte[] encoded;
+        if (encoding == null) {
+            encoded = pathFromList.getEncoded();
+            assertNotNull(providerName, encodedCopy);
+
+            // check idempotence
+            assertEquals(providerName, Arrays.toString(pathFromList.getEncoded()),
+                    Arrays.toString(encoded));
+        } else {
+            encoded = pathFromList.getEncoded(encoding);
+            assertNotNull(providerName, encodedCopy);
+
+            // check idempotence
+            assertEquals(providerName, Arrays.toString(pathFromList.getEncoded(encoding)),
+                    Arrays.toString(encoded));
+        }
+        assertFalse(providerName, Arrays.toString(encoded).equals(Arrays.toString(encodedCopy)));
+
+        encodedCopy[0] ^= (byte) 0xFF;
+        assertEquals(providerName, Arrays.toString(encoded), Arrays.toString(encodedCopy));
+
+        final CertPath actualPath;
+        if (encoding == null) {
+            actualPath = cf.generateCertPath(new ByteArrayInputStream(encoded));
+        } else {
+            actualPath = cf.generateCertPath(new ByteArrayInputStream(encoded), encoding);
+        }
+
+        // PKCS7 certificate bags are not guaranteed to be in order.
+        final List<? extends Certificate> actualCerts;
+        if (!"PKCS7".equals(encoding)) {
+            actualCerts = actualPath.getCertificates();
+            assertEquals(providerName, expectedCerts, actualCerts);
+        } else {
+            actualCerts = pathFromList.getCertificates();
+        }
+
+        try {
+            actualCerts.remove(0);
+            fail("List of certificate should be immutable");
+        } catch (UnsupportedOperationException expected) {
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(actualPath);
+        oos.close();
+
+        byte[] serialized = baos.toByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
+        ObjectInputStream ois = new ObjectInputStream(bais);
+        Object output = ois.readObject();
+        assertTrue(providerName, output instanceof CertPath);
+
+        assertEquals(providerName, actualPath, (CertPath) output);
+    }
+
+    public static class KeyHolder {
+        public X509Certificate certificate;
+
+        public PrivateKey privateKey;
+    }
+
+    @SuppressWarnings("deprecation")
+    private static KeyHolder generateCertificate(boolean isCa, KeyHolder issuer) throws Exception {
+        Date startDate = new Date();
+
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+        cal.set(2100, 0, 1, 0, 0, 0); // Jan 1, 2100 UTC
+        Date expiryDate = cal.getTime();
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        KeyPair keyPair = kpg.generateKeyPair();
+
+        BigInteger serial;
+        X500Principal issuerPrincipal;
+        X500Principal subjectPrincipal;
+        PrivateKey caKey;
+        if (issuer != null) {
+            serial = issuer.certificate.getSerialNumber().add(BigInteger.ONE);
+            subjectPrincipal = new X500Principal("CN=Test Certificate Serial #" + serial.toString());
+            issuerPrincipal = issuer.certificate.getSubjectX500Principal();
+            caKey = issuer.privateKey;
+        } else {
+            serial = BigInteger.ONE;
+            subjectPrincipal = new X500Principal("CN=Test CA, O=Tests, C=US");
+            issuerPrincipal = subjectPrincipal;
+            caKey = keyPair.getPrivate();
+        }
+
+        BasicConstraints basicConstraints;
+        if (isCa) {
+            basicConstraints = new BasicConstraints(10 - serial.intValue());
+        } else {
+            basicConstraints = new BasicConstraints(false);
+        }
+
+        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
+
+        certGen.setSerialNumber(serial);
+        certGen.setIssuerDN(issuerPrincipal);
+        certGen.setNotBefore(startDate);
+        certGen.setNotAfter(expiryDate);
+        certGen.setSubjectDN(subjectPrincipal);
+        certGen.setPublicKey(keyPair.getPublic());
+        certGen.setSignatureAlgorithm("SHA1withRSA");
+
+        if (issuer != null) {
+            certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
+                    new AuthorityKeyIdentifierStructure(issuer.certificate));
+        } else {
+            certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
+                    new AuthorityKeyIdentifierStructure(keyPair.getPublic()));
+        }
+
+        certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
+                new SubjectKeyIdentifierStructure(keyPair.getPublic()));
+        certGen.addExtension(X509Extensions.BasicConstraints, true, basicConstraints);
+
+        X509Certificate cert = certGen.generate(caKey);
+
+        KeyHolder holder = new KeyHolder();
+        holder.certificate = cert;
+        holder.privateKey = keyPair.getPrivate();
+
+        return holder;
+    }
 }
diff --git a/luni/src/test/java/libcore/java/security/cert/SubjectAlternativeNameTest.java b/luni/src/test/java/libcore/java/security/cert/SubjectAlternativeNameTest.java
index 88d5011..1b440c1 100644
--- a/luni/src/test/java/libcore/java/security/cert/SubjectAlternativeNameTest.java
+++ b/luni/src/test/java/libcore/java/security/cert/SubjectAlternativeNameTest.java
@@ -38,7 +38,9 @@
 
     public void testFormatIpv4MappedAddress() throws Exception {
         byte[] mappedAddress = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 127, 0, 0, 1 };
-        assertEquals("127.0.0.1", formatIpAddress(mappedAddress));
+        String decoded = formatIpAddress(mappedAddress);
+        assertTrue(decoded,
+                decoded.equals("127.0.0.1") || decoded.equalsIgnoreCase("::ffff:127.0.0.1"));
     }
 
     public void testFormatIpv6Address() throws Exception {
diff --git a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java
new file mode 100644
index 0000000..fa920ce
--- /dev/null
+++ b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java
@@ -0,0 +1,447 @@
+/*
+ * 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.java.security.cert;
+
+import tests.support.resource.Support_Resources;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.cert.CRL;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509CRLEntry;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
+
+public class X509CRLTest extends TestCase {
+    private Provider[] mX509Providers;
+
+    private static final String CERT_RSA = "x509/cert-rsa.der";
+
+    private static final String CERT_DSA = "x509/cert-dsa.der";
+
+    private static final String CERT_CRL_CA = "x509/cert-crl-ca.der";
+
+    private static final String CRL_RSA = "x509/crl-rsa.der";
+
+    private static final String CRL_RSA_DSA = "x509/crl-rsa-dsa.der";
+
+    private static final String CRL_RSA_DSA_SIGOPT = "x509/crl-rsa-dsa-sigopt.der";
+
+    private static final String CRL_UNSUPPORTED = "x509/crl-unsupported.der";
+
+    private static final String CRL_RSA_DATES = "x509/crl-rsa-dates.txt";
+
+    private static final String CRL_RSA_DSA_DATES = "x509/crl-rsa-dsa-dates.txt";
+
+    private static final String CRL_RSA_SIG = "x509/crl-rsa-sig.der";
+
+    private static final String CRL_RSA_TBS = "x509/crl-rsa-tbs.der";
+
+    private static final String CRL_EMPTY = "x509/crl-empty.der";
+
+    private final X509Certificate getCertificate(CertificateFactory f, String name)
+            throws Exception {
+        final InputStream is = Support_Resources.getStream(name);
+        assertNotNull("File does not exist: " + name, is);
+        try {
+            X509Certificate c = (X509Certificate) f.generateCertificate(is);
+            assertNotNull(c);
+            return c;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private final X509CRL getCRL(CertificateFactory f, String name) throws Exception {
+        final InputStream is = Support_Resources.getStream(name);
+        assertNotNull("File does not exist: " + name, is);
+        try {
+            X509CRL crl = (X509CRL) f.generateCRL(is);
+            assertNotNull(crl);
+            return crl;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private byte[] getResourceAsBytes(String name) throws Exception {
+        final InputStream ris = Support_Resources.getStream(name);
+        try {
+            DataInputStream dis = new DataInputStream(ris);
+            byte[] buf = new byte[ris.available()];
+            dis.readFully(buf);
+            return buf;
+        } finally {
+            try {
+                ris.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private Map<String, Date> getCrlDates(String name) throws Exception {
+        Map<String, Date> dates = new HashMap<String, Date>();
+        final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm:ss yyyy zzz");
+
+        final InputStream ris = Support_Resources.getStream(name);
+        try {
+
+            final BufferedReader buf = new BufferedReader(new InputStreamReader(ris));
+
+            String line;
+            while ((line = buf.readLine()) != null) {
+                int index = line.indexOf('=');
+                String key = line.substring(0, index);
+                final Date value = sdf.parse(line.substring(index + 1));
+                dates.put(key, value);
+            }
+
+            return dates;
+        } finally {
+            try {
+                ris.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    public void test_Provider() throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+
+        for (Provider p : mX509Providers) {
+            try {
+                CertificateFactory f = CertificateFactory.getInstance("X.509", p);
+                isRevoked(f);
+                getType(f);
+                getEncoded(f);
+                getVersion(f);
+                hasUnsupportedCriticalExtension(f);
+                getSignature(f);
+                getTBSCertList(f);
+                getRevokedCertificates(f);
+                getThisUpdateNextUpdate(f);
+                getSigAlgName(f);
+                getSigAlgOID(f);
+                getSigAlgParams(f);
+                verify(f);
+                test_toString(f);
+                test_equals(f);
+            } catch (Throwable e) {
+                out.append("Error encountered checking " + p.getName() + "\n");
+                e.printStackTrace(out);
+            }
+        }
+
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+
+    private void verify(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+
+        X509Certificate caCert = getCertificate(f, CERT_CRL_CA);
+        crlRsa.verify(caCert.getPublicKey());
+
+        X509Certificate dsaCert = getCertificate(f, CERT_DSA);
+        try {
+            crlRsa.verify(dsaCert.getPublicKey());
+            fail("should not verify using incorrect key type");
+        } catch (InvalidKeyException expected) {
+        }
+    }
+
+    private void getType(CertificateFactory f) throws Exception {
+        CRL crlRsa = getCRL(f, CRL_RSA);
+
+        assertEquals("X.509", crlRsa.getType());
+    }
+
+    private void isRevoked(CertificateFactory f) throws Exception {
+        X509Certificate rsaCert = getCertificate(f, CERT_RSA);
+        X509Certificate dsaCert = getCertificate(f, CERT_DSA);
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+        X509CRL crlRsaDsa = getCRL(f, CRL_RSA_DSA);
+
+        assertTrue(crlRsa.isRevoked(rsaCert));
+        assertFalse(crlRsa.isRevoked(dsaCert));
+
+        assertTrue(crlRsaDsa.isRevoked(rsaCert));
+        assertTrue(crlRsaDsa.isRevoked(dsaCert));
+
+        try {
+            assertFalse(crlRsa.isRevoked(null));
+            if ("BC".equals(f.getProvider().getName())) {
+                fail("BouncyCastle throws on null input");
+            }
+        } catch (NullPointerException e) {
+            if (!"BC".equals(f.getProvider().getName())) {
+                fail("Should not throw on null input");
+            }
+        }
+    }
+
+    private void getThisUpdateNextUpdate(CertificateFactory f) throws Exception {
+        {
+            X509CRL crl = getCRL(f, CRL_RSA);
+            Map<String, Date> dates = getCrlDates(CRL_RSA_DATES);
+
+            Date lastUpdate = dates.get("lastUpdate");
+            Date nextUpdate = dates.get("nextUpdate");
+
+            assertNotNull(lastUpdate);
+            assertNotNull(nextUpdate);
+
+            assertDateEquals(lastUpdate, crl.getThisUpdate());
+            assertDateEquals(nextUpdate, crl.getNextUpdate());
+        }
+
+        {
+            X509CRL crl = getCRL(f, CRL_RSA_DSA);
+            Map<String, Date> dates = getCrlDates(CRL_RSA_DSA_DATES);
+
+            Date lastUpdate = dates.get("lastUpdate");
+            Date nextUpdate = dates.get("nextUpdate");
+
+            assertNotNull(lastUpdate);
+            assertNotNull(nextUpdate);
+
+            assertDateEquals(lastUpdate, crl.getThisUpdate());
+            assertDateEquals(nextUpdate, crl.getNextUpdate());
+        }
+    }
+
+    private void getSigAlgName(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+
+        String actual = crlRsa.getSigAlgName().toUpperCase(Locale.US);
+
+        // Bouncycastle is broken
+        if ("BC".equals(f.getProvider().getName())) {
+            assertEquals("1.2.840.113549.1.1.5", actual);
+        } else {
+            assertEquals("SHA1WITHRSA", actual);
+        }
+    }
+
+    private void getSigAlgOID(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+
+        assertEquals("1.2.840.113549.1.1.5", crlRsa.getSigAlgOID());
+    }
+
+    private void getVersion(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+
+        assertEquals(1, crlRsa.getVersion());
+    }
+
+    private void hasUnsupportedCriticalExtension(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+        assertFalse(crlRsa.hasUnsupportedCriticalExtension());
+
+        X509CRL unsupportedCrl = getCRL(f, CRL_UNSUPPORTED);
+        assertTrue(unsupportedCrl.hasUnsupportedCriticalExtension());
+    }
+
+    private void getSignature(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+        byte[] expected = getResourceAsBytes(CRL_RSA_SIG);
+
+        assertEquals(Arrays.toString(expected), Arrays.toString(crlRsa.getSignature()));
+    }
+
+    private void getTBSCertList(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+        byte[] expected = getResourceAsBytes(CRL_RSA_TBS);
+
+        assertEquals(Arrays.toString(expected), Arrays.toString(crlRsa.getTBSCertList()));
+    }
+
+    private void getEncoded(CertificateFactory f) throws Exception {
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+
+        byte[] crlRsaBytes = getResourceAsBytes(CRL_RSA);
+
+        assertEquals(Arrays.toString(crlRsaBytes), Arrays.toString(crlRsa.getEncoded()));
+    }
+
+    private static void assertDateEquals(Date date1, Date date2) throws Exception {
+        SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
+
+        String result1 = formatter.format(date1);
+        String result2 = formatter.format(date2);
+
+        assertEquals(result1, result2);
+    }
+
+    private void assertRsaCrlEntry(CertificateFactory f, X509CRLEntry rsaEntry) throws Exception {
+        assertNotNull(rsaEntry);
+
+        X509Certificate rsaCert = getCertificate(f, CERT_RSA);
+        Map<String, Date> dates = getCrlDates(CRL_RSA_DSA_DATES);
+        Date expectedDate = dates.get("lastUpdate");
+
+        assertEquals(rsaCert.getSerialNumber(), rsaEntry.getSerialNumber());
+        assertDateEquals(expectedDate, rsaEntry.getRevocationDate());
+        assertNull(rsaEntry.getCertificateIssuer());
+        assertFalse(rsaEntry.hasExtensions());
+        assertNull(rsaEntry.getCriticalExtensionOIDs());
+        assertNull(rsaEntry.getNonCriticalExtensionOIDs());
+
+        assertNotNull(rsaEntry.toString());
+    }
+
+    private void assertDsaCrlEntry(CertificateFactory f, X509CRLEntry dsaEntry) throws Exception {
+        X509Certificate dsaCert = getCertificate(f, CERT_DSA);
+        Map<String, Date> dates = getCrlDates(CRL_RSA_DSA_DATES);
+        Date expectedDate = dates.get("lastUpdate");
+
+        assertEquals(dsaCert.getSerialNumber(), dsaEntry.getSerialNumber());
+        assertDateEquals(expectedDate, dsaEntry.getRevocationDate());
+        assertNull(dsaEntry.getCertificateIssuer());
+        assertTrue(dsaEntry.hasExtensions());
+        /* TODO: get the OID */
+        assertNotNull(dsaEntry.getCriticalExtensionOIDs());
+        /* TODO: get the OID */
+        assertNotNull(dsaEntry.getNonCriticalExtensionOIDs());
+
+        assertNotNull(dsaEntry.toString());
+    }
+
+    private void getRevokedCertificates(CertificateFactory f) throws Exception {
+        X509CRL crlEmpty = getCRL(f, CRL_EMPTY);
+        assertNull(crlEmpty.getRevokedCertificates());
+
+        X509CRL crlRsa = getCRL(f, CRL_RSA);
+        X509Certificate rsaCert = getCertificate(f, CERT_RSA);
+        X509Certificate dsaCert = getCertificate(f, CERT_DSA);
+
+        Set<? extends X509CRLEntry> entries = crlRsa.getRevokedCertificates();
+        assertEquals(1, entries.size());
+        for (X509CRLEntry e : entries) {
+            assertRsaCrlEntry(f, e);
+        }
+
+        X509CRL crlRsaDsa = getCRL(f, CRL_RSA_DSA);
+        Set<? extends X509CRLEntry> entries2 = crlRsaDsa.getRevokedCertificates();
+        assertEquals(2, entries2.size());
+        assertRsaCrlEntry(f, crlRsaDsa.getRevokedCertificate(rsaCert));
+        assertDsaCrlEntry(f, crlRsaDsa.getRevokedCertificate(dsaCert));
+    }
+
+    private void getSigAlgParams(CertificateFactory f) throws Exception {
+        X509CRL crl1 = getCRL(f, CRL_RSA);
+        final byte[] sigAlgParams = crl1.getSigAlgParams();
+        if (StandardNames.IS_RI) {
+            assertNull(f.getProvider().getName(), sigAlgParams);
+        } else {
+            assertNotNull(f.getProvider().getName(), sigAlgParams);
+            /* ASN.1 NULL */
+            final byte[] expected = new byte[] {
+                    0x05, 0x00,
+            };
+            assertEquals(f.getProvider().getName(), Arrays.toString(expected),
+                    Arrays.toString(sigAlgParams));
+        }
+
+        {
+            X509CRL crlSigOpt = getCRL(f, CRL_RSA_DSA_SIGOPT);
+
+            /* SEQUENCE, INTEGER 1 */
+            final byte[] expected = new byte[] {
+                    /* SEQUENCE, constructed, len=5 */
+                    (byte) 0x30, (byte) 0x05,
+                    /* Type=2, constructed, context-specific, len=3 */
+                    (byte) 0xA2, (byte) 0x03,
+                    /* INTEGER, len=1, value=1 */
+                    (byte) 0x02, (byte) 0x01, (byte) 0x01,
+            };
+
+            final byte[] params = crlSigOpt.getSigAlgParams();
+            assertNotNull(f.getProvider().getName(), params);
+            assertEquals(Arrays.toString(expected), Arrays.toString(params));
+        }
+    }
+
+    private void test_toString(CertificateFactory f) throws Exception {
+        X509CRL crl1 = getCRL(f, CRL_RSA);
+        X509CRL crl2 = getCRL(f, CRL_RSA);
+
+        X509CRL crlRsaDsa = getCRL(f, CRL_RSA_DSA);
+
+        assertNotNull(crl1);
+
+        assertNotNull(crlRsaDsa);
+
+        assertEquals(crl1.toString(), crl2.toString());
+
+        assertFalse(crl1.toString().equals(crlRsaDsa.toString()));
+    }
+
+    private void test_equals(CertificateFactory f) throws Exception {
+        X509CRL crl1 = getCRL(f, CRL_RSA);
+        X509CRL crl2 = getCRL(f, CRL_RSA);
+        X509Certificate rsaCert = getCertificate(f, CERT_RSA);
+
+        X509CRL crlRsaDsa = getCRL(f, CRL_RSA_DSA);
+
+        assertEquals(crl1, crl2);
+        assertFalse(crl1.equals(crlRsaDsa));
+
+        X509CRLEntry entry1 = crl1.getRevokedCertificate(rsaCert);
+        assertNotNull(entry1);
+        X509CRLEntry entry2 = crl2.getRevokedCertificate(rsaCert);
+        assertNotNull(entry2);
+
+        assertEquals(entry1, entry2);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mX509Providers = Security.getProviders("CertificateFactory.X509");
+    }
+}
diff --git a/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java b/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java
new file mode 100644
index 0000000..8dd1452
--- /dev/null
+++ b/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java
@@ -0,0 +1,1278 @@
+/*
+ * 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.java.security.cert;
+
+import tests.support.resource.Support_Resources;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.PrintStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Principal;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.security.spec.X509EncodedKeySpec;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
+
+public class X509CertificateTest extends TestCase {
+    private Provider[] mX509Providers;
+
+    private static final String CERT_RSA = "x509/cert-rsa.der";
+
+    private static final String CERT_DSA = "x509/cert-dsa.der";
+
+    private static final String CERT_EC = "x509/cert-ec.der";
+
+    private static final String CERT_KEYUSAGE_EXTRALONG = "x509/cert-keyUsage-extraLong.der";
+
+    private static final String CERT_EXTENDEDKEYUSAGE = "x509/cert-extendedKeyUsage.der";
+
+    private final static String CERT_RSA_TBS = "x509/cert-rsa-tbs.der";
+
+    private final static String CERT_RSA_SIGNATURE = "x509/cert-rsa-sig.der";
+
+    private static final String CERT_USERWITHPATHLEN = "x509/cert-userWithPathLen.der";
+
+    private static final String CERT_CA = "x509/cert-ca.der";
+
+    private static final String CERT_CAWITHPATHLEN = "x509/cert-caWithPathLen.der";
+
+    private static final String CERT_INVALIDIP = "x509/cert-invalidip.der";
+
+    private static final String CERT_IPV6 = "x509/cert-ipv6.der";
+
+    private static final String CERT_ALT_OTHER = "x509/cert-alt-other.der";
+
+    private static final String CERT_ALT_EMAIL = "x509/cert-alt-email.der";
+
+    private static final String CERT_ALT_DNS = "x509/cert-alt-dns.der";
+
+    private static final String CERT_ALT_DIRNAME = "x509/cert-alt-dirname.der";
+
+    private static final String CERT_ALT_URI = "x509/cert-alt-uri.der";
+
+    private static final String CERT_ALT_RID = "x509/cert-alt-rid.der";
+
+    private static final String CERT_ALT_NONE = "x509/cert-alt-none.der";
+
+    private static final String CERT_UNSUPPORTED = "x509/cert-unsupported.der";
+
+    private static final String CERT_SIGOPT = "x509/cert-sigopt.der";
+
+    private static final String CERTS_X509_PEM = "x509/certs.pem";
+
+    private static final String CERTS_X509_DER = "x509/certs.der";
+
+    private static final String CERTS_PKCS7_PEM = "x509/certs-pk7.pem";
+
+    private static final String CERTS_PKCS7_DER = "x509/certs-pk7.der";
+
+    /** A list of certs that are all slightly different. */
+    private static final String[] VARIOUS_CERTS = new String[] {
+            CERT_RSA, CERT_DSA, CERT_EC,
+    };
+
+    private final X509Certificate getCertificate(CertificateFactory f, String name)
+            throws Exception {
+        final InputStream is = Support_Resources.getStream(name);
+        assertNotNull("File does not exist: " + name, is);
+        try {
+            return (X509Certificate) f.generateCertificate(is);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private final Collection<? extends X509Certificate> getCertificates(CertificateFactory f, String name)
+            throws Exception {
+        final InputStream is = Support_Resources.getStream(name);
+        assertNotNull("File does not exist: " + name, is);
+        try {
+            return (Collection<? extends X509Certificate>) f.generateCertificates(is);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private PublicKey getRsaCertificatePublicKey() throws Exception {
+        final InputStream ris = Support_Resources.getStream("x509/cert-rsa-pubkey.der");
+        try {
+            final int size = ris.available();
+            final DataInputStream is = new DataInputStream(ris);
+            final byte[] keyBytes = new byte[size];
+            is.readFully(keyBytes);
+
+            final KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePublic(new X509EncodedKeySpec(keyBytes));
+        } finally {
+            try {
+                ris.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private Date[] getRsaCertificateDates() throws Exception {
+        final InputStream ris = Support_Resources.getStream("x509/cert-rsa-dates.txt");
+        try {
+            // notBefore=Dec 26 00:19:14 2012 GMT
+            final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm:ss yyyy zzz");
+
+            final BufferedReader buf = new BufferedReader(new InputStreamReader(ris));
+            String line = buf.readLine();
+            int index = line.indexOf('=');
+            assertEquals("notBefore", line.substring(0, index));
+            final Date startDate = sdf.parse(line.substring(index + 1));
+
+            line = buf.readLine();
+            index = line.indexOf('=');
+            assertEquals("notAfter", line.substring(0, index));
+            final Date endDate = sdf.parse(line.substring(index + 1));
+
+            assertTrue(startDate.before(endDate));
+            assertTrue(endDate.after(startDate));
+
+            return new Date[] { startDate, endDate };
+        } finally {
+            try {
+                ris.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private BigInteger getRsaCertificateSerial() throws Exception {
+        final InputStream ris = Support_Resources.getStream("x509/cert-rsa-serial.txt");
+        try {
+            final BufferedReader buf = new BufferedReader(new InputStreamReader(ris));
+
+            String line = buf.readLine();
+            int index = line.indexOf('=');
+            assertEquals("serial", line.substring(0, index));
+
+            return new BigInteger(line.substring(index + 1), 16);
+        } finally {
+            try {
+                ris.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private byte[] getResourceAsBytes(String name) throws Exception {
+        final InputStream ris = Support_Resources.getStream(name);
+        try {
+            DataInputStream dis = new DataInputStream(ris);
+            byte[] buf = new byte[ris.available()];
+            dis.readFully(buf);
+            return buf;
+        } finally {
+            try {
+                ris.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private byte[] getRsaCertificateSignature() throws Exception {
+        return getResourceAsBytes(CERT_RSA_SIGNATURE);
+    }
+
+    private byte[] getRsaCertificateTbs() throws Exception {
+        return getResourceAsBytes(CERT_RSA_TBS);
+    }
+
+    public void test_Provider() throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+
+        for (Provider p : mX509Providers) {
+            try {
+                CertificateFactory f = CertificateFactory.getInstance("X.509", p);
+                getPublicKey(f);
+                getType(f);
+                check_equals(f);
+                check_toString(f);
+                check_hashCode(f);
+                checkValidity(f);
+                getVersion(f);
+                getSerialNumber(f);
+                getIssuerDN(f);
+                getIssuerX500Principal(f);
+                getSubjectDN(f);
+                getSubjectUniqueID(f);
+                getSubjectX500Principal(f);
+                getNotBeforeAndNotAfterDates(f);
+                getSigAlgName(f);
+                getSigAlgOID(f);
+                getSigAlgParams(f);
+                getIssuerUniqueID(f);
+                getSubjectUniqueID(f);
+                getKeyUsage(f);
+                getExtendedKeyUsage(f);
+                getBasicConstraints(f);
+                getSubjectAlternativeNames(f);
+                getSubjectAlternativeNames_IPV6(f);
+                getSubjectAlternativeNames_InvalidIP(f);
+                getSubjectAlternativeNames_Other(f);
+                getSubjectAlternativeNames_Email(f);
+                getSubjectAlternativeNames_DNS(f);
+                getSubjectAlternativeNames_DirName(f);
+                getSubjectAlternativeNames_URI(f);
+                getSubjectAlternativeNames_RID(f);
+                getSubjectAlternativeNames_None(f);
+                getIssuerAlternativeNames(f);
+                getTBSCertificate(f);
+                getSignature(f);
+                hasUnsupportedCriticalExtension(f);
+                getEncoded(f);
+                verify(f);
+                generateCertificate_PEM_TrailingData(f);
+                generateCertificate_DER_TrailingData(f);
+                generateCertificates_X509_PEM(f);
+                generateCertificates_X509_DER(f);
+                generateCertificates_PKCS7_PEM(f);
+                generateCertificates_PKCS7_DER(f);
+                generateCertificates_Empty(f);
+                generateCertificates_X509_PEM_TrailingData(f);
+                generateCertificates_X509_DER_TrailingData(f);
+                generateCertificates_PKCS7_PEM_TrailingData(f);
+                generateCertificates_PKCS7_DER_TrailingData(f);
+                test_Serialization(f);
+            } catch (Throwable e) {
+                out.append("Error encountered checking " + p.getName() + "\n");
+                e.printStackTrace(out);
+            }
+        }
+
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+
+    private void getPublicKey(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        PublicKey expected = getRsaCertificatePublicKey();
+
+        PublicKey actual = c.getPublicKey();
+        assertEquals(expected, actual);
+        assertEquals(Arrays.toString(expected.getEncoded()),
+                     Arrays.toString(actual.getEncoded()));
+    }
+
+    private void getType(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        assertEquals("X.509", c.getType());
+    }
+
+    private void verify(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        PublicKey signer = getRsaCertificatePublicKey();
+
+        c.verify(signer);
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        KeyPair pair = kpg.generateKeyPair();
+        PublicKey invalidKey = pair.getPublic();
+
+        try {
+            c.verify(invalidKey);
+            fail("RSA signature should not verify");
+        } catch (SignatureException expected) {
+        }
+
+        Provider[] providers = Security.getProviders("Signature." + c.getSigAlgName());
+        for (Provider p : providers) {
+            c.verify(signer, p.getName());
+
+            try {
+                c.verify(invalidKey, p.getName());
+                fail("RSA signature should not verify");
+            } catch (SignatureException expected) {
+            }
+        }
+    }
+
+    private void check_equals(CertificateFactory f) throws Exception {
+        X509Certificate c1 = getCertificate(f, CERT_RSA);
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+
+        assertEquals(c1, c2);
+
+        X509Certificate c3 = getCertificate(f, CERT_DSA);
+        assertFalse(c1.equals(c3));
+        assertFalse(c3.equals(c1));
+    }
+
+    private void check_toString(CertificateFactory f) throws Exception {
+        X509Certificate c1 = getCertificate(f, CERT_RSA);
+
+        String output1 = c1.toString();
+        assertNotNull(output1);
+        assertTrue(output1.length() > 0);
+
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+        assertEquals(c1.toString(), c2.toString());
+
+        X509Certificate c3 = getCertificate(f, CERT_DSA);
+        assertFalse(c3.toString().equals(c1.toString()));
+    }
+
+    private void check_hashCode(CertificateFactory f) throws Exception {
+        X509Certificate c1 = getCertificate(f, CERT_RSA);
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+
+        assertEquals(c1.hashCode(), c2.hashCode());
+
+        X509Certificate c3 = getCertificate(f, CERT_DSA);
+        assertFalse(c3.hashCode() == c1.hashCode());
+    }
+
+    private void checkValidity(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        Calendar cal = Calendar.getInstance();
+        Date[] dates = getRsaCertificateDates();
+
+        /*
+         * The certificate validity periods in the test certificate MUST lie
+         * within the tested period. The API doesn't appear to allow any other
+         * way to test this code path as an unprivileged user.
+         */
+        Date now = new Date();
+        assertTrue(now.after(dates[0]));
+        assertTrue(now.before(dates[1]));
+
+        /* This assumes the script makes a long-lived cert. */
+        c.checkValidity();
+
+        /* A day after the start date. */
+        cal.setTime(dates[0]);
+        cal.add(Calendar.DAY_OF_MONTH, 1);
+        c.checkValidity(cal.getTime());
+
+        /* A second before the start date. */
+        cal.setTime(dates[1]);
+        cal.add(Calendar.SECOND, -1);
+        c.checkValidity(cal.getTime());
+
+        try {
+            cal.setTime(dates[0]);
+            cal.add(Calendar.SECOND, -1);
+            c.checkValidity(cal.getTime());
+            fail();
+        } catch (CertificateNotYetValidException expected) {
+        }
+
+        try {
+            cal.setTime(dates[0]);
+            cal.add(Calendar.MONTH, -6);
+            c.checkValidity(cal.getTime());
+            fail();
+        } catch (CertificateNotYetValidException expected) {
+        }
+
+        try {
+            cal.setTime(dates[1]);
+            cal.add(Calendar.SECOND, 1);
+            c.checkValidity(cal.getTime());
+            fail();
+        } catch (CertificateExpiredException expected) {
+        }
+
+        try {
+            cal.setTime(dates[1]);
+            cal.add(Calendar.YEAR, 1);
+            c.checkValidity(cal.getTime());
+            fail();
+        } catch (CertificateExpiredException expected) {
+        }
+    }
+
+    private void getVersion(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        assertEquals(3, c.getVersion());
+    }
+
+    private void getSerialNumber(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        BigInteger actual = getRsaCertificateSerial();
+
+        assertEquals(actual, c.getSerialNumber());
+    }
+
+    private void getIssuerDN(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        Principal princ = c.getIssuerDN();
+        if (StandardNames.IS_RI) {
+            assertEquals("OU=NetOps, O=Genius.com Inc, L=San Mateo, ST=California, C=US",
+                         princ.getName());
+        } else {
+            if ("BC".equals(f.getProvider().getName())) {
+                // TODO: is it acceptable to have this in reverse order?
+                assertEquals(f.getProvider().getName(),
+                             "C=US,ST=California,L=San Mateo,O=Genius.com Inc,OU=NetOps",
+                             princ.getName());
+            } else {
+                assertEquals("OU=NetOps,O=Genius.com Inc,L=San Mateo,ST=California,C=US",
+                             princ.getName());
+            }
+        }
+
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+        assertEquals(princ, c2.getIssuerDN());
+    }
+
+    private void getIssuerX500Principal(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        final byte[] expected = new byte[] {
+                0x30, 0x60, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+                0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+                0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61,
+                0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x53,
+                0x61, 0x6e, 0x20, 0x4d, 0x61, 0x74, 0x65, 0x6f, 0x31, 0x17, 0x30, 0x15,
+                0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6e, 0x69, 0x75,
+                0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x0f, 0x30,
+                0x0d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x06, 0x4e, 0x65, 0x74, 0x4f,
+                0x70, 0x73
+        };
+        X500Principal princ = c.getIssuerX500Principal();
+        assertEquals(Arrays.toString(expected),
+                     Arrays.toString(princ.getEncoded()));
+        assertEquals("OU=NetOps,O=Genius.com Inc,L=San Mateo,ST=California,C=US",
+                     princ.getName());
+        assertEquals("ou=netops,o=genius.com inc,l=san mateo,st=california,c=us",
+                     princ.getName(X500Principal.CANONICAL));
+        assertEquals("OU=NetOps, O=Genius.com Inc, L=San Mateo, ST=California, C=US",
+                     princ.getName(X500Principal.RFC1779));
+        assertEquals("OU=NetOps,O=Genius.com Inc,L=San Mateo,ST=California,C=US",
+                     princ.getName(X500Principal.RFC2253));
+
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+        assertEquals(princ, c2.getIssuerX500Principal());
+    }
+
+    private void getSubjectDN(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        Principal princ = c.getSubjectDN();
+        if (StandardNames.IS_RI) {
+            assertEquals("OU=NetOps, O=Genius.com Inc, L=San Mateo, ST=California, C=US",
+                         princ.getName());
+        } else {
+            if ("BC".equals(f.getProvider().getName())) {
+                // TODO: is it acceptable to have this in reverse order?
+                assertEquals(f.getProvider().getName(),
+                             "C=US,ST=California,L=San Mateo,O=Genius.com Inc,OU=NetOps",
+                             princ.getName());
+            } else {
+                assertEquals("OU=NetOps,O=Genius.com Inc,L=San Mateo,ST=California,C=US",
+                             princ.getName());
+            }
+        }
+
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+        assertEquals(princ, c2.getSubjectDN());
+    }
+
+    private void getSubjectUniqueID(CertificateFactory f) throws Exception {
+        /* This certificate has no unique ID. */
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        assertNull(c.getSubjectUniqueID());
+
+        // TODO: generate certificate that has a SubjectUniqueID field.
+    }
+
+    private void getIssuerUniqueID(CertificateFactory f) throws Exception {
+        /* This certificate has no unique ID. */
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        assertNull(c.getIssuerUniqueID());
+
+        // TODO: generate certificate that has a IssuerUniqueID field.
+    }
+
+    private void getSubjectX500Principal(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        final byte[] expected = new byte[] {
+                0x30, 0x60, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+                0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+                0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61,
+                0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x53,
+                0x61, 0x6e, 0x20, 0x4d, 0x61, 0x74, 0x65, 0x6f, 0x31, 0x17, 0x30, 0x15,
+                0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6e, 0x69, 0x75,
+                0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x0f, 0x30,
+                0x0d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x06, 0x4e, 0x65, 0x74, 0x4f,
+                0x70, 0x73
+        };
+        X500Principal princ = c.getSubjectX500Principal();
+        assertEquals(Arrays.toString(expected),
+                     Arrays.toString(princ.getEncoded()));
+        assertEquals("OU=NetOps,O=Genius.com Inc,L=San Mateo,ST=California,C=US",
+                     princ.getName());
+        assertEquals("ou=netops,o=genius.com inc,l=san mateo,st=california,c=us",
+                     princ.getName(X500Principal.CANONICAL));
+        assertEquals("OU=NetOps, O=Genius.com Inc, L=San Mateo, ST=California, C=US",
+                     princ.getName(X500Principal.RFC1779));
+        assertEquals("OU=NetOps,O=Genius.com Inc,L=San Mateo,ST=California,C=US",
+                     princ.getName(X500Principal.RFC2253));
+
+        X509Certificate c2 = getCertificate(f, CERT_RSA);
+        assertEquals(princ, c2.getSubjectX500Principal());
+    }
+
+    private static void assertDateEquals(Date date1, Date date2) throws Exception {
+        SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
+
+        String result1 = formatter.format(date1);
+        String result2 = formatter.format(date2);
+
+        assertEquals(result1, result2);
+    }
+
+    private void getNotBeforeAndNotAfterDates(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        Date[] dates = getRsaCertificateDates();
+
+        assertDateEquals(dates[0], c.getNotBefore());
+        assertDateEquals(dates[1], c.getNotAfter());
+    }
+
+    private void getSigAlgName(CertificateFactory f) throws Exception {
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_RSA);
+            assertEquals("SHA1WITHRSA", c.getSigAlgName().toUpperCase(Locale.US));
+        }
+
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_DSA);
+            assertEquals("SHA1WITHDSA", c.getSigAlgName().toUpperCase(Locale.US));
+        }
+
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_EC);
+            if (StandardNames.IS_RI) {
+                assertEquals("SHA1WITHECDSA", c.getSigAlgName().toUpperCase(Locale.US));
+            } else {
+                assertEquals("ECDSA", c.getSigAlgName().toUpperCase(Locale.US));
+            }
+        }
+    }
+
+    private void getSigAlgOID(CertificateFactory f) throws Exception {
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_RSA);
+            assertEquals("1.2.840.113549.1.1.5", c.getSigAlgOID());
+        }
+
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_DSA);
+            assertEquals("1.2.840.10040.4.3", c.getSigAlgOID());
+        }
+
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_EC);
+            assertEquals("1.2.840.10045.4.1", c.getSigAlgOID());
+        }
+    }
+
+    private void getSigAlgParams(CertificateFactory f) throws Exception {
+        {
+            X509Certificate c = getCertificate(f, CERT_RSA);
+            // RI appears to disagree
+            if (StandardNames.IS_RI) {
+                assertNull(f.getProvider().getName(), c.getSigAlgParams());
+            } else {
+                assertNotNull(f.getProvider().getName(), c.getSigAlgParams());
+            }
+        }
+
+        {
+            X509Certificate c = getCertificate(f, CERT_DSA);
+            assertNull(f.getProvider().getName(), c.getSigAlgParams());
+        }
+
+        {
+            X509Certificate c = getCertificate(f, CERT_EC);
+            assertNull(f.getProvider().getName(), c.getSigAlgParams());
+        }
+
+        {
+            X509Certificate c = getCertificate(f, CERT_SIGOPT);
+
+            /* SEQUENCE, INTEGER 1 */
+            final byte[] expected = new byte[] {
+                    /* SEQUENCE, constructed, len=5 */
+                    (byte) 0x30, (byte) 0x05,
+                    /* Type=2, constructed, context-specific, len=3 */
+                    (byte) 0xA2, (byte) 0x03,
+                    /* INTEGER, len=1, value=1 */
+                    (byte) 0x02, (byte) 0x01, (byte) 0x01,
+            };
+
+            final byte[] params = c.getSigAlgParams();
+            assertNotNull(f.getProvider().getName(), params);
+            assertEquals(Arrays.toString(expected), Arrays.toString(params));
+        }
+    }
+
+    private void getKeyUsage(CertificateFactory f) throws Exception {
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_RSA);
+            boolean[] expected = new boolean[] {
+                    true,  /* digitalSignature (0) */
+                    true,  /* nonRepudiation   (1) */
+                    true,  /* keyEncipherment  (2) */
+                    false, /* dataEncipherment (3) */
+                    false, /* keyAgreement     (4) */
+                    false, /* keyCertSign      (5) */
+                    false, /* cRLSign          (6) */
+                    false, /* encipherOnly     (7) */
+                    false, /* decipherOnly     (8) */
+            };
+            assertEquals(Arrays.toString(expected), Arrays.toString(c.getKeyUsage()));
+        }
+
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_DSA);
+            boolean[] expected = new boolean[] {
+                    false, /* digitalSignature (0) */
+                    false, /* nonRepudiation   (1) */
+                    true,  /* keyEncipherment  (2) */
+                    true,  /* dataEncipherment (3) */
+                    false, /* keyAgreement     (4) */
+                    true,  /* keyCertSign      (5) */
+                    true,  /* cRLSign          (6) */
+                    true,  /* encipherOnly     (7) */
+                    false, /* decipherOnly     (8) */
+            };
+            boolean[] actual = c.getKeyUsage();
+            assertEquals(9, actual.length);
+            assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+        }
+
+        {
+            /* The test certificate is sha1WithRSAEncryption */
+            X509Certificate c = getCertificate(f, CERT_EC);
+            boolean[] expected = new boolean[] {
+                    false, /* digitalSignature (0) */
+                    false, /* nonRepudiation   (1) */
+                    false, /* keyEncipherment  (2) */
+                    false, /* dataEncipherment (3) */
+                    true,  /* keyAgreement     (4) */
+                    false, /* keyCertSign      (5) */
+                    false, /* cRLSign          (6) */
+                    false, /* encipherOnly     (7) */
+                    true,  /* decipherOnly     (8) */
+            };
+            boolean[] actual = c.getKeyUsage();
+            assertEquals(9, actual.length);
+            assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+        }
+
+        {
+            /* All the bits are set in addition to some extra ones. */
+            X509Certificate c = getCertificate(f, CERT_KEYUSAGE_EXTRALONG);
+            boolean[] expected = new boolean[] {
+                    true,  /* digitalSignature (0) */
+                    true,  /* nonRepudiation   (1) */
+                    true,  /* keyEncipherment  (2) */
+                    true,  /* dataEncipherment (3) */
+                    true,  /* keyAgreement     (4) */
+                    true,  /* keyCertSign      (5) */
+                    true,  /* cRLSign          (6) */
+                    true,  /* encipherOnly     (7) */
+                    true,  /* decipherOnly     (8) */
+                    true,  /* ?????            (9) */
+                    true,  /* ?????           (10) */
+            };
+            boolean[] actual = c.getKeyUsage();
+            assertEquals(11, actual.length);
+            assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+        }
+    }
+
+    private void getExtendedKeyUsage(CertificateFactory f) throws Exception {
+        {
+            /* No ExtendedKeyUsage section */
+            final X509Certificate c = getCertificate(f, CERT_RSA);
+            List<String> actual = c.getExtendedKeyUsage();
+            assertNull(actual);
+        }
+
+        {
+            /* ExtendedKeyUsage section with one entry of OID 1.2.3.4 */
+            final X509Certificate c = getCertificate(f, CERT_EXTENDEDKEYUSAGE);
+            List<String> actual = c.getExtendedKeyUsage();
+            assertNotNull(actual);
+            assertEquals(1, actual.size());
+            assertEquals("1.2.3.4", actual.get(0));
+        }
+    }
+
+    private void getBasicConstraints(CertificateFactory f) throws Exception {
+        /* Non-CA cert with no pathLenConstraint */
+        {
+            final X509Certificate c = getCertificate(f, CERT_RSA);
+            assertEquals(f.getProvider().getName(), -1, c.getBasicConstraints());
+        }
+
+        /* Non-CA cert with pathLenConstraint */
+        {
+            final X509Certificate c = getCertificate(f, CERT_USERWITHPATHLEN);
+            assertEquals(f.getProvider().getName(), -1, c.getBasicConstraints());
+        }
+
+        /* CA cert with no pathLenConstraint */
+        {
+            final X509Certificate c = getCertificate(f, CERT_CA);
+            assertEquals(f.getProvider().getName(), Integer.MAX_VALUE, c.getBasicConstraints());
+        }
+
+        /* CA cert with pathLenConstraint=10 */
+        {
+            final X509Certificate c = getCertificate(f, CERT_CAWITHPATHLEN);
+            assertEquals(f.getProvider().getName(), 10, c.getBasicConstraints());
+        }
+    }
+
+    /** Encoding of:  OID:1.2.3.4, UTF8:test1 */
+    private static byte[] getOIDTestBytes() {
+        if (StandardNames.IS_RI) {
+            return new byte[] { 0x30, 0x10, 0x06, 0x03, 0x2a, 0x03, 0x04, (byte) 0xa0,
+                    0x09, (byte) 0xa0, 0x07, 0x0c, 0x05, 0x74, 0x65, 0x73, 0x74, 0x31 };
+        } else {
+            return new byte[] { (byte) 0xa0, 0x0e, 0x06, 0x03, 0x2a, 0x03, 0x04,
+                    (byte) 0xa0, 0x07, 0x0c, 0x05, 0x74, 0x65, 0x73, 0x74, 0x31 };
+        }
+    }
+
+    private void getSubjectAlternativeNames(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        checkAlternativeNames(col);
+    }
+
+    private void checkAlternativeNames(Collection<List<?>> col) {
+        assertNotNull(col);
+
+        /* Check to see that the Collection is unmodifiable. */
+        {
+            try {
+                col.add(new ArrayList<Object>());
+                fail("should be an unmodifiable list");
+            } catch (UnsupportedOperationException expected) {
+            }
+        }
+
+        /*
+         * There should be 9 types of alternative names in this test
+         * certificate.
+         */
+        boolean[] typesFound = new boolean[9];
+
+        for (List<?> item : col) {
+            /* Check to see that the List is unmodifiable. */
+            {
+                try {
+                    item.remove(0);
+                    fail("should be an unmodifiable list");
+                } catch (UnsupportedOperationException expected) {
+                }
+            }
+
+            assertTrue(item.get(0) instanceof Integer);
+            int type = (Integer) item.get(0);
+            typesFound[type] = true;
+
+            switch (type) {
+            case 0: /* OtherName */
+                final byte[] der = getOIDTestBytes();
+                assertEquals(Arrays.toString(der), Arrays.toString((byte[]) item.get(1)));
+                break;
+            case 1: /* rfc822Name: IA5String */
+                assertEquals("x509@example.com", (String) item.get(1));
+                break;
+            case 2: /* dNSName: IA5String */
+                assertEquals("x509.example.com", (String) item.get(1));
+                break;
+            case 3: /* x400Address: ORAddress */
+                assertEquals("UNSUPPORTED", (String) item.get(1));
+                break;
+            case 4: /* directoryName: Name */
+                assertEquals("CN=∆ƒ,OU=Über Frîends,O=Awesome Dudes,C=US", (String) item.get(1));
+                break;
+            case 5: /* ediPartyName */
+                assertEquals("UNSUPPORTED", Arrays.toString((byte[]) item.get(1)));
+                break;
+            case 6: /* uniformResourceIdentifier: IA5String */
+                assertEquals("http://www.example.com/?q=awesomeness", (String) item.get(1));
+                break;
+            case 7: /* iPAddress */
+                assertEquals("192.168.0.1", (String) item.get(1));
+                break;
+            case 8:
+                assertEquals("1.2.3.4", (String) item.get(1));
+                break;
+            }
+        }
+
+        Set<Integer> missing = new HashSet<Integer>();
+        for (int i = 0; i < typesFound.length; i++) {
+            if (!typesFound[i]) {
+                missing.add(i);
+            }
+        }
+
+        // TODO: fix X.400 names and ediPartyName
+        missing.remove(3);
+        missing.remove(5);
+
+        if (!missing.isEmpty()) {
+            fail("Missing types: " + Arrays.toString(missing.toArray(new Integer[missing.size()])));
+        }
+    }
+
+    private void getSubjectAlternativeNames_IPV6(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_IPV6);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(7 == (Integer) item.get(0));
+
+        assertTrue(item.get(1) instanceof String);
+        // RI doesn't apply all the IPv6 shortening rules
+        if (StandardNames.IS_RI) {
+            assertEquals("2001:db8:0:0:0:ff00:42:8329", (String) item.get(1));
+        } else {
+            assertEquals("2001:db8::ff00:42:8329", (String) item.get(1));
+        }
+    }
+
+    private void getSubjectAlternativeNames_InvalidIP(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_INVALIDIP);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+        assertNull(col);
+    }
+
+    private void getSubjectAlternativeNames_Other(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_OTHER);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(0 == (Integer) item.get(0));
+
+        /* OID:1.2.3.4, UTF8:test1 */
+        final byte[] der = getOIDTestBytes();
+        final byte[] actual = (byte[]) item.get(1);
+        assertEquals(Arrays.toString(der), Arrays.toString(actual));
+
+        /* Make sure the byte[] array isn't modified by our test. */
+        {
+            actual[0] ^= (byte) 0xFF;
+            byte[] actual2 = (byte[]) c.getSubjectAlternativeNames().iterator().next().get(1);
+
+            if (!StandardNames.IS_RI) {
+                assertEquals(Arrays.toString(der), Arrays.toString(actual2));
+            } else {
+                /* RI is broken here. */
+                assertEquals(Arrays.toString(actual), Arrays.toString(actual2));
+            }
+        }
+    }
+
+    private void getSubjectAlternativeNames_Email(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_EMAIL);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(1 == (Integer) item.get(0));
+
+        assertTrue(item.get(1) instanceof String);
+        assertEquals("x509@example.com", (String) item.get(1));
+    }
+
+    private void getSubjectAlternativeNames_DNS(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_DNS);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(2 == (Integer) item.get(0));
+
+        assertTrue(item.get(1) instanceof String);
+        assertEquals("x509.example.com", (String) item.get(1));
+    }
+
+    private void getSubjectAlternativeNames_DirName(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_DIRNAME);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(String.valueOf((Integer) item.get(0)), 4 == (Integer) item.get(0));
+
+        assertTrue(item.get(1) instanceof String);
+        assertEquals("CN=∆ƒ,OU=Über Frîends,O=Awesome Dudes,C=US", (String) item.get(1));
+    }
+
+    private void getSubjectAlternativeNames_URI(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_URI);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(6 == (Integer) item.get(0));
+
+        assertTrue(item.get(1) instanceof String);
+        assertEquals("http://www.example.com/?q=awesomeness", (String) item.get(1));
+    }
+
+    private void getSubjectAlternativeNames_RID(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_RID);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+
+        assertNotNull(f.getProvider().getName(), col);
+
+        assertEquals(1, col.size());
+        List<?> item = col.iterator().next();
+
+        assertTrue(item.get(0) instanceof Integer);
+        assertTrue(8 == (Integer) item.get(0));
+
+        assertTrue(item.get(1) instanceof String);
+        assertEquals("1.2.3.4", (String) item.get(1));
+    }
+
+    private void getSubjectAlternativeNames_None(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_ALT_NONE);
+        Collection<List<?>> col = c.getSubjectAlternativeNames();
+        assertNull(col);
+    }
+
+    private void getIssuerAlternativeNames(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        Collection<List<?>> col = c.getIssuerAlternativeNames();
+
+        checkAlternativeNames(col);
+    }
+
+    private void getSignature(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        assertEquals(Arrays.toString(getRsaCertificateSignature()),
+                     Arrays.toString(c.getSignature()));
+    }
+
+    private void getTBSCertificate(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        assertEquals(Arrays.toString(getRsaCertificateTbs()),
+                     Arrays.toString(c.getTBSCertificate()));
+    }
+
+    private void hasUnsupportedCriticalExtension(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+        assertFalse(c.hasUnsupportedCriticalExtension());
+
+        X509Certificate unsupported = getCertificate(f, CERT_UNSUPPORTED);
+        assertTrue(unsupported.hasUnsupportedCriticalExtension());
+    }
+
+    private void getEncoded(CertificateFactory f) throws Exception {
+        X509Certificate c = getCertificate(f, CERT_RSA);
+
+        byte[] cBytes = getResourceAsBytes(CERT_RSA);
+
+        assertEquals(Arrays.toString(cBytes), Arrays.toString(c.getEncoded()));
+    }
+
+    private void generateCertificate_PEM_TrailingData(CertificateFactory f) throws Exception {
+        byte[] certsBytes = getResourceAsBytes(CERTS_X509_PEM);
+        byte[] certsTwice = new byte[certsBytes.length * 2];
+        System.arraycopy(certsBytes, 0, certsTwice, 0, certsBytes.length);
+        System.arraycopy(certsBytes, 0, certsTwice, certsBytes.length, certsBytes.length);
+        ByteArrayInputStream bais = new ByteArrayInputStream(certsTwice);
+
+        assertEquals(certsBytes.length * 2, bais.available());
+        X509Certificate cert1 = (X509Certificate) f.generateCertificate(bais);
+        // TODO: If we had a single PEM certificate, we could know exact bytes.
+        assertTrue(certsBytes.length < bais.available());
+    }
+
+    private void generateCertificate_DER_TrailingData(CertificateFactory f) throws Exception {
+        byte[] cert1Bytes = getResourceAsBytes(CERT_RSA);
+        byte[] cert1WithTrailing = new byte[cert1Bytes.length * 2];
+        System.arraycopy(cert1Bytes, 0, cert1WithTrailing, 0, cert1Bytes.length);
+        System.arraycopy(cert1Bytes, 0, cert1WithTrailing, cert1Bytes.length, cert1Bytes.length);
+        ByteArrayInputStream bais = new ByteArrayInputStream(cert1WithTrailing);
+
+        assertEquals(cert1Bytes.length * 2, bais.available());
+        X509Certificate cert1 = (X509Certificate) f.generateCertificate(bais);
+        assertEquals(cert1Bytes.length, bais.available());
+    }
+
+    private void generateCertificates_X509_DER(CertificateFactory f) throws Exception {
+        /* DER-encoded list of certificates */
+        Collection<? extends X509Certificate> certs = getCertificates(f, CERTS_X509_DER);
+        assertNotNull(certs);
+        assertEquals(2, certs.size());
+    }
+
+    private void generateCertificates_X509_PEM(CertificateFactory f) throws Exception {
+        /* PEM-encoded list of certificates */
+        Collection<? extends X509Certificate> certs = getCertificates(f, CERTS_X509_PEM);
+        assertNotNull(certs);
+        assertEquals(2, certs.size());
+    }
+
+    private void generateCertificates_PKCS7_PEM(CertificateFactory f) throws Exception {
+        /* PEM-encoded PKCS7 bag of certificates */
+        Collection<? extends X509Certificate> certs = getCertificates(f, CERTS_PKCS7_PEM);
+        assertNotNull(certs);
+        if ("BC".equals(f.getProvider().getName())) {
+            // Bouncycastle is broken
+            assertEquals(0, certs.size());
+        } else {
+            assertEquals(2, certs.size());
+        }
+    }
+
+    private void generateCertificates_PKCS7_DER(CertificateFactory f) throws Exception {
+        /* DER-encoded PKCS7 bag of certificates */
+        Collection<? extends X509Certificate> certs = getCertificates(f, CERTS_PKCS7_DER);
+        assertNotNull(certs);
+        assertEquals(2, certs.size());
+    }
+
+    private void generateCertificates_Empty(CertificateFactory f) throws Exception {
+        final InputStream is = new ByteArrayInputStream(new byte[0]);
+
+        final Collection<? extends Certificate> certs = f.generateCertificates(is);
+
+        assertNotNull(certs);
+        assertEquals(0, certs.size());
+    }
+
+    private void generateCertificates_X509_PEM_TrailingData(CertificateFactory f) throws Exception {
+        byte[] certBytes = getResourceAsBytes(CERTS_X509_PEM);
+        byte[] certsPlusExtra = new byte[certBytes.length + 4096];
+        System.arraycopy(certBytes, 0, certsPlusExtra, 0, certBytes.length);
+        ByteArrayInputStream bais = new ByteArrayInputStream(certsPlusExtra);
+
+        assertEquals(certsPlusExtra.length, bais.available());
+
+        // RI is broken
+        try {
+            Collection<? extends X509Certificate> certs = (Collection<? extends X509Certificate>)
+                    f.generateCertificates(bais);
+            if (StandardNames.IS_RI) {
+                fail("RI fails on this test.");
+            }
+        } catch (CertificateParsingException e) {
+            if (StandardNames.IS_RI) {
+                return;
+            }
+            throw e;
+        }
+
+        // Bouncycastle is broken
+        if ("BC".equals(f.getProvider().getName())) {
+            assertEquals(0, bais.available());
+        } else {
+            assertEquals(4096, bais.available());
+        }
+    }
+
+    private void generateCertificates_X509_DER_TrailingData(CertificateFactory f) throws Exception {
+        byte[] certBytes = getResourceAsBytes(CERTS_X509_DER);
+        byte[] certsPlusExtra = new byte[certBytes.length + 4096];
+        System.arraycopy(certBytes, 0, certsPlusExtra, 0, certBytes.length);
+        ByteArrayInputStream bais = new ByteArrayInputStream(certsPlusExtra);
+
+        assertEquals(certsPlusExtra.length, bais.available());
+
+        // RI is broken
+        try {
+            Collection<? extends X509Certificate> certs = (Collection<? extends X509Certificate>)
+                    f.generateCertificates(bais);
+            if (StandardNames.IS_RI) {
+                fail("RI fails on this test.");
+            }
+        } catch (CertificateParsingException e) {
+            if (StandardNames.IS_RI) {
+                return;
+            }
+            throw e;
+        }
+
+        // Bouncycastle is broken
+        if ("BC".equals(f.getProvider().getName())) {
+            assertEquals(0, bais.available());
+        } else {
+            assertEquals(4096, bais.available());
+        }
+    }
+
+    private void generateCertificates_PKCS7_PEM_TrailingData(CertificateFactory f) throws Exception {
+        byte[] certBytes = getResourceAsBytes(CERTS_PKCS7_PEM);
+        byte[] certsPlusExtra = new byte[certBytes.length + 4096];
+        System.arraycopy(certBytes, 0, certsPlusExtra, 0, certBytes.length);
+        ByteArrayInputStream bais = new ByteArrayInputStream(certsPlusExtra);
+
+        assertEquals(certsPlusExtra.length, bais.available());
+        Collection<? extends X509Certificate> certs = (Collection<? extends X509Certificate>)
+                f.generateCertificates(bais);
+
+        // Bouncycastle is broken
+        if ("BC".equals(f.getProvider().getName())) {
+            assertEquals(0, bais.available());
+        } else {
+            assertEquals(4096, bais.available());
+        }
+    }
+
+    private void generateCertificates_PKCS7_DER_TrailingData(CertificateFactory f) throws Exception {
+        byte[] certBytes = getResourceAsBytes(CERTS_PKCS7_DER);
+        byte[] certsPlusExtra = new byte[certBytes.length + 4096];
+        System.arraycopy(certBytes, 0, certsPlusExtra, 0, certBytes.length);
+        ByteArrayInputStream bais = new ByteArrayInputStream(certsPlusExtra);
+
+        assertEquals(certsPlusExtra.length, bais.available());
+        Collection<? extends X509Certificate> certs = (Collection<? extends X509Certificate>)
+                f.generateCertificates(bais);
+
+        // RI is broken
+        if (StandardNames.IS_RI) {
+            assertEquals(0, bais.available());
+        } else {
+            assertEquals(4096, bais.available());
+        }
+    }
+
+    private void test_Serialization(CertificateFactory f) throws Exception {
+        for (String certName : VARIOUS_CERTS) {
+            X509Certificate expected = getCertificate(f, certName);
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            try {
+                oos.writeObject(expected);
+            } finally {
+                oos.close();
+            }
+
+            byte[] certBytes = baos.toByteArray();
+
+            ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
+            try {
+                ObjectInputStream ois = new ObjectInputStream(bais);
+
+                X509Certificate actual = (X509Certificate) ois.readObject();
+
+                assertEquals(certName, expected, actual);
+            } finally {
+                bais.close();
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mX509Providers = Security.getProviders("CertificateFactory.X509");
+    }
+}
diff --git a/luni/src/test/java/libcore/java/security/cert/certs.der b/luni/src/test/java/libcore/java/security/cert/certs.der
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/luni/src/test/java/libcore/java/security/cert/certs.der
diff --git a/luni/src/test/java/libcore/java/text/BreakIteratorTest.java b/luni/src/test/java/libcore/java/text/BreakIteratorTest.java
index 45d31b5..47701c8 100644
--- a/luni/src/test/java/libcore/java/text/BreakIteratorTest.java
+++ b/luni/src/test/java/libcore/java/text/BreakIteratorTest.java
@@ -17,6 +17,7 @@
 package libcore.java.text;
 
 import java.text.BreakIterator;
+import java.util.ArrayList;
 import java.util.Locale;
 
 public class BreakIteratorTest extends junit.framework.TestCase {
@@ -50,6 +51,15 @@
         assertTrue("Incorrect BreakIterator", it2 != BreakIterator.getWordInstance());
     }
 
+    // http://b/7307154 - we used to pin an unbounded number of char[]s, relying on finalization.
+    public void testStress() throws Exception {
+        char[] cs = { 'a' };
+        for (int i = 0; i < 4096; ++i) {
+            BreakIterator it = BreakIterator.getWordInstance(Locale.US);
+            it.setText(new String(cs));
+        }
+    }
+
     public void testWordBoundaries() {
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < 1024; ++i) {
@@ -163,4 +173,31 @@
         }
     }
 
+    // http://code.google.com/p/android/issues/detail?id=41143
+    // This code is inherently unsafe and crazy;
+    // we're just trying to provoke native crashes!
+    public void testConcurrentBreakIteratorAccess() throws Exception {
+        final BreakIterator it = BreakIterator.getCharacterInstance();
+
+        ArrayList<Thread> threads = new ArrayList<Thread>();
+        for (int i = 0; i < 10; ++i) {
+            Thread t = new Thread(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < 4096; ++i) {
+                        it.setText("some example text");
+                        for (int index = it.first(); index != BreakIterator.DONE; index = it.next()) {
+                        }
+                    }
+                }
+            });
+            threads.add(t);
+        }
+
+        for (Thread t : threads) {
+            t.start();
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
index 319a76e..e13e4df 100644
--- a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java
@@ -67,9 +67,10 @@
         assertEquals("stycznia", formatDate(pl, "MMMM", originalDfs));
         assertEquals("stycze\u0144", formatDate(pl, "LLLL", originalDfs));
 
-        // Whereas the deserialized object can't, because it lost the strings...
+        // But the deserialized object is screwed because the RI's serialized form doesn't
+        // contain the locale or the necessary strings. Don't serialize DateFormatSymbols, folks!
         assertEquals("stycznia", formatDate(pl, "MMMM", deserializedDfs));
-        assertEquals("stycznia", formatDate(pl, "LLLL", deserializedDfs));
+        assertEquals("January", formatDate(pl, "LLLL", deserializedDfs));
     }
 
     private String formatDate(Locale l, String fmt, DateFormatSymbols dfs) {
@@ -123,4 +124,38 @@
         assertEquals(Arrays.toString(row), "UTC", row[2]);
         assertEquals(Arrays.toString(row), "UTC", row[4]);
     }
+
+    // http://b/8128460
+    // If icu4c doesn't actually have a name, we arrange to return null from native code rather
+    // that use icu4c's probably-out-of-date time zone transition data.
+    // getZoneStrings has to paper over this.
+    public void test_getZoneStrings_no_nulls() throws Exception {
+        String[][] array = DateFormatSymbols.getInstance(Locale.US).getZoneStrings();
+        int failCount = 0;
+        for (String[] row : array) {
+            for (String element : row) {
+                if (element == null) {
+                    System.err.println(Arrays.toString(row));
+                    ++failCount;
+                }
+            }
+        }
+        assertEquals(0, failCount);
+    }
+
+    // http://b/7955614
+    public void test_getZoneStrings_Apia() throws Exception {
+        String[][] array = DateFormatSymbols.getInstance(Locale.US).getZoneStrings();
+        for (int i = 0; i < array.length; ++i) {
+            String[] row = array[i];
+            // Pacific/Apia is somewhat arbitrary; we just want a zone we have to generate
+            // "GMT" strings for the short names.
+            if (row[0].equals("Pacific/Apia")) {
+                assertEquals("Samoa Standard Time", row[1]);
+                assertEquals("GMT+13:00", row[2]);
+                assertEquals("Samoa Daylight Time", row[3]);
+                assertEquals("GMT+14:00", row[4]);
+            }
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/text/NumberFormatTest.java b/luni/src/test/java/libcore/java/text/NumberFormatTest.java
index c2621b4..ce80bd1 100644
--- a/luni/src/test/java/libcore/java/text/NumberFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/NumberFormatTest.java
@@ -70,9 +70,9 @@
             return;
         }
         NumberFormat numberFormat = NumberFormat.getNumberInstance(new Locale("ar"));
-        assertEquals("#,##0.###;#,##0.###-", ((DecimalFormat) numberFormat).toPattern());
+        assertEquals("#0.###;#0.###-", ((DecimalFormat) numberFormat).toPattern());
         NumberFormat integerFormat = NumberFormat.getIntegerInstance(new Locale("ar"));
-        assertEquals("#,##0;#,##0-", ((DecimalFormat) integerFormat).toPattern());
+        assertEquals("#0;#0-", ((DecimalFormat) integerFormat).toPattern());
     }
 
     public void test_numberLocalization() throws Exception {
@@ -82,8 +82,7 @@
         }
         NumberFormat nf = NumberFormat.getNumberInstance(arabic);
         assertEquals('\u0660', new DecimalFormatSymbols(arabic).getZeroDigit());
-        assertEquals("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0666\u0667\u066c\u0668\u0669\u0660",
-                nf.format(1234567890));
+        assertEquals("١٢٣٤٥٦٧٨٩٠", nf.format(1234567890));
     }
 
     // Formatting percentages is confusing but deliberate.
diff --git a/luni/src/test/java/libcore/java/text/OldDecimalFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/OldDecimalFormatSymbolsTest.java
index 2f1a3cc..c078684 100644
--- a/luni/src/test/java/libcore/java/text/OldDecimalFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/OldDecimalFormatSymbolsTest.java
@@ -33,16 +33,18 @@
     public void test_RIHarmony_compatible() throws Exception {
         ObjectInputStream i = null;
         try {
-            DecimalFormatSymbols symbols = new DecimalFormatSymbols(
-                    Locale.FRANCE);
+            DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.FRANCE);
             i = new ObjectInputStream(
                     getClass()
                             .getClassLoader()
                             .getResourceAsStream(
                     "serialization/java/text/DecimalFormatSymbols.ser"));
-            DecimalFormatSymbols symbolsD = (DecimalFormatSymbols) i
-                    .readObject();
-            assertEquals(symbols, symbolsD);
+            DecimalFormatSymbols riSymbols = (DecimalFormatSymbols) i.readObject();
+            // RI's default NaN is U+FFFD, Harmony's is based on ICU
+            // This suggests an RI bug, assuming that non-UTF8 bytes are UTF8 and
+            // getting a conversion failure.
+            riSymbols.setNaN("NaN");
+            assertEquals(symbols, riSymbols);
         } catch(NullPointerException e) {
             assertNotNull("Failed to load /serialization/java/text/" +
                     "DecimalFormatSymbols.ser", i);
@@ -54,7 +56,6 @@
             } catch (Exception e) {
             }
         }
-        assertDecimalFormatSymbolsRIFrance(dfs);
     }
 
 
@@ -121,8 +122,7 @@
                 '#', dfs.getMonetaryDecimalSeparator());
     }
 
-    static void assertDecimalFormatSymbolsRIFrance(DecimalFormatSymbols dfs) {
-        // Values based on Java 1.5 RI DecimalFormatSymbols for Locale.FRANCE
+    public void test_DecimalFormatSymbols_France() {
         /*
          * currency = [EUR]
          * currencySymbol = [U+20ac] // EURO SIGN
@@ -133,12 +133,13 @@
          * internationalCurrencySymbol = [EUR]
          * minusSign = [-][U+002d]
          * monetaryDecimalSeparator = [,][U+002c]
-         * naN = [U+fffd] // REPLACEMENT CHARACTER
+         * naN = "NaN"
          * patternSeparator = [;][U+003b]
          * perMill = [U+2030] // PER MILLE
          * percent = [%][U+0025]
          * zeroDigit = [0][U+0030]
          */
+        DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.FRANCE);
         assertEquals("EUR", dfs.getCurrency().getCurrencyCode());
         assertEquals("\u20AC", dfs.getCurrencySymbol());
         assertEquals(',', dfs.getDecimalSeparator());
@@ -151,7 +152,7 @@
         // RI's default NaN is U+FFFD, Harmony's is based on ICU
         // This suggests an RI bug, assuming that non-UTF8 bytes are UTF8 and
         // getting a conversion failure.
-        assertEquals("\uFFFD", dfs.getNaN());
+        assertEquals("NaN", dfs.getNaN());
         assertEquals('\u003b', dfs.getPatternSeparator());
         assertEquals('\u2030', dfs.getPerMill());
         assertEquals('%', dfs.getPercent());
diff --git a/luni/src/test/java/libcore/java/text/OldNumberFormatTest.java b/luni/src/test/java/libcore/java/text/OldNumberFormatTest.java
index 64fd7ca..7bb5545 100644
--- a/luni/src/test/java/libcore/java/text/OldNumberFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/OldNumberFormatTest.java
@@ -53,7 +53,7 @@
         Locale arLocale = new Locale("ar", "AE");
         if (Support_Locale.isLocaleAvailable(arLocale)) {
             format = (DecimalFormat) NumberFormat.getIntegerInstance(arLocale);
-            assertEquals("#,##0;#,##0-", format.toPattern());
+            assertEquals("#0;#0-", format.toPattern());
             assertEquals("\u0666-", format.format(-6));
             assertEquals(new Long(-36), format.parse("36-"));
             assertEquals(new Long(-36), format.parseObject("36-"));
diff --git a/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java b/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java
index ff24bb6..1cc7554 100644
--- a/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java
@@ -245,7 +245,7 @@
         test.test(" MM", cal, " 06", DateFormat.MONTH_FIELD);
         test.test(" MMM", cal, " Jun", DateFormat.MONTH_FIELD);
         test.test(" MMMM", cal, " June", DateFormat.MONTH_FIELD);
-        test.test(" MMMMM", cal, " June", DateFormat.MONTH_FIELD);
+        test.test(" MMMMM", cal, " J", DateFormat.MONTH_FIELD);
 
         test.test(" d", cal, " 2", DateFormat.DATE_FIELD);
         test.test(" d", new GregorianCalendar(1999, Calendar.NOVEMBER, 12),
@@ -295,7 +295,7 @@
         test.test(" EE", cal, " Wed", DateFormat.DAY_OF_WEEK_FIELD);
         test.test(" EEE", cal, " Wed", DateFormat.DAY_OF_WEEK_FIELD);
         test.test(" EEEE", cal, " Wednesday", DateFormat.DAY_OF_WEEK_FIELD);
-        test.test(" EEEEE", cal, " Wednesday", DateFormat.DAY_OF_WEEK_FIELD);
+        test.test(" EEEEE", cal, " W", DateFormat.DAY_OF_WEEK_FIELD);
 
         test.test(" D", cal, " 153", DateFormat.DAY_OF_YEAR_FIELD);
         test.test(" DD", cal, " 153", DateFormat.DAY_OF_YEAR_FIELD);
@@ -405,7 +405,8 @@
 
         format.setTimeZone(tz0001);
         test.test(" Z", cal, " +0001", DateFormat.TIMEZONE_FIELD);
-        test.test(" ZZZZ", cal, " +0001", DateFormat.TIMEZONE_FIELD);
+        test.test(" ZZZZ", cal, " GMT+00:01", DateFormat.TIMEZONE_FIELD);
+        test.test(" ZZZZZ", cal, " +00:01", DateFormat.TIMEZONE_FIELD);
         format.setTimeZone(tz0130);
         test.test(" Z", cal, " +0130", DateFormat.TIMEZONE_FIELD);
         format.setTimeZone(tzMinus0130);
@@ -441,22 +442,18 @@
         Date winterDate = new GregorianCalendar(1999, Calendar.JANUARY, 12).getTime();
 
         FormatTester test = new FormatTester();
-        test.verifyFormatTimezone("GMT-7", "GMT-07:00, GMT-07:00", "-0700, -0700", summerDate);
-        test.verifyFormatTimezone("GMT-7", "GMT-07:00, GMT-07:00", "-0700, -0700", winterDate);
+        test.verifyFormatTimezone("GMT-7", "GMT-07:00, GMT-07:00", "-0700, GMT-07:00", summerDate);
+        test.verifyFormatTimezone("GMT-7", "GMT-07:00, GMT-07:00", "-0700, GMT-07:00", winterDate);
 
-        test.verifyFormatTimezone("GMT+14", "GMT+14:00, GMT+14:00", "+1400, +1400", summerDate);
-        test.verifyFormatTimezone("GMT+14", "GMT+14:00, GMT+14:00", "+1400, +1400", winterDate);
+        test.verifyFormatTimezone("GMT+14", "GMT+14:00, GMT+14:00", "+1400, GMT+14:00", summerDate);
+        test.verifyFormatTimezone("GMT+14", "GMT+14:00, GMT+14:00", "+1400, GMT+14:00", winterDate);
 
-        test.verifyFormatTimezone("America/Los_Angeles", "PDT, Pacific Daylight Time",
-                "-0700, -0700", summerDate);
-        test.verifyFormatTimezone("America/Los_Angeles", "PST, Pacific Standard Time",
-                "-0800, -0800", winterDate);
+        test.verifyFormatTimezone("America/Los_Angeles", "PDT, Pacific Daylight Time", "-0700, GMT-07:00", summerDate);
+        test.verifyFormatTimezone("America/Los_Angeles", "PST, Pacific Standard Time", "-0800, GMT-08:00", winterDate);
 
         // this fails on the RI!
-        test.verifyFormatTimezone("America/Detroit", "EDT, Eastern Daylight Time",
-                "-0400, -0400", summerDate);
-        test.verifyFormatTimezone("America/Detroit", "EST, Eastern Standard Time",
-                "-0500, -0500", winterDate);
+        test.verifyFormatTimezone("America/Detroit", "EDT, Eastern Daylight Time", "-0400, GMT-04:00", summerDate);
+        test.verifyFormatTimezone("America/Detroit", "EST, Eastern Standard Time", "-0500, GMT-05:00", winterDate);
 
         assertFalse(test.testsFailed);
     }
diff --git a/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java b/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
index cd54d1e..21d7302 100644
--- a/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/SimpleDateFormatTest.java
@@ -77,6 +77,104 @@
         assertEquals(Calendar.TUESDAY, parseDate(ru, "cccc", "\u0412\u0442\u043e\u0440\u043d\u0438\u043a").get(Calendar.DAY_OF_WEEK));
     }
 
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void testFiveCount_parsing() throws Exception {
+      // It's pretty silly to try to parse the shortest names, because they're almost always ambiguous.
+      try {
+        parseDate(Locale.ENGLISH, "MMMMM", "J");
+        fail();
+      } catch (junit.framework.AssertionFailedError expected) {
+      }
+      try {
+        parseDate(Locale.ENGLISH, "LLLLL", "J");
+        fail();
+      } catch (junit.framework.AssertionFailedError expected) {
+      }
+      try {
+        parseDate(Locale.ENGLISH, "EEEEE", "T");
+        fail();
+      } catch (junit.framework.AssertionFailedError expected) {
+      }
+      try {
+        parseDate(Locale.ENGLISH, "ccccc", "T");
+        fail();
+      } catch (junit.framework.AssertionFailedError expected) {
+      }
+    }
+
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void testFiveCount_M() throws Exception {
+      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+      assertEquals("1", formatDate(Locale.ENGLISH, "M"));
+      assertEquals("01", formatDate(Locale.ENGLISH, "MM"));
+      assertEquals("Jan", formatDate(Locale.ENGLISH, "MMM"));
+      assertEquals("January", formatDate(Locale.ENGLISH, "MMMM"));
+      assertEquals("J", formatDate(Locale.ENGLISH, "MMMMM"));
+    }
+
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void testFiveCount_L() throws Exception {
+      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+      assertEquals("1", formatDate(Locale.ENGLISH, "L"));
+      assertEquals("01", formatDate(Locale.ENGLISH, "LL"));
+      assertEquals("Jan", formatDate(Locale.ENGLISH, "LLL"));
+      assertEquals("January", formatDate(Locale.ENGLISH, "LLLL"));
+      assertEquals("J", formatDate(Locale.ENGLISH, "LLLLL"));
+    }
+
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void testFiveCount_E() throws Exception {
+      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+      assertEquals("Thu", formatDate(Locale.ENGLISH, "E"));
+      assertEquals("Thu", formatDate(Locale.ENGLISH, "EE"));
+      assertEquals("Thu", formatDate(Locale.ENGLISH, "EEE"));
+      assertEquals("Thursday", formatDate(Locale.ENGLISH, "EEEE"));
+      assertEquals("T", formatDate(Locale.ENGLISH, "EEEEE"));
+      // assertEquals("Th", formatDate(Locale.ENGLISH, "EEEEEE")); // icu4c doesn't support 6.
+    }
+
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void testFiveCount_c() throws Exception {
+      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+      assertEquals("Thu", formatDate(Locale.ENGLISH, "c"));
+      assertEquals("Thu", formatDate(Locale.ENGLISH, "cc"));
+      assertEquals("Thu", formatDate(Locale.ENGLISH, "ccc"));
+      assertEquals("Thursday", formatDate(Locale.ENGLISH, "cccc"));
+      assertEquals("T", formatDate(Locale.ENGLISH, "ccccc"));
+      // assertEquals("Th", formatDate(Locale.ENGLISH, "cccccc")); // icu4c doesn't support 6.
+    }
+
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void testFiveCount_Z() throws Exception {
+      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+      assertEquals("+0000", formatDate(Locale.ENGLISH, "Z"));
+      assertEquals("+0000", formatDate(Locale.ENGLISH, "ZZ"));
+      assertEquals("+0000", formatDate(Locale.ENGLISH, "ZZZ"));
+      assertEquals("GMT+00:00", formatDate(Locale.ENGLISH, "ZZZZ"));
+      assertEquals("+00:00", formatDate(Locale.ENGLISH, "ZZZZZ"));
+    }
+
+    // The RI fails this test because it doesn't fully support UTS #35.
+    // https://code.google.com/p/android/issues/detail?id=39616
+    public void test_parsing_Z() throws Exception {
+      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+      assertEquals(1325421240000L, parseTime("yyyy-MM-dd' 'Z", "2012-01-01 -1234"));
+      assertEquals(1325421240000L, parseTime("yyyy-MM-dd' 'ZZ", "2012-01-01 -1234"));
+      assertEquals(1325421240000L, parseTime("yyyy-MM-dd' 'ZZZ", "2012-01-01 -1234"));
+      assertEquals(1325421240000L, parseTime("yyyy-MM-dd' 'ZZZZ", "2012-01-01 GMT-12:34"));
+      assertEquals(1325421240000L, parseTime("yyyy-MM-dd' 'ZZZZZ", "2012-01-01 -12:34"));
+    }
+
+    private static long parseTime(String fmt, String value) {
+      return parseDate(Locale.ENGLISH, fmt, value).getTime().getTime();
+    }
+
     public void test2038() {
         SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.US);
         format.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -92,13 +190,14 @@
         assertEquals("Sun Feb 07 06:28:16 2106",
                 format.format(new Date((2L + Integer.MAX_VALUE + Integer.MAX_VALUE) * 1000L)));
     }
+
     private String formatDate(Locale l, String fmt) {
         DateFormat dateFormat = new SimpleDateFormat(fmt, l);
         dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
         return dateFormat.format(new Date(0));
     }
 
-    private Calendar parseDate(Locale l, String fmt, String value) {
+    private static Calendar parseDate(Locale l, String fmt, String value) {
         SimpleDateFormat sdf = new SimpleDateFormat(fmt, l);
         ParsePosition pp = new ParsePosition(0);
         Date d = sdf.parse(value, pp);
diff --git a/luni/src/test/java/libcore/java/util/AbstractCollectionTest.java b/luni/src/test/java/libcore/java/util/AbstractCollectionTest.java
new file mode 100644
index 0000000..2e6905b
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/AbstractCollectionTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.java.util;
+
+import java.io.Serializable;
+import java.util.AbstractCollection;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.ConcurrentHashMap;
+import junit.framework.TestCase;
+
+public final class AbstractCollectionTest extends TestCase {
+  // http://code.google.com/p/android/issues/detail?id=36519
+  public void test_toArray() throws Exception {
+    final ConcurrentHashMap<Integer, Integer> m = new ConcurrentHashMap<Integer, Integer>();
+    assertTrue(m.values() instanceof AbstractCollection);
+
+    final AtomicBoolean finished = new AtomicBoolean(false);
+
+    Thread reader = new Thread(new Runnable() {
+      @Override public void run() {
+        while (!finished.get()) {
+          m.values().toArray();
+          m.values().toArray(new Integer[m.size()]);
+        }
+      }
+    });
+
+    Thread mutator = new Thread(new Runnable() {
+      @Override public void run() {
+        for (int i = 0; i < 100; ++i) {
+          m.put(-i, -i);
+        }
+        for (int i = 0; i < 4096; ++i) {
+          m.put(i, i);
+          m.remove(i);
+        }
+        finished.set(true);
+      }
+    });
+
+    reader.start();
+    mutator.start();
+    reader.join();
+    mutator.join();
+  }
+}
diff --git a/luni/src/test/java/libcore/java/util/CalendarTest.java b/luni/src/test/java/libcore/java/util/CalendarTest.java
index 5c36fe2..dd44789 100644
--- a/luni/src/test/java/libcore/java/util/CalendarTest.java
+++ b/luni/src/test/java/libcore/java/util/CalendarTest.java
@@ -205,4 +205,35 @@
         double ONE_HOUR = 3600d * 1000d;
         return c.getTimeInMillis() / ONE_HOUR;
     }
+
+    // https://code.google.com/p/android/issues/detail?id=45877
+    public void test_clear_45877() {
+      GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("America/Los_Angeles"));
+      cal.set(Calendar.YEAR, 1970);
+      cal.set(Calendar.MONTH, Calendar.JANUARY);
+      cal.set(Calendar.DAY_OF_MONTH, 1);
+      cal.clear(Calendar.HOUR_OF_DAY);
+      cal.clear(Calendar.HOUR);
+      cal.clear(Calendar.MINUTE);
+      cal.clear(Calendar.SECOND);
+      cal.clear(Calendar.MILLISECOND);
+
+      // Now we have a mix of set and unset fields.
+      assertTrue(cal.isSet(Calendar.DAY_OF_MONTH));
+      assertFalse(cal.isSet(Calendar.HOUR_OF_DAY));
+
+      // When we call get, unset fields are computed.
+      assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
+      // And set fields stay the same.
+      assertEquals(1, cal.get(Calendar.DAY_OF_MONTH));
+
+      // ...so now everything is set.
+      assertTrue(cal.isSet(Calendar.DAY_OF_MONTH));
+      assertTrue(cal.isSet(Calendar.HOUR_OF_DAY));
+
+      assertEquals(28800000, cal.getTimeInMillis());
+
+      cal.set(Calendar.HOUR_OF_DAY, 1);
+      assertEquals(32400000, cal.getTimeInMillis());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/CurrencyTest.java b/luni/src/test/java/libcore/java/util/CurrencyTest.java
index 13e2242..8504eaf 100644
--- a/luni/src/test/java/libcore/java/util/CurrencyTest.java
+++ b/luni/src/test/java/libcore/java/util/CurrencyTest.java
@@ -68,4 +68,17 @@
         assertEquals(0, Currency.getInstance("JPY").getDefaultFractionDigits());
         assertEquals(-1, Currency.getInstance("XXX").getDefaultFractionDigits());
     }
+
+    // http://code.google.com/p/android/issues/detail?id=38622
+    public void test_getSymbol_38622() throws Exception {
+        // The CLDR data had the Portuguese symbol for "EUR" in pt, not in pt_PT.
+        // We weren't falling back from pt_PT to pt, so we didn't find it and would
+        // default to U+00A4 CURRENCY SIGN (¤) rather than €.
+        Locale pt_BR = new Locale("pt", "BR");
+        Locale pt_PT = new Locale("pt", "PT");
+        assertEquals("R$", Currency.getInstance(pt_BR).getSymbol(pt_BR));
+        assertEquals("R$", Currency.getInstance(pt_BR).getSymbol(pt_PT));
+        assertEquals("€", Currency.getInstance(pt_PT).getSymbol(pt_BR));
+        assertEquals("€", Currency.getInstance(pt_PT).getSymbol(pt_PT));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/EvilMapTest.java b/luni/src/test/java/libcore/java/util/EvilMapTest.java
new file mode 100644
index 0000000..2c5f913
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/EvilMapTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.java.util;
+
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EvilMapTest extends junit.framework.TestCase {
+  public static class EvilMap<K,V> extends AbstractMap<K,V> {
+    private final Set<Entry<K,V>> entries = new HashSet<Entry<K,V>>();
+
+    public EvilMap() {
+      entries.add(new AbstractMap.SimpleEntry("hi", "there"));
+    }
+
+    // Claim we're empty...
+    @Override public int size() { return 0; }
+    // ...but potentially return many entries.
+    @Override public Set<Entry<K, V>> entrySet() { return entries; }
+
+    // Dummy implementation, not relevant for this test but
+    // necessary to implement AbstractMap.
+    @Override public V put(K key, V val) { return val; }
+  }
+
+  // https://code.google.com/p/android/issues/detail?id=48055
+  public void test_48055_HashMap() throws Exception {
+    Map<String, String> evil = new EvilMap<String, String>();
+    evil.put("hi", "there");
+    // Corrupt one HashMap...
+    HashMap<String, String> map = new HashMap<String, String>(evil);
+    // ...and now they're all corrupted.
+    HashMap<String, String> map2 = new HashMap<String, String>();
+    assertNull(map2.get("hi"));
+  }
+
+  public void test_48055_Hashtable() throws Exception {
+    Map<String, String> evil = new EvilMap<String, String>();
+    evil.put("hi", "there");
+    // Corrupt one Hashtable...
+    Hashtable<String, String> map = new Hashtable<String, String>(evil);
+    // ...and now they're all corrupted.
+    Hashtable<String, String> map2 = new Hashtable<String, String>();
+    assertNull(map2.get("hi"));
+  }
+
+  public void test_48055_LinkedHashMap() throws Exception {
+    Map<String, String> evil = new EvilMap<String, String>();
+    evil.put("hi", "there");
+    // Corrupt one LinkedHashMap...
+    LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(evil);
+    // ...and now they're all corrupted.
+    LinkedHashMap<String, String> map2 = new LinkedHashMap<String, String>();
+    assertNull(map2.get("hi"));
+  }
+
+  public void test_48055_WeakHashMap() throws Exception {
+    Map<String, String> evil = new EvilMap<String, String>();
+    evil.put("hi", "there");
+    // Corrupt one WeakHashMap...
+    WeakHashMap<String, String> map = new WeakHashMap<String, String>(evil);
+    // ...and now they're all corrupted.
+    WeakHashMap<String, String> map2 = new WeakHashMap<String, String>();
+    assertNull(map2.get("hi"));
+  }
+
+  public void test_48055_IdentityHashMap() throws Exception {
+    Map<String, String> evil = new EvilMap<String, String>();
+    evil.put("hi", "there");
+    // Corrupt one IdentityHashMap...
+    IdentityHashMap<String, String> map = new IdentityHashMap<String, String>(evil);
+    // ...and now they're all corrupted.
+    IdentityHashMap<String, String> map2 = new IdentityHashMap<String, String>();
+    assertNull(map2.get("hi"));
+  }
+
+  public void test_48055_ConcurrentHashMap() throws Exception {
+    Map<String, String> evil = new EvilMap<String, String>();
+    evil.put("hi", "there");
+    // Corrupt one ConcurrentHashMap...
+    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>(evil);
+    // ...and now they're all corrupted.
+    ConcurrentHashMap<String, String> map2 = new ConcurrentHashMap<String, String>();
+    assertNull(map2.get("hi"));
+  }
+
+
+}
diff --git a/luni/src/test/java/libcore/java/util/FormatterTest.java b/luni/src/test/java/libcore/java/util/FormatterTest.java
index 5c59807..596e946 100644
--- a/luni/src/test/java/libcore/java/util/FormatterTest.java
+++ b/luni/src/test/java/libcore/java/util/FormatterTest.java
@@ -130,4 +130,9 @@
                     output[i], result);
         }
     }
+
+    // https://code.google.com/p/android/issues/detail?id=42936
+    public void test42936() throws Exception {
+        assertEquals("0.00000000000000", String.format("%.15g",0.0d));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java
index b146b1a..b0522a3 100644
--- a/luni/src/test/java/libcore/java/util/LocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/LocaleTest.java
@@ -28,15 +28,33 @@
 
 public class LocaleTest extends junit.framework.TestCase {
     // http://b/2611311; if there's no display language/country/variant, use the raw codes.
-    public void test_getDisplayName_raw() throws Exception {
-        Locale weird = new Locale("AaBbCc", "DdEeFf", "GgHhIi");
-        assertEquals("aabbcc", weird.getLanguage());
-        assertEquals("", weird.getDisplayLanguage());
-        assertEquals("DDEEFF", weird.getCountry());
-        assertEquals("", weird.getDisplayCountry());
-        assertEquals("GgHhIi", weird.getVariant());
-        assertEquals("", weird.getDisplayVariant());
-        assertEquals("aabbcc (DDEEFF,GgHhIi)", weird.getDisplayName());
+    public void test_getDisplayName_invalid() throws Exception {
+        Locale invalid = new Locale("AaBbCc", "DdEeFf", "GgHhIi");
+
+        assertEquals("aabbcc", invalid.getLanguage());
+        assertEquals("DDEEFF", invalid.getCountry());
+        assertEquals("GgHhIi", invalid.getVariant());
+
+        // Android using icu4c < 49.2 returned empty strings for display language, country,
+        // and variant, but a display name made up of the raw strings.
+        // Newer releases return slightly different results, but no less unreasonable.
+        assertEquals("aabbcc", invalid.getDisplayLanguage());
+        assertEquals("", invalid.getDisplayCountry());
+        assertEquals("DDEEFF_GGHHII", invalid.getDisplayVariant());
+        assertEquals("aabbcc (DDEEFF,DDEEFF_GGHHII)", invalid.getDisplayName());
+    }
+
+    // http://b/2611311; if there's no display language/country/variant, use the raw codes.
+    public void test_getDisplayName_unknown() throws Exception {
+        Locale unknown = new Locale("xx", "YY", "Traditional");
+        assertEquals("xx", unknown.getLanguage());
+        assertEquals("YY", unknown.getCountry());
+        assertEquals("Traditional", unknown.getVariant());
+
+        assertEquals("xx", unknown.getDisplayLanguage());
+        assertEquals("YY", unknown.getDisplayCountry());
+        assertEquals("TRADITIONAL", unknown.getDisplayVariant());
+        assertEquals("xx (YY,TRADITIONAL)", unknown.getDisplayName());
     }
 
     public void test_getDisplayName_easy() throws Exception {
@@ -46,15 +64,22 @@
         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 {
+        // In jb-mr1, we had a last-minute hack to always return "Filipino" because
+        // icu4c 4.8 didn't have any localizations for fil. (http://b/7291355)
         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));
-    }
+
+        // After the icu4c 4.9 upgrade, we could localize "fil" correctly, though we
+        // needed another hack to supply "fil" instead of "tl" to icu4c. (http://b/8023288)
+        Locale es_MX = new Locale("es", "MX");
+        assertEquals("filipino", tl.getDisplayLanguage(es_MX));
+        assertEquals("filipino", tl_PH.getDisplayLanguage(es_MX));
+      }
 
     // http://b/3452611; Locale.getDisplayLanguage fails for the obsolete language codes.
     public void test_getDisplayName_obsolete() throws Exception {
diff --git a/luni/src/test/java/libcore/java/util/OldAndroidLocaleTest.java b/luni/src/test/java/libcore/java/util/OldAndroidLocaleTest.java
index 8fbe5ff..ad9009a 100644
--- a/luni/src/test/java/libcore/java/util/OldAndroidLocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/OldAndroidLocaleTest.java
@@ -61,14 +61,14 @@
         assertEquals("Sunday", engSymbols.getWeekdays()[Calendar.SUNDAY]);
         assertEquals("Sonntag", deuSymbols.getWeekdays()[Calendar.SUNDAY]);
 
-        assertEquals("Central European Time",
+        assertEquals("Central European Standard Time",
                 berlin.getDisplayName(false, TimeZone.LONG, eng));
         assertEquals("Central European Summer Time",
                 berlin.getDisplayName(true, TimeZone.LONG, eng));
 
-        assertEquals("Mitteleurop\u00E4ische Zeit",
+        assertEquals("Mitteleuropäische Normalzeit",
                 berlin.getDisplayName(false, TimeZone.LONG, deu));
-        assertEquals("Mitteleurop\u00E4ische Sommerzeit",
+        assertEquals("Mitteleuropäische Sommerzeit",
                 berlin.getDisplayName(true, TimeZone.LONG, deu));
 
         assertTrue(engSymbols.getZoneStrings().length > 100);
diff --git a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java
index 4ed89de..03a49ca 100644
--- a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java
@@ -116,9 +116,9 @@
             assertEquals("Pacific Standard Time", tz.getDisplayName(false, 1, Locale.UK));
         }
         if (Support_Locale.isLocaleAvailable(Locale.FRANCE)) {
-            //RI fails on following line. RI always returns short time zone name as "PST"
-            assertEquals("UTC-08:00",             tz.getDisplayName(false, 0, Locale.FRANCE));
-            // BEGIN android-note: RI has "Heure", CLDR/ICU has "heure".
+            // RI always returns short time zone name as "PST"
+            // ICU zone/root.txt patched to allow metazone names.
+            assertEquals("PST",             tz.getDisplayName(false, 0, Locale.FRANCE));
             assertEquals("heure avanc\u00e9e du Pacifique", tz.getDisplayName(true,  1, Locale.FRANCE));
             assertEquals("heure normale du Pacifique", tz.getDisplayName(false, 1, Locale.FRANCE));
         }
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 6e8b8d8..08d1e69 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -211,4 +211,111 @@
             }
         };
     }
+
+    // http://b/7955614 and http://b/8026776.
+    public void testDisplayNames() throws Exception {
+        // Check that there are no time zones that use DST but have the same display name for
+        // both standard and daylight time.
+        StringBuilder failures = new StringBuilder();
+        for (String id : TimeZone.getAvailableIDs()) {
+            TimeZone tz = TimeZone.getTimeZone(id);
+            String longDst = tz.getDisplayName(true, TimeZone.LONG, Locale.US);
+            String longStd = tz.getDisplayName(false, TimeZone.LONG, Locale.US);
+            String shortDst = tz.getDisplayName(true, TimeZone.SHORT, Locale.US);
+            String shortStd = tz.getDisplayName(false, TimeZone.SHORT, Locale.US);
+
+            if (tz.useDaylightTime()) {
+                // The long std and dst strings must differ!
+                if (longDst.equals(longStd)) {
+                    failures.append(String.format("\n%20s: LD='%s' LS='%s'!",
+                                                  id, longDst, longStd));
+                }
+                // The short std and dst strings must differ!
+                if (shortDst.equals(shortStd)) {
+                    failures.append(String.format("\n%20s: SD='%s' SS='%s'!",
+                                                  id, shortDst, shortStd));
+                }
+
+                // If the short std matches the long dst, or the long std matches the short dst,
+                // it probably means we have a time zone that icu4c doesn't believe has ever
+                // observed dst.
+                if (shortStd.equals(longDst)) {
+                    failures.append(String.format("\n%20s: SS='%s' LD='%s'!",
+                                                  id, shortStd, longDst));
+                }
+                if (longStd.equals(shortDst)) {
+                    failures.append(String.format("\n%20s: LS='%s' SD='%s'!",
+                                                  id, longStd, shortDst));
+                }
+
+                // The long and short dst strings must differ!
+                if (longDst.equals(shortDst) && !longDst.startsWith("GMT")) {
+                  failures.append(String.format("\n%20s: LD='%s' SD='%s'!",
+                                                id, longDst, shortDst));
+                }
+            }
+
+            // Sanity check that whenever a display name is just a GMT string that it's the
+            // right GMT string.
+            String gmtDst = formatGmtString(tz, true);
+            String gmtStd = formatGmtString(tz, false);
+            if (isGmtString(longDst) && !longDst.equals(gmtDst)) {
+                failures.append(String.format("\n%s: LD %s", id, longDst));
+            }
+            if (isGmtString(longStd) && !longStd.equals(gmtStd)) {
+                failures.append(String.format("\n%s: LS %s", id, longStd));
+            }
+            if (isGmtString(shortDst) && !shortDst.equals(gmtDst)) {
+                failures.append(String.format("\n%s: SD %s", id, shortDst));
+            }
+            if (isGmtString(shortStd) && !shortStd.equals(gmtStd)) {
+                failures.append(String.format("\n%s: SS %s", id, shortStd));
+            }
+        }
+        assertEquals("", failures.toString());
+    }
+
+    public void testSantiago() throws Exception {
+        TimeZone tz = TimeZone.getTimeZone("America/Santiago");
+        assertEquals("Chile Summer Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US));
+        assertEquals("Chile Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US));
+        assertEquals("GMT-03:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US));
+        assertEquals("GMT-04:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US));
+    }
+
+    // http://b/7955614
+    public void testApia() throws Exception {
+        TimeZone tz = TimeZone.getTimeZone("Pacific/Apia");
+        assertEquals("Samoa Daylight Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US));
+        assertEquals("Samoa Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US));
+        assertEquals("GMT+14:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US));
+        assertEquals("GMT+13:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US));
+    }
+
+    private static boolean isGmtString(String s) {
+        return s.startsWith("GMT+") || s.startsWith("GMT-");
+    }
+
+    private static String formatGmtString(TimeZone tz, boolean daylight) {
+        int offset = tz.getRawOffset();
+        if (daylight) {
+            offset += tz.getDSTSavings();
+        }
+        offset /= 60000;
+        char sign = '+';
+        if (offset < 0) {
+            sign = '-';
+            offset = -offset;
+        }
+        return String.format("GMT%c%02d:%02d", sign, offset / 60, offset % 60);
+    }
+
+    public void testAllDisplayNames() throws Exception {
+      for (Locale locale : Locale.getAvailableLocales()) {
+        for (String id : TimeZone.getAvailableIDs()) {
+          TimeZone tz = TimeZone.getTimeZone(id);
+          assertNotNull(tz.getDisplayName(false, TimeZone.LONG, locale));
+        }
+      }
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/jar/OldManifestTest.java b/luni/src/test/java/libcore/java/util/jar/OldManifestTest.java
index 9552aef..e5c3bdb 100644
--- a/luni/src/test/java/libcore/java/util/jar/OldManifestTest.java
+++ b/luni/src/test/java/libcore/java/util/jar/OldManifestTest.java
@@ -141,4 +141,44 @@
         assertTrue(manifest1.equals(manifest2));
     }
 
+    public void test_write_no_version() throws Exception {
+        // If you write a manifest with no MANIFEST_VERSION, your attributes don't get written out.
+        assertEquals(null, doRoundTrip(null));
+        // But they do if you supply a MANIFEST_VERSION.
+        assertEquals("image/pr0n", doRoundTrip(Attributes.Name.MANIFEST_VERSION));
+        assertEquals("image/pr0n", doRoundTrip("Signature-Version"));
+        assertEquals(null, doRoundTrip("Random-String-Version"));
+    }
+
+    private String doRoundTrip(Object versionName) throws Exception {
+        Manifest m1 = new Manifest();
+        m1.getMainAttributes().put(Attributes.Name.CONTENT_TYPE, "image/pr0n");
+        if (versionName != null) {
+            m1.getMainAttributes().putValue(versionName.toString(), "1.2.3");
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        m1.write(os);
+
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+        Manifest m2 = new Manifest();
+        m2.read(is);
+        return (String) m2.getMainAttributes().get(Attributes.Name.CONTENT_TYPE);
+    }
+
+    public void test_write_two_versions() throws Exception {
+        // It's okay to have two versions.
+        Manifest m1 = new Manifest();
+        m1.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+        m1.getMainAttributes().put(Attributes.Name.SIGNATURE_VERSION, "2.0");
+        m1.getMainAttributes().putValue("Aardvark-Version", "3.0");
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        m1.write(os);
+
+        // The Manifest-Version takes precedence,
+        // and the Signature-Version gets no special treatment.
+        String[] lines = new String(os.toByteArray(), "UTF-8").split("\r\n");
+        assertEquals("Manifest-Version: 1.0", lines[0]);
+        assertEquals("Aardvark-Version: 3.0", lines[1]);
+        assertEquals("Signature-Version: 2.0", lines[2]);
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/regex/OldMatcherTest.java b/luni/src/test/java/libcore/java/util/regex/OldMatcherTest.java
index 07d99e9..8d6e186 100644
--- a/luni/src/test/java/libcore/java/util/regex/OldMatcherTest.java
+++ b/luni/src/test/java/libcore/java/util/regex/OldMatcherTest.java
@@ -17,6 +17,7 @@
 
 package libcore.java.util.regex;
 
+import java.util.ArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import junit.framework.TestCase;
@@ -606,4 +607,48 @@
         assertTrue("\u00ea".matches("\\w")); // LATIN SMALL LETTER E WITH CIRCUMFLEX
         assertFalse("\u00ea".matches("\\W")); // LATIN SMALL LETTER E WITH CIRCUMFLEX
     }
+
+    // http://code.google.com/p/android/issues/detail?id=41143
+    public void testConcurrentMatcherAccess() throws Exception {
+        final Pattern p = Pattern.compile("(^|\\W)([a-z])");
+        final Matcher m = p.matcher("");
+
+        ArrayList<Thread> threads = new ArrayList<Thread>();
+        for (int i = 0; i < 10; ++i) {
+            Thread t = new Thread(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < 4096; ++i) {
+                        String s = "some example text";
+                        m.reset(s);
+                        try {
+                            StringBuffer sb = new StringBuffer(s.length());
+                            while (m.find()) {
+                                m.appendReplacement(sb, m.group(1) + m.group(2));
+                            }
+                            m.appendTail(sb);
+                        } catch (Exception expected) {
+                            // This code is inherently unsafe and crazy;
+                            // we're just trying to provoke native crashes!
+                        }
+                    }
+                }
+            });
+            threads.add(t);
+        }
+
+        for (Thread t : threads) {
+            t.start();
+        }
+        for (Thread t : threads) {
+            t.join();
+        }
+    }
+
+    // https://code.google.com/p/android/issues/detail?id=33040
+    public void test33040() throws Exception {
+        Pattern p = Pattern.compile("ma");
+        // replaceFirst resets the region; apparently, this was broken in Android 1.6.
+        String result = p.matcher("mama").region(2, 4).replaceFirst("mi");
+        assertEquals("mima", result);
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java
index 020d8d9..45bd860 100644
--- a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java
+++ b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java
@@ -128,6 +128,19 @@
         in.close();
     }
 
+    public void testZipFileWithLotsOfEntries() throws IOException {
+        int expectedEntryCount = 64*1024 - 1;
+        File f = createZipFile(expectedEntryCount, 0);
+        ZipFile zipFile = new ZipFile(f);
+        int entryCount = 0;
+        for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
+            ZipEntry zipEntry = e.nextElement();
+            ++entryCount;
+        }
+        assertEquals(expectedEntryCount, entryCount);
+        zipFile.close();
+    }
+
     // http://code.google.com/p/android/issues/detail?id=36187
     public void testZipFileLargerThan2GiB() throws IOException {
         if (false) { // TODO: this test requires too much time and too much disk space!
@@ -303,6 +316,85 @@
         return sb.toString();
     }
 
+    public void testComment() throws Exception {
+        String expectedFileComment = "1 \u0666 2";
+        String expectedEntryComment = "a \u0666 b";
+
+        File file = createTemporaryZipFile();
+        ZipOutputStream out = createZipOutputStream(file);
+
+        // Is file comment length checking done on bytes or characters? (Should be bytes.)
+        out.setComment(null);
+        out.setComment(makeString(0xffff, "a"));
+        try {
+            out.setComment(makeString(0xffff + 1, "a"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            out.setComment(makeString(0xffff, "\u0666"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        ZipEntry ze = new ZipEntry("a");
+
+        // Is entry comment length checking done on bytes or characters? (Should be bytes.)
+        ze.setComment(null);
+        ze.setComment(makeString(0xffff, "a"));
+        try {
+            ze.setComment(makeString(0xffff + 1, "a"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            ze.setComment(makeString(0xffff, "\u0666"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        ze.setComment(expectedEntryComment);
+        out.putNextEntry(ze);
+        out.closeEntry();
+
+        out.setComment(expectedFileComment);
+        out.close();
+
+        ZipFile zipFile = new ZipFile(file);
+        // TODO: there's currently no API for reading the file comment --- strings(1) the file?
+        assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment());
+        zipFile.close();
+    }
+
+    public void testNameLengthChecks() throws IOException {
+        // Is entry name length checking done on bytes or characters?
+        // Really it should be bytes, but the RI only checks characters at construction time.
+        // Android does the same, because it's cheap...
+        try {
+            new ZipEntry((String) null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+        new ZipEntry(makeString(0xffff, "a"));
+        try {
+            new ZipEntry(makeString(0xffff + 1, "a"));
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // ...but Android won't let you create a zip file with a truncated name.
+        ZipOutputStream out = createZipOutputStream(createTemporaryZipFile());
+        ZipEntry ze = new ZipEntry(makeString(0xffff, "\u0666"));
+        try {
+            out.putNextEntry(ze);
+            fail(); // The RI fails this test; it just checks the character count at construction time.
+        } catch (IllegalArgumentException expected) {
+        }
+        out.closeEntry();
+        out.putNextEntry(new ZipEntry("okay")); // ZipOutputStream.close throws if you add nothing!
+        out.close();
+    }
+
     // https://code.google.com/p/android/issues/detail?id=58465
     public void test_NUL_in_filename() throws Exception {
         File file = createTemporaryZipFile();
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index 68a46f3..9f19eef 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -58,6 +58,7 @@
 import junit.framework.TestCase;
 import libcore.java.security.StandardNames;
 import libcore.java.security.TestKeyStore;
+import libcore.util.EmptyArray;
 
 public final class CipherTest extends TestCase {
 
@@ -67,7 +68,7 @@
 
     private static final String[] AES_PROVIDERS = ((StandardNames.IS_RI)
                                                    ? new String[] { "SunJCE" }
-                                                   : new String[] { "BC" }); // TOOD: , "AndroidOpenSSL"
+                                                   : new String[] { "BC", "AndroidOpenSSL" });
 
     private static final boolean IS_UNLIMITED;
     static {
@@ -118,14 +119,14 @@
     }
 
     private synchronized static int getEncryptMode(String algorithm) throws Exception {
-        if (isWrap(algorithm)) {
+        if (isOnlyWrappingAlgorithm(algorithm)) {
             return Cipher.WRAP_MODE;
         }
         return Cipher.ENCRYPT_MODE;
     }
 
     private synchronized static int getDecryptMode(String algorithm) throws Exception {
-        if (isWrap(algorithm)) {
+        if (isOnlyWrappingAlgorithm(algorithm)) {
             return Cipher.UNWRAP_MODE;
         }
         return Cipher.DECRYPT_MODE;
@@ -205,7 +206,7 @@
         return getBaseAlgorithm(algorithm).equals("RSA");
     }
 
-    private static boolean isWrap(String algorithm) {
+    private static boolean isOnlyWrappingAlgorithm(String algorithm) {
         return algorithm.endsWith("WRAP");
     }
 
@@ -229,9 +230,6 @@
             key = skf.generateSecret(new PBEKeySpec("secret".toCharArray()));
         } else {
             KeyGenerator kg = KeyGenerator.getInstance(getBaseAlgorithm(algorithm));
-            if (StandardNames.IS_RI && algorithm.equals("AES")) {
-                kg.init(128);
-            }
             key = kg.generateKey();
         }
         ENCRYPT_KEYS.put(algorithm, key);
@@ -685,12 +683,16 @@
 
         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));
+        assertEquals(cipherID + " getBlockSize()",
+                     getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize());
+        assertEquals(cipherID + " getOutputSize(0)",
+                     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));
+        assertEquals(cipherID + " getBlockSize()",
+                     getExpectedBlockSize(algorithm, decryptMode, providerName), c.getBlockSize());
+        assertEquals(cipherID + " getOutputSize(0)",
+                     getExpectedOutputSize(algorithm, decryptMode, providerName), c.getOutputSize(0));
 
         // TODO: test Cipher.getIV()
 
@@ -698,24 +700,36 @@
 
         assertNull(cipherID, c.getExemptionMechanism());
 
-        c.init(getEncryptMode(algorithm), encryptKey, spec);
-        if (isWrap(algorithm)) {
-            byte[] cipherText = c.wrap(encryptKey);
-            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()
-                         + " decryptedKey.getAlgorithm()=" + decryptedKey.getAlgorithm()
-                         + " encryptKey.getEncoded()=" + Arrays.toString(encryptKey.getEncoded())
-                         + " decryptedKey.getEncoded()=" + Arrays.toString(decryptedKey.getEncoded()),
-                         encryptKey, decryptedKey);
-        } else {
+        // Test wrapping a key.  Every cipher should be able to wrap.
+        {
+            // Generate a small SecretKey for AES.
+            KeyGenerator kg = KeyGenerator.getInstance("AES");
+            kg.init(128);
+            SecretKey sk = kg.generateKey();
+
+            // Wrap it
+            c.init(Cipher.WRAP_MODE, encryptKey, spec);
+            byte[] cipherText = c.wrap(sk);
+
+            // Unwrap it
+            c.init(Cipher.UNWRAP_MODE, getDecryptKey(algorithm), spec);
+            Key decryptedKey = c.unwrap(cipherText, sk.getAlgorithm(), Cipher.SECRET_KEY);
+
+            assertEquals(cipherID
+                    + " sk.getAlgorithm()=" + sk.getAlgorithm()
+                    + " decryptedKey.getAlgorithm()=" + decryptedKey.getAlgorithm()
+                    + " encryptKey.getEncoded()=" + Arrays.toString(sk.getEncoded())
+                    + " decryptedKey.getEncoded()=" + Arrays.toString(decryptedKey.getEncoded()),
+                    sk, decryptedKey);
+        }
+
+        if (!isOnlyWrappingAlgorithm(algorithm)) {
+            c.init(Cipher.ENCRYPT_MODE, encryptKey, spec);
             byte[] cipherText = c.doFinal(ORIGINAL_PLAIN_TEXT);
-            c.init(getDecryptMode(algorithm), getDecryptKey(algorithm), spec);
+            c.init(Cipher.DECRYPT_MODE, getDecryptKey(algorithm), spec);
             byte[] decryptedPlainText = c.doFinal(cipherText);
-            assertEquals(cipherID,
-                         Arrays.toString(getExpectedPlainText(algorithm)),
-                         Arrays.toString(decryptedPlainText));
+            assertEquals(cipherID, Arrays.toString(getExpectedPlainText(algorithm)),
+                    Arrays.toString(decryptedPlainText));
         }
     }
 
@@ -1989,7 +2003,9 @@
                 checkCipher(p, provider);
             } catch (Exception e) {
                 out.append("Error encountered checking " + p.mode + ", keySize="
-                        + (p.key.length * 8) + "\n");
+                        + (p.key.length * 8)
+                        + " with provider " + provider + "\n");
+
                 e.printStackTrace(out);
             }
         }
@@ -2009,18 +2025,104 @@
         c.init(Cipher.ENCRYPT_MODE, key, spec);
 
         final byte[] actualCiphertext = c.doFinal(p.plaintext);
-        assertTrue(Arrays.equals(p.ciphertext, actualCiphertext));
+        assertEquals(Arrays.toString(p.ciphertext), Arrays.toString(actualCiphertext));
+
+        byte[] emptyCipherText = c.doFinal();
+        assertNotNull(emptyCipherText);
 
         c.init(Cipher.DECRYPT_MODE, key, spec);
 
-        final byte[] actualPlaintext = c.doFinal(p.ciphertext);
-        assertTrue(Arrays.equals(p.plaintext, actualPlaintext));
+        byte[] emptyPlainText = c.doFinal(emptyCipherText);
+        assertEquals(Arrays.toString(EmptyArray.BYTE), Arrays.toString(emptyPlainText));
+
+        // empty decrypt
+        {
+            if (StandardNames.IS_RI) {
+                assertEquals(Arrays.toString(EmptyArray.BYTE),
+                             Arrays.toString(c.doFinal()));
+
+                c.update(EmptyArray.BYTE);
+                assertEquals(Arrays.toString(EmptyArray.BYTE),
+                             Arrays.toString(c.doFinal()));
+            } else if (provider.equals("BC")) {
+                try {
+                    c.doFinal();
+                    fail();
+                } catch (IllegalBlockSizeException expected) {
+                }
+                try {
+                    c.update(EmptyArray.BYTE);
+                    c.doFinal();
+                    fail();
+                } catch (IllegalBlockSizeException expected) {
+                }
+            } else if (provider.equals("AndroidOpenSSL")) {
+                assertNull(c.doFinal());
+
+                c.update(EmptyArray.BYTE);
+                assertNull(c.doFinal());
+            } else {
+                throw new AssertionError("Define your behavior here for " + provider);
+            }
+        }
+
+        // .doFinal(input)
+        {
+            final byte[] actualPlaintext = c.doFinal(p.ciphertext);
+            assertEquals(Arrays.toString(p.plaintext), Arrays.toString(actualPlaintext));
+        }
+
+        // .doFinal(input, offset, len, output)
+        {
+            final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 5];
+            System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);
+
+            final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length)];
+            assertEquals(p.plaintext.length,
+                    c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext));
+            assertEquals(Arrays.toString(p.plaintext),
+                    Arrays.toString(Arrays.copyOfRange(actualPlaintext, 0, p.plaintext.length)));
+        }
+
+        // .doFinal(input, offset, len, output, offset)
+        {
+            final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 10];
+            System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);
+
+            final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length) + 2];
+            assertEquals(p.plaintext.length,
+                    c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext, 1));
+            assertEquals(Arrays.toString(p.plaintext),
+                    Arrays.toString(Arrays.copyOfRange(actualPlaintext, 1, p.plaintext.length + 1)));
+        }
 
         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));
+        assertEquals(Arrays.toString(p.plaintextPadded), Arrays.toString(actualPlaintextPadded));
+
+        // Test wrapping a key. Every cipher should be able to wrap.
+        {
+            // Generate a small SecretKey for AES.
+            KeyGenerator kg = KeyGenerator.getInstance("AES");
+            kg.init(128);
+            SecretKey sk = kg.generateKey();
+
+            // Wrap it
+            c.init(Cipher.WRAP_MODE, key, spec);
+            byte[] cipherText = c.wrap(sk);
+
+            // Unwrap it
+            c.init(Cipher.UNWRAP_MODE, key, spec);
+            Key decryptedKey = c.unwrap(cipherText, sk.getAlgorithm(), Cipher.SECRET_KEY);
+
+            assertEquals(
+                    "sk.getAlgorithm()=" + sk.getAlgorithm() + " decryptedKey.getAlgorithm()="
+                            + decryptedKey.getAlgorithm() + " encryptKey.getEncoded()="
+                            + Arrays.toString(sk.getEncoded()) + " decryptedKey.getEncoded()="
+                            + Arrays.toString(decryptedKey.getEncoded()), sk, decryptedKey);
+        }
     }
 
     public void testCipher_ShortBlock_Failure() throws Exception {
@@ -2037,7 +2139,8 @@
                 checkCipher_ShortBlock_Failure(p, provider);
             } catch (Exception e) {
                 out.append("Error encountered checking " + p.mode + ", keySize="
-                        + (p.key.length * 8) + "\n");
+                        + (p.key.length * 8)
+                        + " with provider " + provider + "\n");
                 e.printStackTrace(out);
             }
         }
@@ -2165,4 +2268,70 @@
         } catch (InvalidAlgorithmParameterException expected) {
         }
     }
+
+    public void testRC4_MultipleKeySizes() throws Exception {
+        final int SMALLEST_KEY_SIZE = 40;
+        final int LARGEST_KEY_SIZE = 1024;
+
+        /* Make an array of keys for our tests */
+        SecretKey[] keys = new SecretKey[LARGEST_KEY_SIZE - SMALLEST_KEY_SIZE];
+        {
+            KeyGenerator kg = KeyGenerator.getInstance("ARC4");
+            for (int keysize = SMALLEST_KEY_SIZE; keysize < LARGEST_KEY_SIZE; keysize++) {
+                final int index = keysize - SMALLEST_KEY_SIZE;
+                kg.init(keysize);
+                keys[index] = kg.generateKey();
+            }
+        }
+
+        /*
+         * Use this to compare the output of the first provider against
+         * subsequent providers.
+         */
+        String[] expected = new String[LARGEST_KEY_SIZE - SMALLEST_KEY_SIZE];
+
+        /* Find all providers that provide ARC4. We must have at least one! */
+        Map<String, String> filter = new HashMap<String, String>();
+        filter.put("Cipher.ARC4", "");
+        Provider[] providers = Security.getProviders(filter);
+        assertTrue("There must be security providers of Cipher.ARC4", providers.length > 0);
+
+        /* Keep track of this for later error messages */
+        String firstProvider = providers[0].getName();
+
+        for (Provider p : providers) {
+            Cipher c = Cipher.getInstance("ARC4", p);
+
+            for (int keysize = SMALLEST_KEY_SIZE; keysize < LARGEST_KEY_SIZE; keysize++) {
+                final int index = keysize - SMALLEST_KEY_SIZE;
+                final SecretKey sk = keys[index];
+
+                /*
+                 * Test that encryption works. Donig this in a loop also has the
+                 * benefit of testing that re-initialization works for this
+                 * cipher.
+                 */
+                c.init(Cipher.ENCRYPT_MODE, sk);
+                byte[] cipherText = c.doFinal(ORIGINAL_PLAIN_TEXT);
+                assertNotNull(cipherText);
+
+                /*
+                 * Compare providers against eachother to make sure they're all
+                 * in agreement. This helps when you add a brand new provider.
+                 */
+                if (expected[index] == null) {
+                    expected[index] = Arrays.toString(cipherText);
+                } else {
+                    assertEquals(firstProvider + " should output the same as " + p.getName()
+                            + " for key size " + keysize, expected[index],
+                            Arrays.toString(cipherText));
+                }
+
+                c.init(Cipher.DECRYPT_MODE, sk);
+                byte[] actualPlaintext = c.doFinal(cipherText);
+                assertEquals("Key size: " + keysize, Arrays.toString(ORIGINAL_PLAIN_TEXT),
+                        Arrays.toString(actualPlaintext));
+            }
+        }
+    }
 }
diff --git a/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java b/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java
new file mode 100644
index 0000000..cabe5c9
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2013 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.javax.crypto;
+
+import static libcore.java.security.SignatureTest.hexToBytes;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+
+/**
+ * Tests for all registered Elliptic Curve Diffie-Hellman {@link KeyAgreement} providers.
+ */
+public class ECDHKeyAgreementTest extends TestCase {
+    // Two key pairs and the resulting shared secret for the Known Answer Test
+    private static final byte[] KAT_PUBLIC_KEY1_X509 = hexToBytes(
+            "3059301306072a8648ce3d020106082a8648ce3d030107034200049fc2f71f85446b1371244491d83"
+            + "9cf97b5d27cedbb04d2c0058b59709df3a216e6b4ca1b2d622588c5a0e6968144a8965e816a600c"
+            + "05305a1da3df2bf02b41d1");
+    private static final byte[] KAT_PRIVATE_KEY1_PKCS8 = hexToBytes(
+            "308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420e1e683003"
+            + "c8b963a92742e5f955ce7fddc81d0c3ae9b149d6af86a0cacb2271ca00a06082a8648ce3d030107"
+            + "a144034200049fc2f71f85446b1371244491d839cf97b5d27cedbb04d2c0058b59709df3a216e6b"
+            + "4ca1b2d622588c5a0e6968144a8965e816a600c05305a1da3df2bf02b41d1");
+
+    private static final byte[] KAT_PUBLIC_KEY2_X509 = hexToBytes(
+            "3059301306072a8648ce3d020106082a8648ce3d03010703420004358efb6d91e5bbcae21774af3f6"
+            + "d85d0848630e7e61dbeb5ac9e47036ed0f8d38c7a1d1bb249f92861c7c9153fff33f45ab5b171eb"
+            + "e8cad741125e6bb4fc6b07");
+    private static final byte[] KAT_PRIVATE_KEY2_PKCS8 = hexToBytes(
+            "308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104202b1810a69"
+            + "e12b74d50bf0343168f705f0104f76299855268aa526fdb31e6eec0a00a06082a8648ce3d030107"
+            + "a14403420004358efb6d91e5bbcae21774af3f6d85d0848630e7e61dbeb5ac9e47036ed0f8d38c7"
+            + "a1d1bb249f92861c7c9153fff33f45ab5b171ebe8cad741125e6bb4fc6b07");
+
+    private static final byte[] KAT_SECRET =
+            hexToBytes("4faa0594c0e773eb26c8df2163af2443e88aab9578b9e1f324bc61e42d222783");
+
+    private static final ECPublicKey KAT_PUBLIC_KEY1;
+    private static final ECPrivateKey KAT_PRIVATE_KEY1;
+    private static final ECPublicKey KAT_PUBLIC_KEY2;
+    private static final ECPrivateKey KAT_PRIVATE_KEY2;
+    static {
+        try {
+            KAT_PUBLIC_KEY1 = getPublicKey(KAT_PUBLIC_KEY1_X509);
+            KAT_PRIVATE_KEY1 = getPrivateKey(KAT_PRIVATE_KEY1_PKCS8);
+            KAT_PUBLIC_KEY2 = getPublicKey(KAT_PUBLIC_KEY2_X509);
+            KAT_PRIVATE_KEY2 = getPrivateKey(KAT_PRIVATE_KEY2_PKCS8);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to decode KAT key pairs using default provider", e);
+        }
+    }
+
+    /**
+     * Performs a known-answer test of the shared secret for all permutations of {@code Providers}
+     * of: first key pair, second key pair, and the {@code KeyAgreement}. This is to check that
+     * the {@code KeyAgreement} instances work with keys of all registered providers.
+     */
+    public void testKnownAnswer() throws Exception {
+        for (Provider keyFactoryProvider1 : getKeyFactoryProviders()) {
+            ECPrivateKey privateKey1 = getPrivateKey(KAT_PRIVATE_KEY1_PKCS8, keyFactoryProvider1);
+            ECPublicKey publicKey1 = getPublicKey(KAT_PUBLIC_KEY1_X509, keyFactoryProvider1);
+            for (Provider keyFactoryProvider2 : getKeyFactoryProviders()) {
+                ECPrivateKey privateKey2 =
+                        getPrivateKey(KAT_PRIVATE_KEY2_PKCS8, keyFactoryProvider2);
+                ECPublicKey publicKey2 =
+                        getPublicKey(KAT_PUBLIC_KEY2_X509, keyFactoryProvider2);
+                for (Provider keyAgreementProvider : getKeyAgreementProviders()) {
+                    try {
+                        testKnownAnswer(publicKey1, privateKey1, publicKey2, privateKey2,
+                                keyAgreementProvider);
+                    } catch (Throwable e) {
+                        throw new RuntimeException(getClass().getSimpleName() + ".testKnownAnswer("
+                                + keyFactoryProvider1.getName()
+                                + ", " + keyFactoryProvider2.getName()
+                                + ", " + keyAgreementProvider.getName() + ")",
+                                e);
+                    }
+                }
+            }
+        }
+    }
+
+    void testKnownAnswer(
+            ECPublicKey publicKey1, ECPrivateKey privateKey1,
+            ECPublicKey publicKey2, ECPrivateKey privateKey2,
+            Provider keyAgreementProvider) throws Exception {
+        assertTrue(Arrays.equals(
+                KAT_SECRET, generateSecret(keyAgreementProvider, privateKey1, publicKey2)));
+        assertTrue(Arrays.equals(
+                KAT_SECRET, generateSecret(keyAgreementProvider, privateKey2, publicKey1)));
+    }
+
+    public void testGetAlgorithm() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGetAlgorithm(Provider provider) throws Exception {
+        assertEquals("ECDH", getKeyAgreement(provider).getAlgorithm());
+    }
+
+    public void testGetProvider() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGetProvider(Provider provider) throws Exception {
+        assertSame(provider, getKeyAgreement(provider).getProvider());
+    }
+
+    public void testInit_withNullPrivateKey() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testInit_withNullPrivateKey(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        try {
+            keyAgreement.init(null);
+            fail();
+        } catch (InvalidKeyException expected) {}
+    }
+
+    public void testInit_withUnsupportedPrivateKeyType() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testInit_withUnsupportedPrivateKeyType(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        try {
+            keyAgreement.init(KAT_PUBLIC_KEY1);
+            fail();
+        } catch (InvalidKeyException expected) {}
+    }
+
+    public void testInit_withUnsupportedAlgorithmParameterSpec() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testInit_withUnsupportedAlgorithmParameterSpec(Provider provider) throws Exception {
+        try {
+            getKeyAgreement(provider).init(KAT_PRIVATE_KEY1, new ECGenParameterSpec("prime256v1"));
+            fail();
+        } catch (InvalidAlgorithmParameterException expected) {}
+    }
+
+    public void testDoPhase_whenNotInitialized() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testDoPhase_whenNotInitialized(Provider provider) throws Exception {
+        try {
+            getKeyAgreement(provider).doPhase(KAT_PUBLIC_KEY1, true);
+            fail();
+        } catch (IllegalStateException expected) {}
+    }
+
+    public void testDoPhaseReturnsNull() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testDoPhaseReturnsNull(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        assertNull(keyAgreement.doPhase(KAT_PUBLIC_KEY2, true));
+    }
+
+    public void testDoPhase_withPhaseWhichIsNotLast() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testDoPhase_withPhaseWhichIsNotLast(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        try {
+            keyAgreement.doPhase(KAT_PUBLIC_KEY2, false);
+            fail();
+        } catch (IllegalStateException expected) {}
+    }
+
+    public void testDoPhase_withNullKey() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testDoPhase_withNullKey(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        try {
+            keyAgreement.doPhase(null, true);
+            fail();
+        } catch (InvalidKeyException expected) {}
+    }
+
+    public void testDoPhase_withInvalidKeyType() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testDoPhase_withInvalidKeyType(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        try {
+            keyAgreement.doPhase(KAT_PRIVATE_KEY1, true);
+            fail();
+        } catch (InvalidKeyException expected) {}
+    }
+
+    public void testGenerateSecret_withNullOutputBuffer() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGenerateSecret_withNullOutputBuffer(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+        try {
+            keyAgreement.generateSecret(null, 0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void testGenerateSecret_withBufferOfTheRightSize() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGenerateSecret_withBufferOfTheRightSize(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+
+        byte[] buffer = new byte[KAT_SECRET.length];
+        int secretLengthBytes = keyAgreement.generateSecret(buffer, 0);
+        assertEquals(KAT_SECRET.length, secretLengthBytes);
+        assertTrue(Arrays.equals(KAT_SECRET, buffer));
+    }
+
+    public void testGenerateSecret_withLargerThatNeededBuffer() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGenerateSecret_withLargerThatNeededBuffer(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+
+        // Place the shared secret in the middle of the larger buffer and check that only that
+        // part of the buffer is affected.
+        byte[] buffer = new byte[KAT_SECRET.length + 2];
+        buffer[0] = (byte) 0x85; // arbitrary canary value
+        buffer[buffer.length - 1] = (byte) 0x3b; // arbitrary canary value
+        int secretLengthBytes = keyAgreement.generateSecret(buffer, 1);
+        assertEquals(KAT_SECRET.length, secretLengthBytes);
+        assertEquals((byte) 0x85, buffer[0]);
+        assertEquals((byte) 0x3b, buffer[buffer.length - 1]);
+        byte[] secret = new byte[KAT_SECRET.length];
+        System.arraycopy(buffer, 1, secret, 0, secret.length);
+        assertTrue(Arrays.equals(KAT_SECRET, secret));
+    }
+
+    public void testGenerateSecret_withSmallerThanNeededBuffer() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGenerateSecret_withSmallerThanNeededBuffer(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY1);
+        keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+        try {
+            // Although the buffer is big enough (1024 bytes) the shared secret should be placed
+            // at offset 1020 thus leaving only 4 bytes for the secret, which is not enough.
+            keyAgreement.generateSecret(new byte[1024], 1020);
+            fail();
+        } catch (ShortBufferException expected) {}
+    }
+
+    public void testGenerateSecret_withoutBuffer() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGenerateSecret_withoutBuffer(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY2);
+        keyAgreement.doPhase(KAT_PUBLIC_KEY1, true);
+
+        byte[] secret = keyAgreement.generateSecret();
+        assertTrue(Arrays.equals(KAT_SECRET, secret));
+    }
+
+    public void testGenerateSecret_withAlgorithm() throws Exception {
+        invokeCallingMethodForEachKeyAgreementProvider();
+    }
+
+    void testGenerateSecret_withAlgorithm(Provider provider) throws Exception {
+        KeyAgreement keyAgreement = getKeyAgreement(provider);
+        keyAgreement.init(KAT_PRIVATE_KEY2);
+        keyAgreement.doPhase(KAT_PUBLIC_KEY1, true);
+
+        SecretKey key = keyAgreement.generateSecret("AES");
+        assertEquals("AES", key.getAlgorithm());
+        // The check below will need to change if it's a hardware-backed key.
+        // We'll have to encrypt a known plaintext and check that the ciphertext is as
+        // expected.
+        assertTrue(Arrays.equals(KAT_SECRET, key.getEncoded()));
+    }
+
+    private void invokeCallingMethodForEachKeyAgreementProvider() throws Exception {
+        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+        String callingMethodName = null;
+        for (int i = 0; i < stackTrace.length; i++) {
+            if ("invokeCallingMethodForEachKeyAgreementProvider".equals(
+                    stackTrace[i].getMethodName())) {
+                callingMethodName = stackTrace[i + 1].getMethodName();
+            }
+        }
+        if (callingMethodName == null) {
+            throw new RuntimeException("Failed to deduce calling method name from stack trace");
+        }
+
+        String invokedMethodName = callingMethodName;
+        Method method;
+        try {
+            method = getClass().getDeclaredMethod(invokedMethodName, Provider.class);
+        } catch (NoSuchMethodError e) {
+            throw new AssertionFailedError("Failed to find per-Provider test method "
+                    + getClass().getSimpleName() + "." + invokedMethodName + "(Provider)");
+        }
+
+        for (Provider provider : getKeyAgreementProviders()) {
+            try {
+                method.invoke(this, provider);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(getClass().getSimpleName() + "." + invokedMethodName
+                        + "(provider: " + provider.getName() + ") failed",
+                        e.getCause());
+            }
+        }
+    }
+
+    private static Provider[] getKeyAgreementProviders() {
+        Provider[] providers = Security.getProviders("KeyAgreement.ECDH");
+        if (providers == null) {
+            return new Provider[0];
+        }
+        // Sort providers by name to guarantee non-determinism in the order in which providers are
+        // used in the tests.
+        return sortByName(providers);
+    }
+
+    private static Provider[] getKeyFactoryProviders() {
+        Provider[] providers = Security.getProviders("KeyFactory.EC");
+        if (providers == null) {
+            return new Provider[0];
+        }
+        // Sort providers by name to guarantee non-determinism in the order in which providers are
+        // used in the tests.
+        return sortByName(providers);
+    }
+
+    private static ECPrivateKey getPrivateKey(byte[] pkcs8EncodedKey, Provider provider)
+            throws GeneralSecurityException {
+        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
+        return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
+    }
+
+    private static ECPublicKey getPublicKey(byte[] x509EncodedKey, Provider provider)
+            throws GeneralSecurityException {
+        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
+        return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedKey));
+    }
+
+    private static ECPrivateKey getPrivateKey(byte[] pkcs8EncodedKey)
+            throws GeneralSecurityException {
+        KeyFactory keyFactory = KeyFactory.getInstance("EC");
+        return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
+    }
+
+    private static ECPublicKey getPublicKey(byte[] x509EncodedKey)
+            throws GeneralSecurityException {
+        KeyFactory keyFactory = KeyFactory.getInstance("EC");
+        return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedKey));
+    }
+
+    private static KeyAgreement getKeyAgreement(Provider provider) throws NoSuchAlgorithmException {
+        return KeyAgreement.getInstance("ECDH", provider);
+    }
+
+    private static byte[] generateSecret(
+            Provider keyAgreementProvider, PrivateKey privateKey, PublicKey publicKey)
+            throws GeneralSecurityException {
+        KeyAgreement keyAgreement = getKeyAgreement(keyAgreementProvider);
+        keyAgreement.init(privateKey);
+        keyAgreement.doPhase(publicKey, true);
+        return keyAgreement.generateSecret();
+    }
+
+    private static Provider[] sortByName(Provider[] providers) {
+        Arrays.sort(providers, new Comparator<Provider>() {
+            @Override
+            public int compare(Provider lhs, Provider rhs) {
+                return lhs.getName().compareTo(rhs.getName());
+            }
+        });
+        return providers;
+    }
+}
diff --git a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java
index 7cb7792..69ae1c1 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java
@@ -31,7 +31,6 @@
 import java.util.List;
 import java.util.Set;
 import javax.net.ssl.DefaultHostnameVerifier;
-import javax.net.ssl.DistinguishedNameParser;
 import javax.security.auth.x500.X500Principal;
 import junit.framework.TestCase;
 
@@ -42,39 +41,6 @@
 
     private final DefaultHostnameVerifier verifier = new DefaultHostnameVerifier();
 
-    public void testGetFirstCn() {
-        assertFirstCn("", null);
-        assertFirstCn("ou=xxx", null);
-        assertFirstCn("ou=xxx,cn=xxx", "xxx");
-        assertFirstCn("ou=xxx+cn=yyy,cn=zzz+cn=abc", "yyy");
-        assertFirstCn("cn=a,cn=b", "a");
-        assertFirstCn("cn=Cc,cn=Bb,cn=Aa", "Cc");
-        assertFirstCn("cn=imap.gmail.com", "imap.gmail.com");
-    }
-
-    public void testGetFirstCnWithOid() {
-        assertFirstCn("2.5.4.3=a,ou=xxx", "a");
-    }
-
-    public void testGetFirstCnWithQuotedStrings() {
-        assertFirstCn("cn=\"\\\" a ,=<>#;\"", "\" a ,=<>#;");
-        assertFirstCn("cn=abc\\,def", "abc,def");
-    }
-
-    public void testGetFirstCnWithUtf8() {
-        assertFirstCn("cn=Lu\\C4\\8Di\\C4\\87", "\u004c\u0075\u010d\u0069\u0107");
-    }
-
-    public void testGetFirstCnWithWhitespace() {
-        assertFirstCn("ou=a, cn=  a  b  ,o=x", "a  b");
-        assertFirstCn("cn=\"  a  b  \" ,o=x", "  a  b  ");
-    }
-
-    private void assertFirstCn(String dn, String expected) {
-        X500Principal principal = new X500Principal(dn);
-        assertEquals("dn:" + dn, expected, new DistinguishedNameParser(principal).find("cn"));
-    }
-
     public void testVerify() {
         assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap.g.com")));
         assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap2.g.com")));
diff --git a/luni/src/test/java/libcore/javax/net/ssl/DistinguishedNameParserTest.java b/luni/src/test/java/libcore/javax/net/ssl/DistinguishedNameParserTest.java
new file mode 100644
index 0000000..723c697
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/net/ssl/DistinguishedNameParserTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.javax.net.ssl;
+
+import javax.net.ssl.DistinguishedNameParser;
+import javax.security.auth.x500.X500Principal;
+import junit.framework.TestCase;
+
+public final class DistinguishedNameParserTest extends TestCase {
+    public void testGetFirstCn() {
+        assertFirstCn("", null);
+        assertFirstCn("ou=xxx", null);
+        assertFirstCn("ou=xxx,cn=xxx", "xxx");
+        assertFirstCn("ou=xxx+cn=yyy,cn=zzz+cn=abc", "yyy");
+        assertFirstCn("cn=a,cn=b", "a");
+        assertFirstCn("cn=Cc,cn=Bb,cn=Aa", "Cc");
+        assertFirstCn("cn=imap.gmail.com", "imap.gmail.com");
+    }
+
+    public void testGetFirstCnWithOid() {
+        assertFirstCn("2.5.4.3=a,ou=xxx", "a");
+    }
+
+    public void testGetFirstCnWithQuotedStrings() {
+        assertFirstCn("cn=\"\\\" a ,=<>#;\"", "\" a ,=<>#;");
+        assertFirstCn("cn=abc\\,def", "abc,def");
+    }
+
+    public void testGetFirstCnWithUtf8() {
+        assertFirstCn("cn=Lu\\C4\\8Di\\C4\\87", "\u004c\u0075\u010d\u0069\u0107");
+    }
+
+    public void testGetFirstCnWithWhitespace() {
+        assertFirstCn("ou=a, cn=  a  b  ,o=x", "a  b");
+        assertFirstCn("cn=\"  a  b  \" ,o=x", "  a  b  ");
+    }
+
+    private void assertFirstCn(String dn, String expected) {
+        X500Principal principal = new X500Principal(dn);
+        assertEquals(dn, expected, new DistinguishedNameParser(principal).findMostSpecific("cn"));
+    }
+}
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 4095081..fcbb0e4 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
@@ -19,7 +19,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.Thread.UncaughtExceptionHandler;
 import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketException;
@@ -50,6 +52,7 @@
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
 import junit.framework.TestCase;
 import libcore.java.security.StandardNames;
 import libcore.java.security.TestKeyStore;
@@ -71,41 +74,51 @@
                 .aliasPrefix("rsa-dsa-ec")
                 .ca(true)
                 .build();
+        StringBuilder error = new StringBuilder();
         if (StandardNames.IS_RI) {
             test_SSLSocket_getSupportedCipherSuites_connect(testKeyStore,
                                                             StandardNames.JSSE_PROVIDER_NAME,
                                                             StandardNames.JSSE_PROVIDER_NAME,
                                                             true,
-                                                            true);
+                                                            true,
+                                                            error);
         } else  {
             test_SSLSocket_getSupportedCipherSuites_connect(testKeyStore,
                                                             "HarmonyJSSE",
                                                             "HarmonyJSSE",
                                                             false,
-                                                            false);
+                                                            false,
+                                                            error);
             test_SSLSocket_getSupportedCipherSuites_connect(testKeyStore,
                                                             "AndroidOpenSSL",
                                                             "AndroidOpenSSL",
                                                             true,
-                                                            true);
+                                                            true,
+                                                            error);
             test_SSLSocket_getSupportedCipherSuites_connect(testKeyStore,
                                                             "HarmonyJSSE",
                                                             "AndroidOpenSSL",
                                                             false,
-                                                            true);
+                                                            true,
+                                                            error);
             test_SSLSocket_getSupportedCipherSuites_connect(testKeyStore,
                                                             "AndroidOpenSSL",
                                                             "HarmonyJSSE",
                                                             true,
-                                                            false);
+                                                            false,
+                                                            error);
         }
-
+        if (error.length() > 0) {
+            throw new Exception("One or more problems in "
+                    + "test_SSLSocket_getSupportedCipherSuites_connect:\n" + error);
+        }
     }
     private void test_SSLSocket_getSupportedCipherSuites_connect(TestKeyStore testKeyStore,
                                                                  String clientProvider,
                                                                  String serverProvider,
                                                                  boolean clientSecureRenegotiation,
-                                                                 boolean serverSecureRenegotiation)
+                                                                 boolean serverSecureRenegotiation,
+                                                                 StringBuilder error)
             throws Exception {
 
         String clientToServerString = "this is sent from the client to the server...";
@@ -180,10 +193,13 @@
                 assertFalse(errorExpected);
             } catch (Exception maybeExpected) {
                 if (!errorExpected) {
-                    throw new Exception("Problem trying to connect cipher suite " + cipherSuite
-                                        + " client=" + clientProvider
-                                        + " server=" + serverProvider,
-                                        maybeExpected);
+                    String message = ("Problem trying to connect cipher suite " + cipherSuite
+                                      + " client=" + clientProvider
+                                      + " server=" + serverProvider);
+                    System.out.println(message);
+                    maybeExpected.printStackTrace();
+                    error.append(message);
+                    error.append('\n');
                 }
             }
         }
@@ -479,7 +495,22 @@
         c.close();
     }
 
+    private static final class TestUncaughtExceptionHandler implements UncaughtExceptionHandler {
+        Throwable actualException;
+        @Override public void uncaughtException(Thread thread, Throwable ex) {
+            assertNull(actualException);
+            actualException = ex;
+        }
+    }
+
     public void test_SSLSocket_HandshakeCompletedListener_RuntimeException() throws Exception {
+        final Thread self = Thread.currentThread();
+        final UncaughtExceptionHandler original = self.getUncaughtExceptionHandler();
+
+        final RuntimeException expectedException = new RuntimeException("expected");
+        final TestUncaughtExceptionHandler test = new TestUncaughtExceptionHandler();
+        self.setUncaughtExceptionHandler(test);
+
         final TestSSLContext c = TestSSLContext.create();
         final SSLSocket client = (SSLSocket)
                 c.clientContext.getSocketFactory().createSocket(c.host, c.port);
@@ -494,7 +525,7 @@
         executor.shutdown();
         client.addHandshakeCompletedListener(new HandshakeCompletedListener() {
             public void handshakeCompleted(HandshakeCompletedEvent event) {
-                throw new RuntimeException("RuntimeException from handshakeCompleted");
+                throw expectedException;
             }
         });
         client.startHandshake();
@@ -502,6 +533,9 @@
         client.close();
         server.close();
         c.close();
+
+        assertSame(expectedException, test.actualException);
+        self.setUncaughtExceptionHandler(original);
     }
 
     public void test_SSLSocket_getUseClientMode() throws Exception {
@@ -730,6 +764,47 @@
         c.close();
     }
 
+    public void test_SSLSocket_TrustManagerRuntimeException() throws Exception {
+        TestSSLContext c = TestSSLContext.create();
+        SSLContext clientContext = SSLContext.getInstance("TLS");
+        X509TrustManager trustManager = new X509TrustManager() {
+            @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
+                    throws CertificateException {
+                throw new AssertionError();
+            }
+            @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
+                    throws CertificateException {
+                throw new RuntimeException();  // throw a RuntimeException from custom TrustManager
+            }
+            @Override public X509Certificate[] getAcceptedIssuers() {
+                throw new AssertionError();
+            }
+        };
+        clientContext.init(null, new TrustManager[] { trustManager }, null);
+        SSLSocket client = (SSLSocket) 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();
+        try {
+            client.startHandshake();
+            fail();
+        } catch (SSLHandshakeException expected) {
+            // before we would get a RuntimeException from checkServerTrusted.
+        }
+        future.get();
+        client.close();
+        server.close();
+        c.close();
+    }
+
     public void test_SSLSocket_getEnableSessionCreation() throws Exception {
         TestSSLContext c = TestSSLContext.create();
         SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host,
@@ -1058,8 +1133,27 @@
         }
 
         final TestSSLContext c = TestSSLContext.create();
-        SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host,
-                                                                                       c.port);
+        SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket();
+
+        // Try to make the client SO_SNDBUF size as small as possible
+        // (it can default to 512k or even megabytes).  Note that
+        // socket(7) says that the kernel will double the request to
+        // leave room for its own book keeping and that the minimal
+        // value will be 2048. Also note that tcp(7) says the value
+        // needs to be set before connect(2).
+        int sendBufferSize = 1024;
+        client.setSendBufferSize(sendBufferSize);
+        sendBufferSize = client.getSendBufferSize();
+
+        // In jb-mr2 it was found that we need to also set SO_RCVBUF
+        // to a minimal size or the write would not block. While
+        // tcp(2) says the value has to be set before listen(2), it
+        // seems fine to set it before accept(2).
+        final int recvBufferSize = 128;
+        c.serverSocket.setReceiveBufferSize(recvBufferSize);
+
+        client.connect(new InetSocketAddress(c.host, c.port));
+
         final SSLSocket server = (SSLSocket) c.serverSocket.accept();
         ExecutorService executor = Executors.newSingleThreadExecutor();
         Future<Void> future = executor.submit(new Callable<Void>() {
@@ -1079,14 +1173,12 @@
                                                          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]);
+            // Add extra space to the write to exceed the send buffer
+            // size and cause the write to block.
+            final int extra = 1;
+            client.getOutputStream().write(new byte[sendBufferSize + extra]);
             fail();
         } catch (SocketTimeoutException expected) {
         }
diff --git a/luni/src/test/java/libcore/javax/net/ssl/TrustManagerFactoryTest.java b/luni/src/test/java/libcore/javax/net/ssl/TrustManagerFactoryTest.java
index 8a3fe25..ad931af 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/TrustManagerFactoryTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/TrustManagerFactoryTest.java
@@ -53,17 +53,20 @@
         return TEST_KEY_STORE;
     }
 
+    private static boolean supportsManagerFactoryParameters(String algorithm) {
+        return (StandardNames.IS_RI && algorithm.equals("PKIX"));
+    }
+
     public void test_TrustManagerFactory_getDefaultAlgorithm() throws Exception {
         String algorithm = TrustManagerFactory.getDefaultAlgorithm();
         assertEquals(StandardNames.TRUST_MANAGER_FACTORY_DEFAULT, algorithm);
         TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
-        test_TrustManagerFactory(tmf, StandardNames.IS_RI);
+        test_TrustManagerFactory(tmf);
     }
 
     private static class UselessManagerFactoryParameters implements ManagerFactoryParameters {}
 
-    private void test_TrustManagerFactory(TrustManagerFactory tmf,
-                                          boolean supportsManagerFactoryParameters)
+    private void test_TrustManagerFactory(TrustManagerFactory tmf)
             throws Exception {
         assertNotNull(tmf);
         assertNotNull(tmf.getAlgorithm());
@@ -103,7 +106,7 @@
         X509CertSelector xcs = new X509CertSelector();
         PKIXBuilderParameters pbp = new PKIXBuilderParameters(getTestKeyStore().keyStore, xcs);
         CertPathTrustManagerParameters cptmp = new CertPathTrustManagerParameters(pbp);
-        if (supportsManagerFactoryParameters) {
+        if (supportsManagerFactoryParameters(tmf.getAlgorithm())) {
             tmf.init(cptmp);
             test_TrustManagerFactory_getTrustManagers(tmf);
         } else {
@@ -179,11 +182,10 @@
                     continue;
                 }
                 String algorithm = service.getAlgorithm();
-                boolean supportsManagerFactoryParameters = algorithm.equals("PKIX");
                 {
                     TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
                     assertEquals(algorithm, tmf.getAlgorithm());
-                    test_TrustManagerFactory(tmf, supportsManagerFactoryParameters);
+                    test_TrustManagerFactory(tmf);
                 }
 
                 {
@@ -191,7 +193,7 @@
                                                                           provider);
                     assertEquals(algorithm, tmf.getAlgorithm());
                     assertEquals(provider, tmf.getProvider());
-                    test_TrustManagerFactory(tmf, supportsManagerFactoryParameters);
+                    test_TrustManagerFactory(tmf);
                 }
 
                 {
@@ -199,7 +201,7 @@
                                                                           provider.getName());
                     assertEquals(algorithm, tmf.getAlgorithm());
                     assertEquals(provider, tmf.getProvider());
-                    test_TrustManagerFactory(tmf, supportsManagerFactoryParameters);
+                    test_TrustManagerFactory(tmf);
                 }
             }
         }
diff --git a/luni/src/test/java/libcore/xml/DomTest.java b/luni/src/test/java/libcore/xml/DomTest.java
index 88da565..e4d4ecd 100644
--- a/luni/src/test/java/libcore/xml/DomTest.java
+++ b/luni/src/test/java/libcore/xml/DomTest.java
@@ -1655,6 +1655,19 @@
         assertNull(text.getNextSibling());
     }
 
+    // http://code.google.com/p/android/issues/detail?id=24530
+    public void testInsertBefore() throws Exception {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        Document d = factory.newDocumentBuilder().newDocument();
+        d.appendChild(d.createElement("root"));
+        d.getFirstChild().insertBefore(d.createElement("foo"), null);
+        assertEquals("foo", d.getFirstChild().getFirstChild().getNodeName());
+        assertEquals("foo", d.getFirstChild().getLastChild().getNodeName());
+        d.getFirstChild().insertBefore(d.createElement("bar"), null);
+        assertEquals("foo", d.getFirstChild().getFirstChild().getNodeName());
+        assertEquals("bar", d.getFirstChild().getLastChild().getNodeName());
+    }
+
     public void testBomAndByteInput() throws Exception {
         byte[] xml = {
                 (byte) 0xef, (byte) 0xbb, (byte) 0xbf,
diff --git a/luni/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java b/luni/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java
index 226ea66..64a179b 100644
--- a/luni/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java
+++ b/luni/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java
@@ -69,6 +69,12 @@
 
     private final String jarName5 = "hyts_signed_inc.jar";
 
+    private final String jarName6 = "hyts_signed_sha256withrsa.jar";
+
+    private final String jarName7 = "hyts_signed_sha256digest_sha256withrsa.jar";
+
+    private final String jarName8 = "hyts_signed_sha512digest_sha512withecdsa.jar";
+
     private final String entryName = "foo/bar/A.class";
 
     private final String entryName3 = "coucou/FileAccess.class";
@@ -520,7 +526,7 @@
     // This test doesn't pass on RI. If entry size is set up incorrectly,
     // SecurityException is thrown. But SecurityException is thrown on RI only
     // if jar file is signed incorrectly.
-    public void test_getInputStreamLjava_util_jar_JarEntry_subtest0() {
+    public void test_getInputStreamLjava_util_jar_JarEntry_subtest0() throws Exception {
         File signedFile = null;
         try {
             Support_Resources.copyFile(resources, null, jarName4);
@@ -582,6 +588,42 @@
         } catch (Exception e) {
             fail("Exception during test 5: " + e);
         }
+
+        // SHA1 digest, SHA256withRSA signed JAR
+        checkSignedJar(jarName6);
+
+        // SHA-256 digest, SHA256withRSA signed JAR
+        checkSignedJar(jarName7);
+
+        // SHA-512 digest, SHA512withECDSA signed JAR
+        checkSignedJar(jarName8);
+    }
+
+    private void checkSignedJar(String jarName) throws Exception {
+        Support_Resources.copyFile(resources, null, jarName);
+
+        File file = new File(resources, jarName);
+
+        JarFile jarFile = new JarFile(file, true);
+
+        boolean foundCerts = false;
+
+        Enumeration<JarEntry> e = jarFile.entries();
+        while (e.hasMoreElements()) {
+            JarEntry entry = e.nextElement();
+            InputStream is = jarFile.getInputStream(entry);
+            is.skip(100000);
+            is.close();
+            Certificate[] certs = entry.getCertificates();
+            if (certs != null && certs.length > 0) {
+                foundCerts = true;
+                break;
+            }
+        }
+
+        assertTrue(
+                "No certificates found during signed jar test for jar \""
+                        + jarName + "\"", foundCerts);
     }
 
     /*
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 c07b180..b4da1b8 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
@@ -362,8 +362,8 @@
 
             byte[] plaintextBytes = loadBytes("hyts_" + "des-ede3-cbc.test"
                     + index + ".plaintext");
-            assertTrue("Operation produced incorrect results", Arrays.equals(
-                    plaintextBytes, decipheredCipherText));
+            assertEquals("Operation produced incorrect results for index " + index,
+                    Arrays.toString(plaintextBytes), Arrays.toString(decipheredCipherText));
         }
 
         Cipher cipher = Cipher.getInstance("DESEDE/CBC/PKCS5Padding");
@@ -418,8 +418,8 @@
 
             byte[] cipherText = loadBytes("hyts_" + "des-ede3-cbc.test" + index
                     + ".ciphertext");
-            assertTrue("Operation produced incorrect results", Arrays.equals(
-                    encryptedPlaintext, cipherText));
+            assertEquals("Operation produced incorrect results for index " + index,
+                    Arrays.toString(cipherText), Arrays.toString(encryptedPlaintext));
         }
 
         byte[] b = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java
index e808bc1..d65dd4b 100644
--- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java
+++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java
@@ -29,11 +29,14 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.Provider;
+import java.security.Security;
 import java.security.spec.DSAParameterSpec;
 import java.security.spec.PSSParameterSpec;
+import java.util.Arrays;
 
 import javax.crypto.Mac;
 import javax.crypto.MacSpi;
+import javax.crypto.SecretKey;
 import javax.crypto.ShortBufferException;
 import javax.crypto.spec.DHGenParameterSpec;
 
@@ -41,6 +44,9 @@
 
 import org.apache.harmony.crypto.tests.support.MyMacSpi;
 import org.apache.harmony.security.tests.support.SpiEngUtils;
+import org.apache.harmony.xnet.provider.jsse.NativeCryptoTest;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLProvider;
 
 import junit.framework.TestCase;
 
@@ -443,19 +449,18 @@
         SecretKeySpec scs = new SecretKeySpec(b, "SHA1");
         for (int i = 0; i < macs.length; i++) {
             macs[i].init(scs);
-            byte [] res1 = macs[i].doFinal();
-            byte [] res2 = macs[i].doFinal();
-            assertEquals("Results are not the same", res1.length, res2.length);
-            for(int t = 0; t < res1.length; t++) {
-                assertEquals("Results are not the same", res1[t], res2[t]);
-            }
+            byte[] res1 = macs[i].doFinal();
+            byte[] res2 = macs[i].doFinal();
+            assertEquals("Results are not the same",
+                    IntegralToString.bytesToHexString(res1, false),
+                    IntegralToString.bytesToHexString(res2, false));
+
             res2 = macs[i].doFinal(upd);
             macs[i].update(upd);
             res1 = macs[i].doFinal();
-            assertEquals("Results are not the same", res1.length, res2.length);
-            for(int t = 0; t < res1.length; t++) {
-                assertEquals("Results are not the same", res1[t], res2[t]);
-            }
+            assertEquals("Results are not the same",
+                    IntegralToString.bytesToHexString(res1, false),
+                    IntegralToString.bytesToHexString(res2, false));
         }
     }
 
@@ -828,6 +833,104 @@
         assertNull(mac.getProvider());
     }
 
+    private static final byte[] TEST_INPUT = new byte[] {
+            0x01, (byte) 0xFF, 0x55, (byte) 0xAA
+    };
+
+    public void test_ConsistentBetweenProviders() throws Exception {
+        SecretKey key = new SecretKeySpec(new byte[] {
+                (byte) 0x7b, (byte) 0x10, (byte) 0x6d, (byte) 0x68, (byte) 0x3f, (byte) 0x70,
+                (byte) 0xa3, (byte) 0xb5, (byte) 0xa3, (byte) 0xdd, (byte) 0x9f, (byte) 0x54,
+                (byte) 0x74, (byte) 0x36, (byte) 0xde, (byte) 0xa7, (byte) 0x88, (byte) 0x81,
+                (byte) 0x0d, (byte) 0x89, (byte) 0xef, (byte) 0x2e, (byte) 0x42, (byte) 0x4f,
+        }, "HmacMD5");
+        byte[] label = new byte[] {
+                (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x20, (byte) 0x65, (byte) 0x78,
+                (byte) 0x70, (byte) 0x61, (byte) 0x6e, (byte) 0x73, (byte) 0x69, (byte) 0x6f,
+                (byte) 0x6e,
+        };
+        byte[] seed = new byte[] {
+                (byte) 0x50, (byte) 0xf9, (byte) 0xce, (byte) 0x14, (byte) 0xb2, (byte) 0xdd,
+                (byte) 0x3d, (byte) 0xfa, (byte) 0x96, (byte) 0xd9, (byte) 0xfe, (byte) 0x3a,
+                (byte) 0x1a, (byte) 0xe5, (byte) 0x79, (byte) 0x55, (byte) 0xe7, (byte) 0xbc,
+                (byte) 0x84, (byte) 0x68, (byte) 0x0e, (byte) 0x2d, (byte) 0x20, (byte) 0xd0,
+                (byte) 0x6e, (byte) 0xb4, (byte) 0x03, (byte) 0xbf, (byte) 0xa2, (byte) 0xe6,
+                (byte) 0xc4, (byte) 0x9d, (byte) 0x50, (byte) 0xf9, (byte) 0xce, (byte) 0x14,
+                (byte) 0xbc, (byte) 0xc5, (byte) 0x9e, (byte) 0x9a, (byte) 0x36, (byte) 0xa7,
+                (byte) 0xaa, (byte) 0xfe, (byte) 0x3b, (byte) 0xca, (byte) 0xcb, (byte) 0x4c,
+                (byte) 0xfa, (byte) 0x87, (byte) 0x9a, (byte) 0xac, (byte) 0x02, (byte) 0x25,
+                (byte) 0xce, (byte) 0xda, (byte) 0x74, (byte) 0x10, (byte) 0x86, (byte) 0x9c,
+                (byte) 0x03, (byte) 0x18, (byte) 0x0f, (byte) 0xe2,
+        };
+        Provider[] providers = Security.getProviders("Mac.HmacMD5");
+        Provider defProvider = null;
+        byte[] output = null;
+        byte[] output2 = null;
+        for (int i = 0; i < providers.length; i++) {
+            System.out.println("provider = " + providers[i].getName());
+            Mac mac = Mac.getInstance("HmacMD5", providers[i]);
+            mac.init(key);
+            mac.update(label);
+            mac.update(seed);
+            if (output == null) {
+                output = new byte[mac.getMacLength()];
+                defProvider = providers[i];
+                mac.doFinal(output, 0);
+                mac.init(new SecretKeySpec(label, "HmacMD5"));
+                output2 = mac.doFinal(output);
+            } else {
+                byte[] tmp = new byte[mac.getMacLength()];
+                mac.doFinal(tmp, 0);
+                assertEquals(defProvider.getName() + " vs. " + providers[i].getName(),
+                        Arrays.toString(output), Arrays.toString(tmp));
+                mac.init(new SecretKeySpec(label, "HmacMD5"));
+                assertEquals(defProvider.getName() + " vs. " + providers[i].getName(),
+                        Arrays.toString(output2), Arrays.toString(mac.doFinal(output)));
+            }
+
+        }
+    }
+
+    public void test_getInstance_OpenSSL_ENGINE() throws Exception {
+        final String secret = "-HMAC-test1";
+        final byte[] testString = "testing123".getBytes();
+
+        Provider p = Security.getProvider(OpenSSLProvider.PROVIDER_NAME);
+        NativeCryptoTest.loadTestEngine();
+        OpenSSLEngine engine = OpenSSLEngine.getInstance(NativeCryptoTest.TEST_ENGINE_ID);
+
+        /*
+         * The "-HMAC-" prefix is a special prefix recognized by
+         * test_openssl_engine.cpp
+         */
+        SecretKey key1 = engine.getSecretKeyById(secret, "HmacSHA256");
+        SecretKey key1dupe = engine.getSecretKeyById(secret, "HmacSHA256");
+
+        /* Non-ENGINE-based SecretKey */
+        SecretKey key2 = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
+
+        /* The one that is ENGINE-based can't be equal to a non-ENGINE one. */
+        assertFalse(key1.equals(key2));
+        assertEquals(key1, key1dupe);
+        assertNull(key1.getFormat());
+        assertNull(key1.getEncoded());
+        assertEquals("RAW", key2.getFormat());
+        assertEquals(Arrays.toString(secret.getBytes()), Arrays.toString(key2.getEncoded()));
+
+        Mac mac1 = Mac.getInstance("HmacSHA256", p);
+        mac1.init(key1);
+        mac1.update(testString);
+        byte[] output1 = mac1.doFinal();
+        assertEquals(mac1.getMacLength(), output1.length);
+
+        Mac mac2 = Mac.getInstance("HmacSHA256", p);
+        mac2.init(key2);
+        mac2.update(testString);
+        byte[] output2 = mac2.doFinal();
+
+        assertEquals(Arrays.toString(output2), Arrays.toString(output1));
+    }
+
     class Mock_Mac extends Mac {
         protected Mock_Mac(MacSpi arg0, Provider arg1, String arg2) {
             super(arg0, arg1, arg2);
diff --git a/luni/src/test/java/org/apache/harmony/regex/tests/java/util/regex/PatternTest.java b/luni/src/test/java/org/apache/harmony/regex/tests/java/util/regex/PatternTest.java
index ffbdf9e..a81d294 100644
--- a/luni/src/test/java/org/apache/harmony/regex/tests/java/util/regex/PatternTest.java
+++ b/luni/src/test/java/org/apache/harmony/regex/tests/java/util/regex/PatternTest.java
@@ -1774,4 +1774,11 @@
         }
     }
 
+    // http://code.google.com/p/android/issues/detail?id=19308
+    public void test_hitEnd() {
+        Pattern p = Pattern.compile("^2(2[4-9]|3\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
+        Matcher m = p.matcher("224..");
+        boolean isPartialMatch = !m.matches() && m.hitEnd();
+        assertFalse(isPartialMatch);
+    }
 }
diff --git a/luni/src/test/java/org/apache/harmony/security/tests/AlgNameMapperTest.java b/luni/src/test/java/org/apache/harmony/security/tests/AlgNameMapperTest.java
new file mode 100644
index 0000000..943eb43
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/security/tests/AlgNameMapperTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.security.tests;
+
+import org.apache.harmony.security.utils.AlgNameMapper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+public class AlgNameMapperTest extends TestCase {
+    private final String[][] HARDCODED_ALIASES = {
+            {"1.2.840.10040.4.1",       "DSA"},
+            {"1.2.840.10040.4.3",       "SHA1withDSA"},
+            {"1.2.840.113549.1.1.1",    "RSA"},
+            {"1.2.840.113549.1.1.4",    "MD5withRSA"},
+            {"1.2.840.113549.1.1.5",    "SHA1withRSA"},
+            {"1.2.840.113549.1.3.1",    "DiffieHellman"},
+            {"1.2.840.113549.1.5.3",    "pbeWithMD5AndDES-CBC"},
+            {"1.2.840.113549.1.12.1.3", "pbeWithSHAAnd3-KeyTripleDES-CBC"},
+            {"1.2.840.113549.1.12.1.6", "pbeWithSHAAnd40BitRC2-CBC"},
+            {"1.2.840.113549.3.2",      "RC2-CBC"},
+            {"1.2.840.113549.3.3",      "RC2-EBC"},
+            {"1.2.840.113549.3.4",      "RC4"},
+            {"1.2.840.113549.3.5",      "RC4WithMAC"},
+            {"1.2.840.113549.3.6",      "DESx-CBC"},
+            {"1.2.840.113549.3.7",      "TripleDES-CBC"},
+            {"1.2.840.113549.3.8",      "rc5CBC"},
+            {"1.2.840.113549.3.9",      "RC5-CBC"},
+            {"1.2.840.113549.3.10",     "DESCDMF"},
+            {"2.23.42.9.11.4.1",        "ECDSA"},
+    };
+
+    public void testHardcodedAliases() throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+
+        for (int i = 0; i < HARDCODED_ALIASES.length; i++) {
+            try {
+                assertEquals(HARDCODED_ALIASES[i][1].toUpperCase(Locale.US),
+                        AlgNameMapper.map2AlgName(HARDCODED_ALIASES[i][0]).toUpperCase(Locale.US));
+
+                assertEquals(HARDCODED_ALIASES[i][0],
+                        AlgNameMapper.map2OID(HARDCODED_ALIASES[i][1]));
+
+                assertEquals(HARDCODED_ALIASES[i][1].toUpperCase(Locale.US),
+                        AlgNameMapper.getStandardName(HARDCODED_ALIASES[i][1]
+                                .toUpperCase(Locale.US)).toUpperCase(Locale.US));
+
+                assertTrue(AlgNameMapper.isOID(HARDCODED_ALIASES[i][0]));
+            } catch (Throwable e) {
+                out.append("Error encountered checking " + HARDCODED_ALIASES[i][1] + "\n");
+                e.printStackTrace(out);
+            }
+        }
+
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+
+    private final String[][] NON_HARDCODED_ALIASES = {
+            {"2.16.840.1.101.3.4.2.3", "SHA512"}, // This isn't currently hardcoded in AlgNameMapper
+            {"1.2.840.10045.3.1.7", "prime256v1"}, // No provider provides EC curves
+    };
+
+    public void testNon_Hardcoded_Aliases_Exist() throws Exception {
+        final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(errBuffer);
+
+        for (int i = 0; i < NON_HARDCODED_ALIASES.length; i++) {
+            try {
+                String algName = AlgNameMapper.map2AlgName(NON_HARDCODED_ALIASES[i][0]);
+                assertNotNull(algName);
+                assertEquals(NON_HARDCODED_ALIASES[i][1].toUpperCase(Locale.US),
+                        algName.toUpperCase(Locale.US));
+
+                String oid = AlgNameMapper.map2OID(algName);
+                assertNotNull(oid);
+                assertEquals(NON_HARDCODED_ALIASES[i][0], oid);
+            } catch (Throwable e) {
+                out.append("Error encountered checking " + HARDCODED_ALIASES[i][1] + "\n");
+                e.printStackTrace(out);
+            }
+        }
+
+        out.flush();
+        if (errBuffer.size() > 0) {
+            throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n");
+        }
+    }
+}
diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyFactory2Test.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyFactory2Test.java
index 1be7fa6..f6210a9 100644
--- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyFactory2Test.java
+++ b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyFactory2Test.java
@@ -86,14 +86,10 @@
         return null;
     }
 
-    public void test_constructor() {
+    public void test_constructor() throws Exception {
         KeyFactorySpi kfs = new KeyFactorySpiStub();
 
-        try {
-            new KeyFactoryStub(null, null, null);
-        } catch (Exception e) {
-            fail("Unexpected exception " + e.getMessage());
-        }
+        new KeyFactoryStub(null, null, null);
 
         Provider[] providers = Security.getProviders("KeyFactory.DSA");
         if (providers != null) {
@@ -108,265 +104,200 @@
         }
     }
 
-    public void test_generatePrivateLjava_security_spec_KeySpec() {
+    public void test_generatePrivateLjava_security_spec_KeySpec() throws Exception {
         // Test for method java.security.PrivateKey
         // java.security.KeyFactory.generatePrivate(java.security.spec.KeySpec)
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
-                        providerName);
-                KeyPairGenerator keyGen = KeyPairGenerator
-                        .getInstance(keyfactAlgs[i]);
-                SecureRandom random = new SecureRandom(); // We don't use
-                // getInstance
-                keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
-                KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
-                KeyPair keys = keyGen.generateKeyPair();
-                if (keepalive != null) {
-                    keepalive.interrupt();
-                }
-
-                KeySpec privateKeySpec = fact.getKeySpec(keys.getPrivate(),
-                        StandardNames.getPrivateKeySpecClass(keyfactAlgs[i]));
-                PrivateKey privateKey = fact.generatePrivate(privateKeySpec);
-                boolean samePrivate = Arrays.equals(keys.getPrivate()
-                        .getEncoded(), privateKey.getEncoded());
-                assertTrue(
-                        "generatePrivate generated different key for algorithm "
-                                + keyfactAlgs[i], samePrivate);
-                fact.generatePrivate(new PKCS8EncodedKeySpec(keys.getPrivate()
-                        .getEncoded()));
-
-            } catch (InvalidKeySpecException e) {
-                fail("invalid key spec for algorithm " + keyfactAlgs[i]);
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            } catch (NoSuchProviderException e) {
-                fail("getInstance did not find provider " + providerName);
+            KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i], providerName);
+            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyfactAlgs[i]);
+            SecureRandom random = new SecureRandom(); // We don't use
+            // getInstance
+            keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
+            KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
+            KeyPair keys = keyGen.generateKeyPair();
+            if (keepalive != null) {
+                keepalive.interrupt();
             }
+
+            KeySpec privateKeySpec = fact.getKeySpec(keys.getPrivate(),
+                    StandardNames.getPrivateKeySpecClass(keyfactAlgs[i]));
+            PrivateKey privateKey = fact.generatePrivate(privateKeySpec);
+            assertEquals("generatePrivate generated different key for algorithm " + keyfactAlgs[i],
+                    Arrays.toString(keys.getPrivate().getEncoded()),
+                    Arrays.toString(privateKey.getEncoded()));
+            privateKey = fact.generatePrivate(new PKCS8EncodedKeySpec(keys.getPrivate().getEncoded()));
+            assertEquals("generatePrivate generated different key for algorithm " + keyfactAlgs[i],
+                    Arrays.toString(keys.getPrivate().getEncoded()),
+                    Arrays.toString(privateKey.getEncoded()));
         }
     }
 
-    public void test_generatePublicLjava_security_spec_KeySpec() {
+    public void test_generatePublicLjava_security_spec_KeySpec() throws Exception {
         // Test for method java.security.PublicKey
         // java.security.KeyFactory.generatePublic(java.security.spec.KeySpec)
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
-                        providerName);
-                KeyPairGenerator keyGen = KeyPairGenerator
-                        .getInstance(keyfactAlgs[i]);
-                // We don't use getInstance
-                SecureRandom random = new SecureRandom();
-                keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
-                KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
-                KeyPair keys = keyGen.generateKeyPair();
-                if (keepalive != null) {
-                    keepalive.interrupt();
-                }
-                KeySpec publicKeySpec = fact.getKeySpec(keys.getPublic(),
-                        StandardNames.getPublicKeySpecClass(keyfactAlgs[i]));
-                PublicKey publicKey = fact.generatePublic(publicKeySpec);
-                boolean samePublic = Arrays.equals(keys.getPublic()
-                        .getEncoded(), publicKey.getEncoded());
-                assertTrue(
-                        "generatePublic generated different key for algorithm "
-                                + keyfactAlgs[i], samePublic);
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            } catch (NoSuchProviderException e) {
-                fail("getInstance did not find provider " + providerName);
-            } catch (InvalidKeySpecException e) {
-                fail("invalid key spec for algorithm " + keyfactAlgs[i]);
+            KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
+                    providerName);
+            KeyPairGenerator keyGen = KeyPairGenerator
+                    .getInstance(keyfactAlgs[i]);
+            // We don't use getInstance
+            SecureRandom random = new SecureRandom();
+            keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
+            KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
+            KeyPair keys = keyGen.generateKeyPair();
+            if (keepalive != null) {
+                keepalive.interrupt();
             }
+            KeySpec publicKeySpec = fact.getKeySpec(keys.getPublic(),
+                    StandardNames.getPublicKeySpecClass(keyfactAlgs[i]));
+            PublicKey publicKey = fact.generatePublic(publicKeySpec);
+            assertEquals(
+                    "generatePublic generated different key for algorithm "
+                            + keyfactAlgs[i] + " (provider="
+                            + fact.getProvider().getName() + ")",
+                            Arrays.toString(keys.getPublic().getEncoded()),
+                            Arrays.toString(publicKey.getEncoded()));
         }
     }
 
-    public void test_getAlgorithm() {
+    public void test_getAlgorithm() throws Exception {
         // Test for method java.lang.String
         // java.security.KeyFactory.getAlgorithm()
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
-                        providerName);
-                assertTrue("getAlgorithm ok for algorithm " + keyfactAlgs[i],
-                        fact.getAlgorithm().equals(keyfactAlgs[i]));
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            } catch (NoSuchProviderException e) {
-                fail("getInstance did not find provider " + providerName);
-            }
-        }// end for
+            KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
+                    providerName);
+            assertTrue("getAlgorithm ok for algorithm " + keyfactAlgs[i],
+                    fact.getAlgorithm().equals(keyfactAlgs[i]));
+        }
     }
 
-    public void test_getInstanceLjava_lang_String() {
+    public void test_getInstanceLjava_lang_String() throws Exception {
         // Test for method java.security.KeyFactory
         // java.security.KeyFactory.getInstance(java.lang.String)
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                assertNotNull(KeyFactory.getInstance(keyfactAlgs[i]));
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            }
-        }// end for
+            assertNotNull(KeyFactory.getInstance(keyfactAlgs[i]));
+        }
     }
 
-    public void test_getInstanceLjava_lang_StringLjava_lang_String() {
+    public void test_getInstanceLjava_lang_StringLjava_lang_String() throws Exception {
         // Test1: Test for method java.security.KeyFactory
         // java.security.KeyFactory.getInstance(java.lang.String,
         // java.lang.String)
-        try {
-            Provider[] providers = Security.getProviders("KeyFactory.DSA");
-            if (providers != null) {
-                for (int i = 0; i < providers.length; i++) {
-                    KeyFactory.getInstance("DSA", providers[i].getName());
-                }// end for
-            } else {
-                fail("No providers support KeyFactory.DSA");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            fail("getInstance did not find algorithm");
-        } catch (NoSuchProviderException e) {
-            fail("getInstance did not find the provider");
+        Provider[] providers = Security.getProviders("KeyFactory.DSA");
+        assertNotNull(providers);
+
+        for (int i = 0; i < providers.length; i++) {
+            KeyFactory.getInstance("DSA", providers[i].getName());
         }
 
+
         // Test2: Test with null provider name
         try {
             KeyFactory.getInstance("DSA", (String) null);
             fail("Expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expected
+        } catch (IllegalArgumentException expected) {
         } catch (Exception e) {
             fail("Expected IllegalArgumentException, got " + e);
         }
     }
 
-    public void test_getInstanceLjava_lang_StringLjava_security_Provider() {
+    public void test_getInstanceLjava_lang_StringLjava_security_Provider() throws Exception {
         // Test1: Test for method java.security.KeyFactory
         // java.security.KeyFactory.getInstance(java.lang.String,
         // java.security.Provider)
-        try {
-            Provider[] providers = Security.getProviders("KeyFactory.DSA");
-            if (providers != null) {
-                for (int i = 0; i < providers.length; i++) {
-                    KeyFactory.getInstance("DSA", providers[i]);
-                }// end for
-            } else {
-                fail("No providers support KeyFactory.DSA");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            fail("getInstance did not find algorithm");
-        } catch (Exception e) {
-            fail("unexpected exception " + e.getMessage());
+        Provider[] providers = Security.getProviders("KeyFactory.DSA");
+        assertNotNull(providers);
+
+        for (int i = 0; i < providers.length; i++) {
+            KeyFactory.getInstance("DSA", providers[i]);
         }
 
         // Test2: Test with null provider name
         try {
             KeyFactory.getInstance("DSA", (Provider) null);
             fail("Expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expected
+        } catch (IllegalArgumentException expected) {
         } catch (Exception e) {
             fail("Expected IllegalArgumentException, got " + e);
         }
     }
 
-    public void test_getKeySpecLjava_security_KeyLjava_lang_Class() {
+    public void test_getKeySpecLjava_security_KeyLjava_lang_Class() throws Exception {
         // Test for method java.security.spec.KeySpec
         // java.security.KeyFactory.getKeySpec(java.security.Key,
         // java.lang.Class)
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
-                        providerName);
-                KeyPairGenerator keyGen = KeyPairGenerator
-                        .getInstance(keyfactAlgs[i]);
+            KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
+                    providerName);
+            KeyPairGenerator keyGen = KeyPairGenerator
+                    .getInstance(keyfactAlgs[i]);
 
-                // We don't use getInstance
-                SecureRandom random = new SecureRandom();
-                keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
-                KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
-                KeyPair keys = keyGen.generateKeyPair();
-                if (keepalive != null) {
-                    keepalive.interrupt();
-                }
-                KeySpec privateKeySpec = fact.getKeySpec(keys.getPrivate(),
-                        StandardNames.getPrivateKeySpecClass(keyfactAlgs[i]));
-                KeySpec publicKeySpec = fact.getKeySpec(keys.getPublic(),
-                        StandardNames.getPublicKeySpecClass(keyfactAlgs[i]));
-                PrivateKey privateKey = fact.generatePrivate(privateKeySpec);
-                PublicKey publicKey = fact.generatePublic(publicKeySpec);
-                boolean samePublic = Arrays.equals(keys.getPublic()
-                        .getEncoded(), publicKey.getEncoded());
-                boolean samePrivate = Arrays.equals(keys.getPrivate()
-                        .getEncoded(), privateKey.getEncoded());
-                assertTrue(
-                        "generatePrivate generated different key for algorithm "
-                                + keyfactAlgs[i], samePrivate);
-                assertTrue(
-                        "generatePublic generated different key for algorithm "
-                                + keyfactAlgs[i], samePublic);
-                KeySpec encodedSpec = fact.getKeySpec(keys.getPublic(),
-                        X509EncodedKeySpec.class);
-                assertTrue("improper key spec for encoded public key",
-                        encodedSpec.getClass().equals(X509EncodedKeySpec.class));
-                encodedSpec = fact.getKeySpec(keys.getPrivate(),
-                        PKCS8EncodedKeySpec.class);
-                assertTrue("improper key spec for encoded private key",
-                        encodedSpec.getClass()
-                                .equals(PKCS8EncodedKeySpec.class));
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            } catch (NoSuchProviderException e) {
-                fail("getInstance did not find provider " + providerName);
-            } catch (InvalidKeySpecException e) {
-                fail("invalid key spec for algorithm " + keyfactAlgs[i]);
+            // We don't use getInstance
+            SecureRandom random = new SecureRandom();
+            keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
+            KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
+            KeyPair keys = keyGen.generateKeyPair();
+            if (keepalive != null) {
+                keepalive.interrupt();
             }
+            KeySpec privateKeySpec = fact.getKeySpec(keys.getPrivate(),
+                    StandardNames.getPrivateKeySpecClass(keyfactAlgs[i]));
+            KeySpec publicKeySpec = fact.getKeySpec(keys.getPublic(),
+                    StandardNames.getPublicKeySpecClass(keyfactAlgs[i]));
+            PrivateKey privateKey = fact.generatePrivate(privateKeySpec);
+            PublicKey publicKey = fact.generatePublic(publicKeySpec);
+            assertEquals(
+                    "generatePrivate generated different key for algorithm "
+                            + keyfactAlgs[i] + " (provider="
+                            + fact.getProvider().getName() + ")",
+                            Arrays.toString(keys.getPrivate().getEncoded()),
+                            Arrays.toString(privateKey.getEncoded()));
+            assertEquals(
+                    "generatePublic generated different key for algorithm "
+                            + keyfactAlgs[i] + " (provider="
+                            + fact.getProvider().getName() + ")",
+                            Arrays.toString(keys.getPublic().getEncoded()),
+                            Arrays.toString(publicKey.getEncoded()));
+            KeySpec encodedSpec = fact.getKeySpec(keys.getPublic(),
+                    X509EncodedKeySpec.class);
+            assertTrue("improper key spec for encoded public key",
+                    encodedSpec.getClass().equals(X509EncodedKeySpec.class));
+            encodedSpec = fact.getKeySpec(keys.getPrivate(),
+                    PKCS8EncodedKeySpec.class);
+            assertTrue("improper key spec for encoded private key",
+                    encodedSpec.getClass()
+                            .equals(PKCS8EncodedKeySpec.class));
         }
     }
 
-    public void test_getProvider() {
+    public void test_getProvider() throws Exception {
         // Test for method java.security.Provider
         // java.security.KeyFactory.getProvider()
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i]);
-                Provider p = fact.getProvider();
-                assertNotNull("provider is null for algorithm "
-                        + keyfactAlgs[i], p);
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            }
-        }// end for
+            KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i]);
+            Provider p = fact.getProvider();
+            assertNotNull("provider is null for algorithm " + keyfactAlgs[i], p);
+        }
     }
 
-    public void test_translateKeyLjava_security_Key() {
+    public void test_translateKeyLjava_security_Key() throws Exception {
         // Test for method java.security.Key
         // java.security.KeyFactory.translateKey(java.security.Key)
         for (int i = 0; i < keyfactAlgs.length; i++) {
-            try {
-                KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
-                        providerName);
-                KeyPairGenerator keyGen = KeyPairGenerator
-                        .getInstance(keyfactAlgs[i]);
+            KeyFactory fact = KeyFactory.getInstance(keyfactAlgs[i],
+                    providerName);
+            KeyPairGenerator keyGen = KeyPairGenerator
+                    .getInstance(keyfactAlgs[i]);
 
-                // We don't use getInstance
-                SecureRandom random = new SecureRandom();
-                keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
-                KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
-                KeyPair keys = keyGen.generateKeyPair();
-                if (keepalive != null) {
-                    keepalive.interrupt();
-                }
-                fact.translateKey(keys.getPrivate());
-            } catch (NoSuchAlgorithmException e) {
-                fail("getInstance did not find algorithm " + keyfactAlgs[i]);
-            } catch (NoSuchProviderException e) {
-                fail("getInstance did not find provider " + providerName);
-            } catch (InvalidKeyException e) {
-                fail("generatePublic did not generate right spec for algorithm "
-                        + keyfactAlgs[i]);
+            // We don't use getInstance
+            SecureRandom random = new SecureRandom();
+            keyGen.initialize(StandardNames.getMinimumKeySize(keyfactAlgs[i]), random);
+            KeepAlive keepalive = createKeepAlive(keyfactAlgs[i]);
+            KeyPair keys = keyGen.generateKeyPair();
+            if (keepalive != null) {
+                keepalive.interrupt();
             }
+            fact.translateKey(keys.getPrivate());
         }
     }
 
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 68e7cbc..4dfffda 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
@@ -57,17 +57,15 @@
             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)
-                            + entry.getValue();
+                    String aliasName = key.substring("ALG.ALIAS.".length()).toUpperCase();
+                    String realName = aliasName.substring(0, aliasName.indexOf(".") + 1) + entry.getValue();
                     // Skip over nonsense alias declarations where alias and
                     // aliased are identical. Such entries can occur.
-                    if (!aliasVal.equalsIgnoreCase(aliasKey)) {
-                        // Has a real entry been added for aliasValue ?
-                        if (allSupported.containsKey(aliasVal.toUpperCase())) {
-                            // Add 1 to the provider count of the thing being
-                            // aliased
-                            addOrIncrementTable(allSupported, aliasKey);
+                    if (!aliasName.equalsIgnoreCase(realName)) {
+                        // Has a real entry been added for aliasName ?
+                        if (allSupported.containsKey(aliasName)) {
+                            // Add 1 to the provider count of the thing being aliased
+                            addOrIncrementTable(allSupported, aliasName);
                         }
                     }
                 }
diff --git a/luni/src/test/java/org/apache/harmony/security/tests/x509/SubjectPublicKeyInfoTest.java b/luni/src/test/java/org/apache/harmony/security/tests/x509/SubjectPublicKeyInfoTest.java
new file mode 100644
index 0000000..ade1069
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/security/tests/x509/SubjectPublicKeyInfoTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.security.tests.x509;
+
+import org.apache.harmony.security.asn1.ASN1Integer;
+import org.apache.harmony.security.asn1.ASN1Sequence;
+import org.apache.harmony.security.asn1.ASN1Type;
+import org.apache.harmony.security.x509.AlgorithmIdentifier;
+import org.apache.harmony.security.x509.SubjectPublicKeyInfo;
+import org.apache.harmony.security.x509.X509PublicKey;
+
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+public class SubjectPublicKeyInfoTest extends TestCase {
+    private static final byte[] ENCODED_BROKEN = "BROKEN!".getBytes(Charset.forName("ASCII"));
+
+    public void test_getPublicKey_WellKnownOid() throws Exception {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        KeyPair pair = kpg.generateKeyPair();
+
+        final RSAPublicKey rsaPubKey = (RSAPublicKey) pair.getPublic();
+
+        /* Do some fancy footwork to get an ASN.1 SubjectPublicKey for RSA */
+        final ASN1Sequence rsaPubKeyInfo = new ASN1Sequence(new ASN1Type[] {
+                ASN1Integer.getInstance(), ASN1Integer.getInstance(),
+        }) {
+            @Override
+            protected void getValues(Object object, Object[] values) {
+                values[0] = rsaPubKey.getModulus().toByteArray();
+                values[1] = rsaPubKey.getPublicExponent().toByteArray();
+            }
+        };
+
+        /* The algorithm ID for RSA encryption */
+        AlgorithmIdentifier algid = new AlgorithmIdentifier("1.2.840.113549.1.1.1");
+
+        SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algid, rsaPubKeyInfo.encode(null));
+
+        PublicKey pubKey = spki.getPublicKey();
+        assertNotNull(pubKey);
+        assertTrue(pubKey instanceof RSAPublicKey);
+    }
+
+    public void test_getPublicKey_Unknown_OID() throws Exception {
+        AlgorithmIdentifier algid = new AlgorithmIdentifier("1.30.9999999999.8734878");
+        SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algid, ENCODED_BROKEN);
+        PublicKey pubKey = spki.getPublicKey();
+        assertNotNull(pubKey);
+        assertEquals(X509PublicKey.class, pubKey.getClass());
+    }
+
+    private static final String MY_TEST_KEY_OID = "1.30.987654321.1.1.1.2.2.2";
+
+    public void test_getPublicKey_NameKnownButOnlyOIDFactoryRegistered() throws Exception {
+        Security.addProvider(new MyTestProvider());
+        try {
+            AlgorithmIdentifier algid = new AlgorithmIdentifier(MY_TEST_KEY_OID, "UnknownKey");
+            SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algid, ENCODED_BROKEN);
+            PublicKey pubKey = spki.getPublicKey();
+            assertNotNull(pubKey);
+            assertEquals(MyTestPublicKey.class, pubKey.getClass());
+            byte[] encoded = pubKey.getEncoded();
+            assertEquals(
+                    Arrays.toString(ENCODED_BROKEN),
+                    Arrays.toString(Arrays.copyOfRange(encoded, encoded.length
+                            - ENCODED_BROKEN.length, encoded.length)));
+        } finally {
+            Security.removeProvider(MyTestProvider.NAME);
+        }
+    }
+
+    public static class MyTestProvider extends Provider {
+        public static final String NAME = "MyTestProvider";
+
+        protected MyTestProvider() {
+            super(NAME, 1.0, "MyTestProvider");
+
+            put("KeyFactory." + MY_TEST_KEY_OID, MyTestKeyFactory.class.getName());
+        }
+    }
+
+    public static class MyTestKeyFactory extends KeyFactorySpi {
+        @Override
+        protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+            if (!(keySpec instanceof X509EncodedKeySpec)) {
+                throw new InvalidKeySpecException("Only X509EncodedKeySpec supported");
+            }
+
+            X509EncodedKeySpec x509ks = (X509EncodedKeySpec) keySpec;
+            return new MyTestPublicKey(x509ks.getEncoded());
+        }
+
+        @Override
+        protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
+                throws InvalidKeySpecException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public static class MyTestPublicKey implements PublicKey {
+        private final byte[] data;
+
+        public MyTestPublicKey(byte[] data) {
+            this.data = data;
+        }
+
+        @Override
+        public String getAlgorithm() {
+            return "MyTestPublicKey";
+        }
+
+        @Override
+        public String getFormat() {
+            return null;
+        }
+
+        @Override
+        public byte[] getEncoded() {
+            return data;
+        }
+    }
+}
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/ChainStrengthAnalyzerTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/ChainStrengthAnalyzerTest.java
new file mode 100644
index 0000000..42585b9
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/ChainStrengthAnalyzerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import junit.framework.TestCase;
+
+public class ChainStrengthAnalyzerTest extends TestCase {
+
+    //openssl req -x509 -nodes -days 365 -subj '/C=US/ST=Testsota/L=Testville/CN=test.com' \
+    //-newkey rsa:2048 -sha256 -keyout k.pem -out good.pem
+    private static final String GOOD_PEM = "" +
+                            "-----BEGIN CERTIFICATE-----\n" +
+                            "MIIDYTCCAkmgAwIBAgIJAPFX8KGuEZcgMA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNV\n" +
+                            "BAYTAlVTMREwDwYDVQQIDAhUZXN0c290YTESMBAGA1UEBwwJVGVzdHZpbGxlMREw\n" +
+                            "DwYDVQQDDAh0ZXN0LmNvbTAeFw0xMjEwMTUyMTQ0MTBaFw0xMzEwMTUyMTQ0MTBa\n" +
+                            "MEcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhUZXN0c290YTESMBAGA1UEBwwJVGVz\n" +
+                            "dHZpbGxlMREwDwYDVQQDDAh0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" +
+                            "ADCCAQoCggEBAM44hz3eTINuAIS9OYmg6DkUIj3MItn5dgbcMEdbXrhNpeWY93ho\n" +
+                            "WQFfsqcSSx28NzqKJmnX+cyinzIUfVde/qciP9P7fxRDokRsf34DJ6gXQplz6P2t\n" +
+                            "s4CWjYM+WXJrvEUgLUQ3CBV0CCrtYvG1B9wYsBdAdWkVaMxTvEt7aVxcvJYzp+KU\n" +
+                            "ME7HDg0PVxptvUExIskcqKVmW7i748AgBLhd0r1nFWLuH20d42Aowja0Wi19fWl2\n" +
+                            "SEMErDRjG8jIPUdSoOLPVLGTktEpex51xnAaZ+I7hy6zs55dq8ua/hE/v2cXIkiQ\n" +
+                            "ZXpWyvI/MaKEfeydLnNpa7J3GpH3KW93HQcCAwEAAaNQME4wHQYDVR0OBBYEFA0M\n" +
+                            "RI+3hIPCSpVVArisr3Y3/sheMB8GA1UdIwQYMBaAFA0MRI+3hIPCSpVVArisr3Y3\n" +
+                            "/sheMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFgUNyuy2qaJvgDO\n" +
+                            "plYudTrJR38O3id1B5oKOzgTEgRrfmHHfyloY4fL5gjAGNp7vdlDKSHC2Ebo23/X\n" +
+                            "Wg535MJ2296R855jaTMdkSE0+4ASpdmon1D007H0FhLyojlKVta3pqMAF1zsp0YF\n" +
+                            "Mf3V/rVMDxCOnbSnqAX0+1nW8Qm4Jgrr3AAMafZk6ypq0xuNQn+sUWuIWw3Xv5Jl\n" +
+                            "KehjnuKtMgVYkn2ItRNnUdhm2dQK+Phdb5Yg8WHXN/r9sZQdORg8FQS9TfQJmimB\n" +
+                            "CVYuqA9Dt0JJZPuO/Pd1yAxWP4NpxX1xr3lNQ5jrTO702QA3gOrscluULLzrYR50\n" +
+                            "FoAjeos=\n" +
+                            "-----END CERTIFICATE-----";
+
+    //openssl req -x509 -nodes -days 365 -subj '/C=US/ST=Testsota/L=Testville/CN=test.com' \
+    //-newkey rsa:2048 -md5 -keyout k.pem -out md5.pem
+    private static final String MD5_PEM = "" +
+                            "-----BEGIN CERTIFICATE-----\n" +
+                            "MIIDYTCCAkmgAwIBAgIJAJsffMf2cyx0MA0GCSqGSIb3DQEBBAUAMEcxCzAJBgNV\n" +
+                            "BAYTAlVTMREwDwYDVQQIDAhUZXN0c290YTESMBAGA1UEBwwJVGVzdHZpbGxlMREw\n" +
+                            "DwYDVQQDDAh0ZXN0LmNvbTAeFw0xMjEwMTUyMTQzMzZaFw0xMzEwMTUyMTQzMzZa\n" +
+                            "MEcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhUZXN0c290YTESMBAGA1UEBwwJVGVz\n" +
+                            "dHZpbGxlMREwDwYDVQQDDAh0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" +
+                            "ADCCAQoCggEBAOJyiUwgf/VsdbTTdx6dsb742adeBFBY1FpSWCeQW/JVtdMephbK\n" +
+                            "AA00nu8Xq3dNx9bp8AqvzeyHi/RBsZOtb2eAsOXE3RbFy28ehDTHdG34fRQNT6kp\n" +
+                            "RUHw8wrUGovMVqS8j+iW8HfAy3sjArje0ygz2NIETlNQbEOifAJtY+AEfZwZE0/0\n" +
+                            "IMVP4hwTmIgyReJBDmAx31clwsWZSPar9x+WQfeJ3rfy5LBCtf3RUbdgnvynBHFk\n" +
+                            "FjucwoqgOOXviCWxIa0F+ZAmZJBj5+pLN/V92RXOu0c2fR3Mf68J67OJ+K4ueo1N\n" +
+                            "nBhRsulWMmGqIVjYOZQxiNzWYcOVXj3DTRMCAwEAAaNQME4wHQYDVR0OBBYEFJbY\n" +
+                            "TU06RuJaiMBs2vzx5y0MbaQOMB8GA1UdIwQYMBaAFJbYTU06RuJaiMBs2vzx5y0M\n" +
+                            "baQOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBAFEky0jLTmKefDVX\n" +
+                            "8O84KoupmQ2qQQBaQF3F5GEuhi0qJRwnmsWkCmsxPP55S67WDFp3JH+LX14UxL4T\n" +
+                            "fbG2CXHt/BF1yU3Z8JBwx3bDmfUnUOAFkO3nmByb11FyZTHMzq4jp03DexWREv4q\n" +
+                            "Ai5+5Xb56VECgCH/hnGqhQeFGhlZUcSXobVhAU+39L6azWELXxk1K4bpVxYFGn1N\n" +
+                            "uZ+dWmb6snPKDzG6J5IIX8QIs6G8H6ptj+QNoU/qTcZEnuzMJxpqMsyq10AA+bY/\n" +
+                            "VAYyXeZm3XZrtqYosDeiUdmcL0jjmyQtyOcAoVUQWj1EJuRjXg4BvI6xxRAIPWYT\n" +
+                            "EDeWHJE=\n" +
+                            "-----END CERTIFICATE-----";
+
+    //openssl req -x509 -nodes -days 365 -subj '/C=US/ST=Testsota/L=Testville/CN=test.com' \
+    //-newkey rsa:512 -sha256 -keyout k.pem -out short.pem
+    private static final String SHORT_PEM = "" +
+                            "-----BEGIN CERTIFICATE-----\n" +
+                            "MIIB1zCCAYGgAwIBAgIJAOxaz9TreDNIMA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNV\n" +
+                            "BAYTAlVTMREwDwYDVQQIDAhUZXN0c290YTESMBAGA1UEBwwJVGVzdHZpbGxlMREw\n" +
+                            "DwYDVQQDDAh0ZXN0LmNvbTAeFw0xMjEwMTUyMTQzMjNaFw0xMzEwMTUyMTQzMjNa\n" +
+                            "MEcxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhUZXN0c290YTESMBAGA1UEBwwJVGVz\n" +
+                            "dHZpbGxlMREwDwYDVQQDDAh0ZXN0LmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC\n" +
+                            "QQCoMgxK9HG0L+hXEht1mKq6ApN3+3lmIEVUcWQKL7EMmn9+L6rVSJyOAGwpTVG7\n" +
+                            "eZ5uulC0Lkm5/bzKFSrCf1jlAgMBAAGjUDBOMB0GA1UdDgQWBBTda66RZsgUvR4e\n" +
+                            "2RSsq65K1xcz0jAfBgNVHSMEGDAWgBTda66RZsgUvR4e2RSsq65K1xcz0jAMBgNV\n" +
+                            "HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAZWYgoNDn6yEzcmWgsYnG3w2BT6fL\n" +
+                            "Npi0+APKWkwxnEJk1kgpdeSTMgaHAphQ8qksHnSgeBAJSs2ZCQMinVPgOg==\n" +
+                            "-----END CERTIFICATE-----";
+
+    public void testMD5() throws Exception {
+        assertBad(MD5_PEM, "Weak hash check did not fail as expected");
+    }
+
+    public void test512() throws Exception {
+        assertBad(SHORT_PEM, "Short modulus check did not fail as expected");
+    }
+
+    public void testGoodChain() throws Exception {
+        assertGood(GOOD_PEM);
+    }
+
+    private static void assertBad(String pem, String msg) throws Exception {
+        try {
+            check(createCert(pem));
+            fail(msg);
+        } catch (CertificateException expected) {
+        }
+    }
+
+    private static void assertGood(String pem) throws Exception {
+        check(createCert(pem));
+    }
+
+    private static void check(X509Certificate cert) throws Exception {
+        X509Certificate[] chain = {cert};
+        ChainStrengthAnalyzer.check(chain);
+    }
+
+    private static X509Certificate createCert(String pem) throws Exception {
+        CertificateFactory cf = CertificateFactory.getInstance("X509");
+        InputStream pemInput = new ByteArrayInputStream(pem.getBytes());
+        return (X509Certificate) cf.generateCertificate(pemInput);
+    }
+}
\ No newline at end of file
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 303c234..f456f3e 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
@@ -16,8 +16,12 @@
 
 package org.apache.harmony.xnet.provider.jsse;
 
+import dalvik.system.BaseDexClassLoader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.math.BigInteger;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
@@ -25,10 +29,14 @@
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
 import java.security.KeyStore.PrivateKeyEntry;
+import java.security.PrivateKey;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECPrivateKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -49,8 +57,10 @@
 import static org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_MODE_HANDSHAKE_CUTTHROUGH;
 
 public class NativeCryptoTest extends TestCase {
+    /** Corresponds to the native test library "libjavacoretests.so" */
+    public static final String TEST_ENGINE_ID = "javacoretests";
 
-    private static final int NULL = 0;
+    private static final long NULL = 0;
     private static final FileDescriptor INVALID_FD = new FileDescriptor();
     private static final SSLHandshakeCallbacks DUMMY_CB
             = new TestSSLHandshakeCallbacks(null, 0, null);
@@ -62,6 +72,13 @@
     private static byte[] CLIENT_PRIVATE_KEY;
     private static byte[][] CLIENT_CERTIFICATES;
     private static byte[][] CA_PRINCIPALS;
+    private static PrivateKey CHANNEL_ID_PRIVATE_KEY;
+    private static byte[] CHANNEL_ID;
+
+    @Override
+    protected void tearDown() throws Exception {
+        assertEquals(0, NativeCrypto.ERR_peek_last_error());
+    }
 
     private static byte[] getServerPrivateKey() {
         initCerts();
@@ -114,12 +131,32 @@
             X509Certificate certificate = (X509Certificate) ks.getCertificate(caCertAlias);
             X500Principal principal = certificate.getIssuerX500Principal();
             CA_PRINCIPALS = new byte[][] { principal.getEncoded() };
+            initChannelIdKey();
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
-    public static void assertEqualSessions(int expected, int actual) {
+    private static synchronized void initChannelIdKey() throws Exception {
+        if (CHANNEL_ID_PRIVATE_KEY != null) {
+            return;
+        }
+
+        // NIST P-256 aka SECG secp256r1 aka X9.62 prime256v1
+        OpenSSLECGroupContext openSslSpec = OpenSSLECGroupContext.getCurveByName("prime256v1");
+        BigInteger s = new BigInteger(
+                "229cdbbf489aea584828a261a23f9ff8b0f66f7ccac98bf2096ab3aee41497c5", 16);
+        CHANNEL_ID_PRIVATE_KEY = new OpenSSLECPrivateKey(
+                new ECPrivateKeySpec(s, openSslSpec.getECParameterSpec()));
+
+        // Channel ID is the concatenation of the X and Y coordinates of the public key.
+        CHANNEL_ID = new BigInteger(
+                "702b07871fd7955c320b26f15e244e47eed60272124c92b9ebecf0b42f90069b" +
+                        "ab53592ebfeb4f167dbf3ce61513afb0e354c479b1c1b69874fa471293494f77",
+                16).toByteArray();
+    }
+
+    public static void assertEqualSessions(long expected, long actual) {
         assertEqualByteArrays(NativeCrypto.SSL_SESSION_session_id(expected),
                               NativeCrypto.SSL_SESSION_session_id(actual));
     }
@@ -153,7 +190,7 @@
         KeyPair kp2 = kpg.generateKeyPair();
         RSAPrivateCrtKey privKey2 = (RSAPrivateCrtKey) kp2.getPrivate();
 
-        int pkey1 = 0, pkey1_copy = 0, pkey2 = 0;
+        long pkey1 = 0, pkey1_copy = 0, pkey2 = 0;
         try {
             pkey1 = NativeCrypto.EVP_PKEY_new_RSA(privKey1.getModulus().toByteArray(),
                         privKey1.getPublicExponent().toByteArray(),
@@ -219,9 +256,9 @@
     }
 
     public void test_SSL_CTX_new() throws Exception {
-        int c = NativeCrypto.SSL_CTX_new();
+        long c = NativeCrypto.SSL_CTX_new();
         assertTrue(c != NULL);
-        int c2 = NativeCrypto.SSL_CTX_new();
+        long c2 = NativeCrypto.SSL_CTX_new();
         assertTrue(c != c2);
         NativeCrypto.SSL_CTX_free(c);
         NativeCrypto.SSL_CTX_free(c2);
@@ -244,7 +281,7 @@
             fail();
         } catch (NullPointerException expected) {
         }
-        int c = NativeCrypto.SSL_CTX_new();
+        long c = NativeCrypto.SSL_CTX_new();
         try {
             NativeCrypto.SSL_CTX_set_session_id_context(c, null);
             fail();
@@ -260,8 +297,8 @@
     }
 
     public void test_SSL_new() throws Exception {
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         assertTrue(s != NULL);
         assertTrue((NativeCrypto.SSL_get_options(s) & 0x01000000L) != 0); // SSL_OP_NO_SSLv2
@@ -270,7 +307,7 @@
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_TLSv1_1) == 0);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_TLSv1_2) == 0);
 
-        int s2 = NativeCrypto.SSL_new(c);
+        long s2 = NativeCrypto.SSL_new(c);
         assertTrue(s != s2);
         NativeCrypto.SSL_free(s2);
 
@@ -285,8 +322,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         try {
             NativeCrypto.SSL_use_certificate(s, null);
@@ -300,6 +337,41 @@
         NativeCrypto.SSL_CTX_free(c);
     }
 
+    public void test_SSL_use_PrivateKey_for_tls_channel_id() throws Exception {
+        try {
+            NativeCrypto.SSL_set1_tls_channel_id(NULL, null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+
+        try {
+            NativeCrypto.SSL_set1_tls_channel_id(s, null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+
+        // Use the key via the wrapper that decides whether to use PKCS#8 or native OpenSSL.
+        NativeCrypto.SSL_set1_tls_channel_id(s, CHANNEL_ID_PRIVATE_KEY);
+
+        // Use the key via its PKCS#8 representation.
+        assertEquals("PKCS#8", CHANNEL_ID_PRIVATE_KEY.getFormat());
+        byte[] pkcs8EncodedKeyBytes = CHANNEL_ID_PRIVATE_KEY.getEncoded();
+        assertNotNull(pkcs8EncodedKeyBytes);
+        NativeCrypto.SSL_use_PKCS8_PrivateKey_for_tls_channel_id(s, pkcs8EncodedKeyBytes);
+
+        // Use the key natively. This works because the initChannelIdKey method ensures that the
+        // key is backed by OpenSSL.
+        NativeCrypto.SSL_use_OpenSSL_PrivateKey_for_tls_channel_id(
+                s,
+                ((OpenSSLECPrivateKey) CHANNEL_ID_PRIVATE_KEY).getOpenSSLKey().getPkeyContext());
+
+        NativeCrypto.SSL_free(s);
+        NativeCrypto.SSL_CTX_free(c);
+    }
+
     public void test_SSL_use_PrivateKey() throws Exception {
         try {
             NativeCrypto.SSL_use_PrivateKey(NULL, null);
@@ -307,8 +379,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         try {
             NativeCrypto.SSL_use_PrivateKey(s, null);
@@ -331,8 +403,8 @@
     }
 
     public void test_SSL_check_private_key_no_key_no_cert() throws Exception {
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         // neither private or certificate set
         try {
@@ -346,8 +418,8 @@
     }
 
     public void test_SSL_check_private_key_cert_then_key() throws Exception {
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         // first certificate, then private
         NativeCrypto.SSL_use_certificate(s, getServerCertificates());
@@ -365,8 +437,8 @@
         NativeCrypto.SSL_CTX_free(c);
     }
     public void test_SSL_check_private_key_key_then_cert() throws Exception {
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         // first private, then certificate
         NativeCrypto.SSL_use_PrivateKey(s, getServerPrivateKey());
@@ -391,8 +463,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         assertTrue(NativeCrypto.SSL_get_mode(s) != 0);
         NativeCrypto.SSL_free(s);
         NativeCrypto.SSL_CTX_free(c);
@@ -405,8 +477,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         // check SSL_MODE_HANDSHAKE_CUTTHROUGH off by default
         assertEquals(0, NativeCrypto.SSL_get_mode(s) & SSL_MODE_HANDSHAKE_CUTTHROUGH);
         // set SSL_MODE_HANDSHAKE_CUTTHROUGH on
@@ -429,8 +501,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         assertTrue(NativeCrypto.SSL_get_options(s) != 0);
         NativeCrypto.SSL_free(s);
         NativeCrypto.SSL_CTX_free(c);
@@ -443,8 +515,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_SSLv3) == 0);
         NativeCrypto.SSL_set_options(s, NativeCrypto.SSL_OP_NO_SSLv3);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_SSLv3) != 0);
@@ -459,8 +531,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_SSLv3) == 0);
         NativeCrypto.SSL_set_options(s, NativeCrypto.SSL_OP_NO_SSLv3);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_SSLv3) != 0);
@@ -477,8 +549,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         try {
             NativeCrypto.SSL_set_cipher_lists(s, null);
@@ -527,8 +599,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_NONE);
         NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_PEER);
         NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
@@ -541,19 +613,25 @@
     private static final boolean DEBUG = false;
 
     public static class Hooks {
-        public int getContext() throws SSLException {
+        private PrivateKey channelIdPrivateKey;
+
+        public long getContext() throws SSLException {
             return NativeCrypto.SSL_CTX_new();
         }
-        public int beforeHandshake(int context) throws SSLException {
-            int s = NativeCrypto.SSL_new(context);
+        public long beforeHandshake(long context) throws SSLException {
+            long s = NativeCrypto.SSL_new(context);
             // without this SSL_set_cipher_lists call the tests were
             // negotiating DHE-RSA-AES256-SHA by default which had
             // very slow ephemeral RSA key generation
             NativeCrypto.SSL_set_cipher_lists(s, new String[] { "RC4-MD5" });
+
+            if (channelIdPrivateKey != null) {
+                NativeCrypto.SSL_set1_tls_channel_id(s, channelIdPrivateKey);
+            }
             return s;
         }
-        public void clientCertificateRequested(int s) {}
-        public void afterHandshake(int session, int ssl, int context,
+        public void clientCertificateRequested(long s) {}
+        public void afterHandshake(long session, long ssl, long context,
                                    Socket socket, FileDescriptor fd,
                                    SSLHandshakeCallbacks callback)
                 throws Exception {
@@ -578,11 +656,11 @@
 
     public static class TestSSLHandshakeCallbacks implements SSLHandshakeCallbacks {
         private final Socket socket;
-        private final int sslNativePointer;
+        private final long sslNativePointer;
         private final Hooks hooks;
 
         public TestSSLHandshakeCallbacks(Socket socket,
-                                         int sslNativePointer,
+                                         long sslNativePointer,
                                          Hooks hooks) {
             this.socket = socket;
             this.sslNativePointer = sslNativePointer;
@@ -597,7 +675,7 @@
                                            String authMethod)
                 throws CertificateException {
             if (DEBUG) {
-                System.out.println("ssl=0x" + Integer.toString(sslNativePointer, 16)
+                System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
                                    + " verifyCertificateChain"
                                    + " asn1DerEncodedCertificateChain="
                                    + asn1DerEncodedCertificateChain
@@ -614,7 +692,7 @@
         public void clientCertificateRequested(byte[] keyTypes,
                                                byte[][] asn1DerEncodedX500Principals) {
             if (DEBUG) {
-                System.out.println("ssl=0x" + Integer.toString(sslNativePointer, 16)
+                System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
                                    + " clientCertificateRequested"
                                    + " keyTypes=" + keyTypes
                                    + " asn1DerEncodedX500Principals="
@@ -631,7 +709,7 @@
         public boolean handshakeCompletedCalled;
         public void handshakeCompleted() {
             if (DEBUG) {
-                System.out.println("ssl=0x" + Integer.toString(sslNativePointer, 16)
+                System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
                                    + " handshakeCompleted");
             }
             this.handshakeCompletedCalled = true;
@@ -645,23 +723,46 @@
     public static class ServerHooks extends Hooks {
         private final byte[] privateKey;
         private final byte[][] certificates;
+        private boolean channelIdEnabled;
+        private byte[] channelIdAfterHandshake;
+        private Throwable channelIdAfterHandshakeException;
+
         public ServerHooks(byte[] privateKey, byte[][] certificates) {
             this.privateKey = privateKey;
             this.certificates = certificates;
         }
 
         @Override
-        public int beforeHandshake(int c) throws SSLException {
-            int s = super.beforeHandshake(c);
+        public long beforeHandshake(long c) throws SSLException {
+            long s = super.beforeHandshake(c);
             if (privateKey != null) {
                 NativeCrypto.SSL_use_PrivateKey(s, privateKey);
             }
             if (certificates != null) {
                 NativeCrypto.SSL_use_certificate(s, certificates);
             }
+            if (channelIdEnabled) {
+                NativeCrypto.SSL_enable_tls_channel_id(s);
+            }
             return s;
         }
-        public void clientCertificateRequested(int s) {
+
+        @Override
+        public void afterHandshake(long session, long ssl, long context,
+                                   Socket socket, FileDescriptor fd,
+                                   SSLHandshakeCallbacks callback)
+                throws Exception {
+          if (channelIdEnabled) {
+            try {
+              channelIdAfterHandshake = NativeCrypto.SSL_get_tls_channel_id(ssl);
+            } catch (Exception e) {
+              channelIdAfterHandshakeException = e;
+            }
+          }
+          super.afterHandshake(session, ssl, context, socket, fd, callback);
+        }
+
+        public void clientCertificateRequested(long s) {
             fail("Server asked for client certificates");
         }
     }
@@ -680,27 +781,27 @@
                     return new TestSSLHandshakeCallbacks(socket, 0, null);
                 }
                 FileDescriptor fd = socket.getFileDescriptor$();
-                int c = hooks.getContext();
-                int s = hooks.beforeHandshake(c);
+                long c = hooks.getContext();
+                long s = hooks.beforeHandshake(c);
                 TestSSLHandshakeCallbacks callback
                         = new TestSSLHandshakeCallbacks(socket, s, hooks);
                 if (DEBUG) {
-                    System.out.println("ssl=0x" + Integer.toString(s, 16)
+                    System.out.println("ssl=0x" + Long.toString(s, 16)
                                        + " handshake"
-                                       + " context=0x" + Integer.toString(c, 16)
+                                       + " context=0x" + Long.toString(c, 16)
                                        + " socket=" + socket
                                        + " fd=" + fd
                                        + " timeout=" + timeout
                                        + " client=" + client);
                 }
-                int session = NULL;
+                long 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)
+                        System.out.println("ssl=0x" + Long.toString(s, 16)
                                            + " handshake"
-                                           + " session=0x" + Integer.toString(session, 16));
+                                           + " session=0x" + Long.toString(session, 16));
                     }
                 } finally {
                     // Ensure afterHandshake is called to free resources
@@ -722,8 +823,8 @@
     }
 
     public void test_SSL_do_handshake_null_args() throws Exception {
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
 
         try {
             NativeCrypto.SSL_do_handshake(s, null, null, 0, true, null);
@@ -767,7 +868,7 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void clientCertificateRequested(int s) {
+            public void clientCertificateRequested(long s) {
                 super.clientCertificateRequested(s);
                 NativeCrypto.SSL_use_PrivateKey(s, getClientPrivateKey());
                 NativeCrypto.SSL_use_certificate(s, getClientCertificates());
@@ -775,8 +876,8 @@
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
             @Override
-            public int beforeHandshake(int c) throws SSLException {
-                int s = super.beforeHandshake(c);
+            public long beforeHandshake(long c) throws SSLException {
+                long s = super.beforeHandshake(c);
                 NativeCrypto.SSL_set_client_CA_list(s, getCaPrincipals());
                 NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_PEER);
                 return s;
@@ -818,8 +919,8 @@
             Hooks cHooks = new Hooks();
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
                 @Override
-                public int beforeHandshake(int c) throws SSLException {
-                    int s = super.beforeHandshake(c);
+                public long beforeHandshake(long c) throws SSLException {
+                    long s = super.beforeHandshake(c);
                     NativeCrypto.SSL_set_client_CA_list(s, getCaPrincipals());
                     NativeCrypto.SSL_set_verify(s,
                                                 NativeCrypto.SSL_VERIFY_PEER
@@ -851,13 +952,13 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public int beforeHandshake(int context) throws SSLException {
-                int s = super.beforeHandshake(context);
+            public long beforeHandshake(long context) throws SSLException {
+                long s = super.beforeHandshake(context);
                 NativeCrypto.SSL_clear_mode(s, SSL_MODE_HANDSHAKE_CUTTHROUGH);
                 return s;
             }
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -866,14 +967,14 @@
                 super.afterHandshake(session, s, c, sock, fd, callback);
             }
             @Override
-            public void clientCertificateRequested(int s) {
+            public void clientCertificateRequested(long s) {
                 super.clientCertificateRequested(s);
                 throw new RuntimeException("expected");
             }
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -945,6 +1046,84 @@
         }
     }
 
+    public void test_SSL_do_handshake_with_channel_id_normal() throws Exception {
+        initChannelIdKey();
+
+        // Normal handshake with TLS Channel ID.
+        final ServerSocket listener = new ServerSocket(0);
+        Hooks cHooks = new Hooks();
+        cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
+        ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
+        sHooks.channelIdEnabled = true;
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null);
+        Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null);
+        TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue(clientCallback.verifyCertificateChainCalled);
+        assertEqualCertificateChains(getServerCertificates(),
+                                     clientCallback.asn1DerEncodedCertificateChain);
+        assertEquals("RSA", clientCallback.authMethod);
+        assertFalse(serverCallback.verifyCertificateChainCalled);
+        assertFalse(clientCallback.clientCertificateRequestedCalled);
+        assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.handshakeCompletedCalled);
+        assertTrue(serverCallback.handshakeCompletedCalled);
+        assertNull(sHooks.channelIdAfterHandshakeException);
+        assertEqualByteArrays(CHANNEL_ID, sHooks.channelIdAfterHandshake);
+    }
+
+    public void test_SSL_do_handshake_with_channel_id_not_supported_by_server() throws Exception {
+        initChannelIdKey();
+
+        // Client tries to use TLS Channel ID but the server does not enable/offer the extension.
+        final ServerSocket listener = new ServerSocket(0);
+        Hooks cHooks = new Hooks();
+        cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
+        ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
+        sHooks.channelIdEnabled = false;
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null);
+        Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null);
+        TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue(clientCallback.verifyCertificateChainCalled);
+        assertEqualCertificateChains(getServerCertificates(),
+                                     clientCallback.asn1DerEncodedCertificateChain);
+        assertEquals("RSA", clientCallback.authMethod);
+        assertFalse(serverCallback.verifyCertificateChainCalled);
+        assertFalse(clientCallback.clientCertificateRequestedCalled);
+        assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.handshakeCompletedCalled);
+        assertTrue(serverCallback.handshakeCompletedCalled);
+        assertNull(sHooks.channelIdAfterHandshakeException);
+        assertNull(sHooks.channelIdAfterHandshake);
+    }
+
+    public void test_SSL_do_handshake_with_channel_id_not_enabled_by_client() throws Exception {
+        initChannelIdKey();
+
+        // Client does not use TLS Channel ID when the server has the extension enabled/offered.
+        final ServerSocket listener = new ServerSocket(0);
+        Hooks cHooks = new Hooks();
+        cHooks.channelIdPrivateKey = null;
+        ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
+        sHooks.channelIdEnabled = true;
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null);
+        Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null);
+        TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue(clientCallback.verifyCertificateChainCalled);
+        assertEqualCertificateChains(getServerCertificates(),
+                                     clientCallback.asn1DerEncodedCertificateChain);
+        assertEquals("RSA", clientCallback.authMethod);
+        assertFalse(serverCallback.verifyCertificateChainCalled);
+        assertFalse(clientCallback.clientCertificateRequestedCalled);
+        assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.handshakeCompletedCalled);
+        assertTrue(serverCallback.handshakeCompletedCalled);
+        assertNull(sHooks.channelIdAfterHandshakeException);
+        assertNull(sHooks.channelIdAfterHandshake);
+    }
+
     public void test_SSL_set_session() throws Exception {
         try {
             NativeCrypto.SSL_set_session(NULL, NULL);
@@ -953,27 +1132,27 @@
         }
 
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             NativeCrypto.SSL_set_session(s, NULL);
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
         }
 
         {
-            final int clientContext = NativeCrypto.SSL_CTX_new();
-            final int serverContext = NativeCrypto.SSL_CTX_new();
+            final long clientContext = NativeCrypto.SSL_CTX_new();
+            final long serverContext = NativeCrypto.SSL_CTX_new();
             final ServerSocket listener = new ServerSocket(0);
-            final int[] clientSession = new int[] { NULL };
-            final int[] serverSession = new int[] { NULL };
+            final long[] clientSession = new long[] { NULL };
+            final long[] serverSession = new long[] { NULL };
             {
                 Hooks cHooks = new Hooks() {
                     @Override
-                    public int getContext() throws SSLException {
+                    public long getContext() throws SSLException {
                         return clientContext;
                     }
                     @Override
-                    public void afterHandshake(int session, int s, int c,
+                    public void afterHandshake(long session, long s, long c,
                                                Socket sock, FileDescriptor fd,
                                                SSLHandshakeCallbacks callback)
                             throws Exception {
@@ -983,11 +1162,11 @@
                 };
                 Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
                     @Override
-                    public int getContext() throws SSLException {
+                    public long getContext() throws SSLException {
                         return serverContext;
                     }
                     @Override
-                    public void afterHandshake(int session, int s, int c,
+                    public void afterHandshake(long session, long s, long c,
                                                Socket sock, FileDescriptor fd,
                                                SSLHandshakeCallbacks callback)
                             throws Exception {
@@ -1006,17 +1185,17 @@
             {
                 Hooks cHooks = new Hooks() {
                     @Override
-                    public int getContext() throws SSLException {
+                    public long getContext() throws SSLException {
                         return clientContext;
                     }
                     @Override
-                    public int beforeHandshake(int c) throws SSLException {
-                        int s = NativeCrypto.SSL_new(clientContext);
+                    public long beforeHandshake(long c) throws SSLException {
+                        long s = NativeCrypto.SSL_new(clientContext);
                         NativeCrypto.SSL_set_session(s, clientSession[0]);
                         return s;
                     }
                     @Override
-                    public void afterHandshake(int session, int s, int c,
+                    public void afterHandshake(long session, long s, long c,
                                                Socket sock, FileDescriptor fd,
                                                SSLHandshakeCallbacks callback)
                             throws Exception {
@@ -1026,11 +1205,11 @@
                 };
                 Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
                     @Override
-                    public int getContext() throws SSLException {
+                    public long getContext() throws SSLException {
                         return serverContext;
                     }
                     @Override
-                    public void afterHandshake(int session, int s, int c,
+                    public void afterHandshake(long session, long s, long c,
                                                Socket sock, FileDescriptor fd,
                                                SSLHandshakeCallbacks callback)
                             throws Exception {
@@ -1060,8 +1239,8 @@
         }
 
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             NativeCrypto.SSL_set_session_creation_enabled(s, false);
             NativeCrypto.SSL_set_session_creation_enabled(s, true);
             NativeCrypto.SSL_free(s);
@@ -1074,8 +1253,8 @@
         try {
             Hooks cHooks = new Hooks() {
                 @Override
-                public int beforeHandshake(int c) throws SSLException {
-                    int s = super.beforeHandshake(c);
+                public long beforeHandshake(long c) throws SSLException {
+                    long s = super.beforeHandshake(c);
                     NativeCrypto.SSL_set_session_creation_enabled(s, false);
                     return s;
                 }
@@ -1094,8 +1273,8 @@
             Hooks cHooks = new Hooks();
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
                 @Override
-                public int beforeHandshake(int c) throws SSLException {
-                    int s = super.beforeHandshake(c);
+                public long beforeHandshake(long c) throws SSLException {
+                    long s = super.beforeHandshake(c);
                     NativeCrypto.SSL_set_session_creation_enabled(s, false);
                     return s;
                 }
@@ -1120,8 +1299,8 @@
         final String hostname = "www.android.com";
 
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
 
             // null hostname
             try {
@@ -1152,15 +1331,15 @@
         // normal
         Hooks cHooks = new Hooks() {
             @Override
-            public int beforeHandshake(int c) throws SSLException {
-                int s = super.beforeHandshake(c);
+            public long beforeHandshake(long c) throws SSLException {
+                long s = super.beforeHandshake(c);
                 NativeCrypto.SSL_set_tlsext_host_name(s, hostname);
                 return s;
             }
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1187,11 +1366,11 @@
         };
 
         Hooks cHooks = new Hooks() {
-            @Override public int beforeHandshake(int context) throws SSLException {
+            @Override public long beforeHandshake(long context) throws SSLException {
                 NativeCrypto.SSL_CTX_enable_npn(context);
                 return super.beforeHandshake(context);
             }
-            @Override public void afterHandshake(int session, int ssl, int context, Socket socket,
+            @Override public void afterHandshake(long session, long ssl, long context, Socket socket,
                     FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception {
                 byte[] negotiated = NativeCrypto.SSL_get_npn_negotiated_protocol(ssl);
                 assertEquals("spdy/2", new String(negotiated));
@@ -1201,11 +1380,11 @@
             }
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
-            @Override public int beforeHandshake(int context) throws SSLException {
+            @Override public long beforeHandshake(long context) throws SSLException {
                 NativeCrypto.SSL_CTX_enable_npn(context);
                 return super.beforeHandshake(context);
             }
-            @Override public void afterHandshake(int session, int ssl, int c, Socket sock,
+            @Override public void afterHandshake(long session, long ssl, long c, Socket sock,
                     FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception {
                 byte[] negotiated = NativeCrypto.SSL_get_npn_negotiated_protocol(ssl);
                 assertEquals("spdy/2", new String(negotiated));
@@ -1232,8 +1411,8 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         assertNull(NativeCrypto.SSL_get_servername(s));
         NativeCrypto.SSL_free(s);
         NativeCrypto.SSL_CTX_free(c);
@@ -1251,7 +1430,7 @@
         final ServerSocket listener = new ServerSocket(0);
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1263,7 +1442,7 @@
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                 throws Exception {
@@ -1288,7 +1467,7 @@
         final ServerSocket listener = new ServerSocket(0);
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                 throws Exception {
@@ -1298,7 +1477,7 @@
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1325,7 +1504,7 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1354,8 +1533,8 @@
 
         // null FileDescriptor
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_read(s, null, DUMMY_CB, null, 0, 0, 0);
                 fail();
@@ -1367,8 +1546,8 @@
 
         // null SSLHandshakeCallbacks
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_read(s, INVALID_FD, null, null, 0, 0, 0);
                 fail();
@@ -1380,8 +1559,8 @@
 
         // null byte array
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, null, 0, 0, 0);
                 fail();
@@ -1393,8 +1572,8 @@
 
         // handshaking not yet performed
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_read(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
                 fail();
@@ -1410,7 +1589,7 @@
         {
             Hooks cHooks = new Hooks() {
                 @Override
-                public void afterHandshake(int session, int s, int c,
+                public void afterHandshake(long session, long s, long c,
                                            Socket sock, FileDescriptor fd,
                                            SSLHandshakeCallbacks callback)
                         throws Exception {
@@ -1431,7 +1610,7 @@
             };
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
                 @Override
-                public void afterHandshake(int session, int s, int c,
+                public void afterHandshake(long session, long s, long c,
                                            Socket sock, FileDescriptor fd,
                                            SSLHandshakeCallbacks callback)
                         throws Exception {
@@ -1449,7 +1628,7 @@
         try {
             Hooks cHooks = new Hooks() {
                 @Override
-                public void afterHandshake(int session, int s, int c,
+                public void afterHandshake(long session, long s, long c,
                                            Socket sock, FileDescriptor fd,
                                            SSLHandshakeCallbacks callback)
                         throws Exception {
@@ -1459,7 +1638,7 @@
             };
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
                 @Override
-                public void afterHandshake(int session, int s, int c,
+                public void afterHandshake(long session, long s, long c,
                                            Socket sock, FileDescriptor fd,
                                            SSLHandshakeCallbacks callback)
                         throws Exception {
@@ -1485,8 +1664,8 @@
 
         // null FileDescriptor
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1, 0);
                 fail();
@@ -1498,8 +1677,8 @@
 
         // null SSLHandshakeCallbacks
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1, 0);
                 fail();
@@ -1511,8 +1690,8 @@
 
         // null byte array
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1, 0);
                 fail();
@@ -1524,8 +1703,8 @@
 
         // handshaking not yet performed
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             try {
                 NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0);
                 fail();
@@ -1544,8 +1723,8 @@
 
         // also works without handshaking
         {
-            int c = NativeCrypto.SSL_CTX_new();
-            int s = NativeCrypto.SSL_new(c);
+            long c = NativeCrypto.SSL_CTX_new();
+            long s = NativeCrypto.SSL_new(c);
             NativeCrypto.SSL_interrupt(s);
             NativeCrypto.SSL_free(s);
             NativeCrypto.SSL_CTX_free(c);
@@ -1555,7 +1734,7 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1565,7 +1744,7 @@
         };
         Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates()) {
             @Override
-            public void afterHandshake(int session, final int s, int c,
+            public void afterHandshake(long session, final long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1606,8 +1785,8 @@
         NativeCrypto.SSL_shutdown(NULL, INVALID_FD, DUMMY_CB);
 
         // handshaking not yet performed
-        int c = NativeCrypto.SSL_CTX_new();
-        int s = NativeCrypto.SSL_new(c);
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
         try {
             NativeCrypto.SSL_shutdown(s, INVALID_FD, DUMMY_CB);
         } catch (SSLProtocolException expected) {
@@ -1626,7 +1805,7 @@
         } catch (NullPointerException expected) {
         }
 
-        int c = NativeCrypto.SSL_CTX_new();
+        long c = NativeCrypto.SSL_CTX_new();
         NativeCrypto.SSL_free(NativeCrypto.SSL_new(c));
         NativeCrypto.SSL_CTX_free(c);
 
@@ -1645,7 +1824,7 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1674,7 +1853,7 @@
         {
             Hooks cHooks = new Hooks() {
                 @Override
-                public void afterHandshake(int session, int s, int c,
+                public void afterHandshake(long session, long s, long c,
                                            Socket sock, FileDescriptor fd,
                                            SSLHandshakeCallbacks callback)
                         throws Exception {
@@ -1703,7 +1882,7 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
@@ -1730,7 +1909,7 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                         throws Exception {
@@ -1768,13 +1947,13 @@
 
         Hooks cHooks = new Hooks() {
             @Override
-            public void afterHandshake(int session, int s, int c,
+            public void afterHandshake(long session, long s, long c,
                                        Socket sock, FileDescriptor fd,
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
                 byte[] b = NativeCrypto.i2d_SSL_SESSION(session);
                 assertNotNull(b);
-                int session2 = NativeCrypto.d2i_SSL_SESSION(b);
+                long session2 = NativeCrypto.d2i_SSL_SESSION(b);
                 assertTrue(session2 != NULL);
 
                 // Make sure d2i_SSL_SESSION retores SSL_SESSION_cipher value http://b/7091840
@@ -1818,16 +1997,99 @@
     public void test_ENGINE_by_id_Failure() throws Exception {
         NativeCrypto.ENGINE_load_dynamic();
 
+        long engine = NativeCrypto.ENGINE_by_id("non-existent");
+        if (engine != 0) {
+            NativeCrypto.ENGINE_finish(engine);
+            fail("should not acquire reference to non-existent engine");
+        }
+    }
+
+    /**
+     * Loads the test OpenSSL ENGINE. If it's already loaded, returns
+     * immediately.
+     */
+    public static void loadTestEngine() throws Exception {
+        long testEngine = NativeCrypto.ENGINE_by_id(TEST_ENGINE_ID);
+        if (testEngine != 0) {
+            NativeCrypto.ENGINE_finish(testEngine);
+            return;
+        }
+
+        NativeCrypto.ENGINE_load_dynamic();
+        long dynEngine = NativeCrypto.ENGINE_by_id("dynamic");
         try {
-            int engine = NativeCrypto.ENGINE_by_id("non-existent");
-            fail("Shouldn't load non-existent engine");
-        } catch (RuntimeException e) {
-            // Success
+            ClassLoader loader = NativeCryptoTest.class.getClassLoader();
+
+            final String libraryPaths;
+            if (loader instanceof BaseDexClassLoader) {
+                libraryPaths = ((BaseDexClassLoader) loader).getLdLibraryPath();
+            } else {
+                libraryPaths = System.getProperty("java.library.path");
+            }
+            assertNotNull(libraryPaths);
+
+            String[] libraryPathArray = libraryPaths.split(":");
+            for (String path : libraryPathArray) {
+                assertEquals(1, NativeCrypto.ENGINE_ctrl_cmd_string(dynEngine, "DIR_ADD", path, 0));
+            }
+
+            // We must add this to the list of ENGINEs
+            assertEquals(1, NativeCrypto.ENGINE_ctrl_cmd_string(dynEngine, "LIST_ADD", "2", 0));
+
+            // Do a direct load of the ENGINE.
+            assertEquals(1,
+                    NativeCrypto.ENGINE_ctrl_cmd_string(dynEngine, "ID", TEST_ENGINE_ID, 0));
+            assertEquals(1, NativeCrypto.ENGINE_ctrl_cmd_string(dynEngine, "LOAD", null, 0));
+        } finally {
+            NativeCrypto.ENGINE_finish(dynEngine);
+        }
+
+        testEngine = NativeCrypto.ENGINE_by_id(TEST_ENGINE_ID);
+        if (testEngine == 0) {
+            fail("could not load test engine");
+        }
+        NativeCrypto.ENGINE_finish(testEngine);
+    }
+
+    public void test_ENGINE_by_id_TestEngine() throws Exception {
+        loadTestEngine();
+
+        long engine = NativeCrypto.ENGINE_by_id(TEST_ENGINE_ID);
+        assertTrue(engine != 0);
+        NativeCrypto.ENGINE_add(engine);
+
+        long pkey = NULL;
+        try {
+            final String rsaPem =
+                      "-----BEGIN RSA PRIVATE KEY-----\n"
+                    + "MIICXAIBAAKBgQCvvsYz1VKhU9PT0NHlotX22tcCjeaiVFNg0JrkjoK2XuMb+7a6\n"
+                    + "R5bzgIr24+OnBB0LqgaKnHwxZTA73lo/Wy/Ms5Kvg4yX9UMkNE+PvH5vzcQBbFdI\n"
+                    + "lwETFPvFokHO5OyOcEY+iVWG2fDloteH2JsrKYLh9Sx3Br5pHFCCm5qT5wIDAQAB\n"
+                    + "AoGAWDxoNs371pPH3qkROUIwOuhU2ytziDzeP9V8bxQ9/GJXlE0kyRH4b/kxzBNO\n"
+                    + "0SP3kUukTSOUFxi+xtA0b2rQ7Be2txtjzW1TGOHSCWbFrJAdTqeBcmQJSaZay8n1\n"
+                    + "LOpk4/zvBl7VScBth1IgXP44v6lOzthsrDhMlUYs07ymwYECQQDonaLOhkmVThPa\n"
+                    + "CIThdE5CN/wF5UDzGOz+ZBz3dt8D8QQMu0aZaPzibq9BC462j/fWeWS5OFzbq2+T\n"
+                    + "+cor3nwPAkEAwWmTQdra6GMPEc40zNsM5ehF2FjOpX8aU8267eG56y0Y+GbHx2BN\n"
+                    + "zAHfPxGBBH8cZ0cLhk4RSo/po7Vv+cRyqQJAAQz1N0mT+4Cmxk1TjFEiKVpnYP9w\n"
+                    + "E6kBKQT6vINk7negNQ6Dex3mRn+Jexm6Q0jTLbzOn6eJg9R6ZIi0SQ5wMQJAKX2n\n"
+                    + "fGohqdaORgiRZRzcsHlaemXatsAEetPYdO2Gf7/l6mvKEahEKC6CoLn1jmxiQHmK\n"
+                    + "LF6U8QTcXyUuB0uwOQJBAIwWWjQGGc2sAQ1HW0C2wwCQbWneeBkiRBedonDBHtiB\n"
+                    + "Wz0zS2CMCtBPNeHQmmsXH2Ca+ADdh53sKTuperLiuiw=\n"
+                    + "-----END RSA PRIVATE KEY-----";
+            pkey = NativeCrypto.ENGINE_load_private_key(engine, rsaPem);
+            assertTrue(pkey != 0);
+        } finally {
+            if (pkey != NULL) {
+                NativeCrypto.EVP_PKEY_free(pkey);
+            }
+
+            NativeCrypto.ENGINE_free(engine);
+            NativeCrypto.ENGINE_finish(engine);
         }
     }
 
     public void test_RAND_bytes_Success() throws Exception {
-        byte[] output = new byte[32];
+        byte[] output = new byte[128];
         NativeCrypto.RAND_bytes(output);
 
         boolean isZero = true;
@@ -1835,7 +2097,7 @@
             isZero &= (output[i] == 0);
         }
 
-        assertFalse("Random output was zero. This is a very low probability event "
+        assertFalse("Random output was zero. This is a very low probability event (1 in 2^128) "
                 + "and probably indicates an error.", isZero);
     }
 
@@ -1844,6 +2106,468 @@
         try {
             NativeCrypto.RAND_bytes(output);
             fail("Should be an error on null buffer input");
-        } catch (RuntimeException success) { }
+        } catch (RuntimeException expected) {
+        }
+    }
+
+    public void test_EVP_get_digestbyname() throws Exception {
+        assertTrue(NativeCrypto.EVP_get_digestbyname("sha256") != NULL);
+
+        try {
+            NativeCrypto.EVP_get_digestbyname(null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            NativeCrypto.EVP_get_digestbyname("");
+            NativeCrypto.EVP_get_digestbyname("foobar");
+            fail();
+        } catch (RuntimeException expected) {
+        }
+    }
+
+    public void test_EVP_SignInit() throws Exception {
+        final long ctx = NativeCrypto.EVP_SignInit("RSA-SHA256");
+        assertTrue(ctx != NULL);
+        NativeCrypto.EVP_MD_CTX_destroy(ctx);
+
+        try {
+            NativeCrypto.EVP_SignInit("foobar");
+            fail();
+        } catch (RuntimeException expected) {
+        }
+    }
+
+    public void test_get_RSA_private_params() throws Exception {
+        try {
+            NativeCrypto.get_RSA_private_params(NULL);
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            NativeCrypto.get_RSA_private_params(NULL);
+        } catch (NullPointerException expected) {
+        }
+
+        // Test getting params for the wrong kind of key.
+        final byte[] seed = new byte[20];
+        long ctx = 0;
+        try {
+            ctx = NativeCrypto.DSA_generate_key(2048, seed, dsa2048_g, dsa2048_p, dsa2048_q);
+            assertTrue(ctx != NULL);
+            try {
+                NativeCrypto.get_RSA_private_params(ctx);
+                fail();
+            } catch (RuntimeException expected) {
+            }
+        } finally {
+            if (ctx != 0) {
+                NativeCrypto.EVP_PKEY_free(ctx);
+            }
+        }
+    }
+
+    public void test_get_RSA_public_params() throws Exception {
+        try {
+            NativeCrypto.get_RSA_public_params(NULL);
+        } catch (NullPointerException expected) {
+        }
+
+        try {
+            NativeCrypto.get_RSA_public_params(NULL);
+        } catch (NullPointerException expected) {
+        }
+
+        // Test getting params for the wrong kind of key.
+        final byte[] seed = new byte[20];
+        long ctx = 0;
+        try {
+            ctx = NativeCrypto.DSA_generate_key(2048, seed, dsa2048_g, dsa2048_p, dsa2048_q);
+            assertTrue(ctx != NULL);
+            try {
+                NativeCrypto.get_RSA_public_params(ctx);
+                fail();
+            } catch (RuntimeException expected) {
+            }
+        } finally {
+            if (ctx != 0) {
+                NativeCrypto.EVP_PKEY_free(ctx);
+            }
+        }
+    }
+
+    final byte[] dsa2048_p = {
+            (byte) 0xC3, (byte) 0x16, (byte) 0xD4, (byte) 0xBA, (byte) 0xDC, (byte) 0x0E,
+            (byte) 0xB8, (byte) 0xFC, (byte) 0x40, (byte) 0xDB, (byte) 0xB0, (byte) 0x76,
+            (byte) 0x47, (byte) 0xB8, (byte) 0x8D, (byte) 0xC1, (byte) 0xF1, (byte) 0xAB,
+            (byte) 0x9B, (byte) 0x80, (byte) 0x9D, (byte) 0xDC, (byte) 0x55, (byte) 0x33,
+            (byte) 0xEC, (byte) 0xB6, (byte) 0x09, (byte) 0x8F, (byte) 0xB7, (byte) 0xD9,
+            (byte) 0xA5, (byte) 0x7F, (byte) 0xC1, (byte) 0xE3, (byte) 0xAD, (byte) 0xE1,
+            (byte) 0x7A, (byte) 0x58, (byte) 0xF4, (byte) 0x2D, (byte) 0xB9, (byte) 0x61,
+            (byte) 0xCF, (byte) 0x5B, (byte) 0xCA, (byte) 0x41, (byte) 0x9F, (byte) 0x73,
+            (byte) 0x8D, (byte) 0x81, (byte) 0x62, (byte) 0xD2, (byte) 0x19, (byte) 0x7D,
+            (byte) 0x18, (byte) 0xDB, (byte) 0xB3, (byte) 0x04, (byte) 0xE7, (byte) 0xB2,
+            (byte) 0x28, (byte) 0x59, (byte) 0x14, (byte) 0x73, (byte) 0x43, (byte) 0xF1,
+            (byte) 0x45, (byte) 0xC7, (byte) 0x47, (byte) 0xCC, (byte) 0xD1, (byte) 0x12,
+            (byte) 0x8E, (byte) 0x19, (byte) 0x00, (byte) 0x2C, (byte) 0xD0, (byte) 0x86,
+            (byte) 0x54, (byte) 0x64, (byte) 0x2D, (byte) 0x42, (byte) 0x6C, (byte) 0x6B,
+            (byte) 0x5C, (byte) 0x2D, (byte) 0x4D, (byte) 0x97, (byte) 0x6A, (byte) 0x1D,
+            (byte) 0x89, (byte) 0xB1, (byte) 0x2C, (byte) 0xA0, (byte) 0x05, (byte) 0x2B,
+            (byte) 0x3C, (byte) 0xDB, (byte) 0x1F, (byte) 0x89, (byte) 0x03, (byte) 0x03,
+            (byte) 0x92, (byte) 0x63, (byte) 0xB6, (byte) 0x08, (byte) 0x32, (byte) 0x50,
+            (byte) 0xB2, (byte) 0x54, (byte) 0xA3, (byte) 0xFE, (byte) 0x6C, (byte) 0x35,
+            (byte) 0x17, (byte) 0x2F, (byte) 0x7F, (byte) 0x54, (byte) 0xA4, (byte) 0xAE,
+            (byte) 0x96, (byte) 0x1E, (byte) 0x31, (byte) 0x83, (byte) 0xF1, (byte) 0x3F,
+            (byte) 0x9E, (byte) 0xB9, (byte) 0x5D, (byte) 0xD3, (byte) 0xA9, (byte) 0xCB,
+            (byte) 0xE5, (byte) 0x2F, (byte) 0xBC, (byte) 0xA4, (byte) 0x1A, (byte) 0x31,
+            (byte) 0x41, (byte) 0x91, (byte) 0x2C, (byte) 0xA0, (byte) 0xF4, (byte) 0x83,
+            (byte) 0xAC, (byte) 0xD5, (byte) 0xBA, (byte) 0x3D, (byte) 0x19, (byte) 0xED,
+            (byte) 0xF1, (byte) 0x6C, (byte) 0xD9, (byte) 0x3F, (byte) 0x30, (byte) 0xDA,
+            (byte) 0x80, (byte) 0x06, (byte) 0x56, (byte) 0x3A, (byte) 0x8C, (byte) 0x74,
+            (byte) 0x63, (byte) 0xF2, (byte) 0xED, (byte) 0x1E, (byte) 0xE3, (byte) 0x86,
+            (byte) 0x95, (byte) 0x64, (byte) 0x2A, (byte) 0xC4, (byte) 0x5F, (byte) 0xB2,
+            (byte) 0x64, (byte) 0x40, (byte) 0x9D, (byte) 0xA6, (byte) 0xB8, (byte) 0xF5,
+            (byte) 0x84, (byte) 0x03, (byte) 0x2E, (byte) 0x4A, (byte) 0x7A, (byte) 0x1A,
+            (byte) 0xB0, (byte) 0x0E, (byte) 0xBA, (byte) 0xB1, (byte) 0xF5, (byte) 0xD2,
+            (byte) 0xE7, (byte) 0x65, (byte) 0xCE, (byte) 0xEE, (byte) 0x2C, (byte) 0x7C,
+            (byte) 0x68, (byte) 0x20, (byte) 0x50, (byte) 0x53, (byte) 0x0F, (byte) 0x60,
+            (byte) 0x92, (byte) 0x81, (byte) 0xC0, (byte) 0x2C, (byte) 0x2A, (byte) 0xEA,
+            (byte) 0xE9, (byte) 0xB3, (byte) 0x2A, (byte) 0x81, (byte) 0xDA, (byte) 0x0F,
+            (byte) 0xBB, (byte) 0xFA, (byte) 0x5B, (byte) 0x47, (byte) 0xDA, (byte) 0x57,
+            (byte) 0x4E, (byte) 0xFC, (byte) 0x05, (byte) 0x2C, (byte) 0x6A, (byte) 0x90,
+            (byte) 0xA0, (byte) 0x99, (byte) 0x88, (byte) 0x71, (byte) 0x8A, (byte) 0xCC,
+            (byte) 0xD2, (byte) 0x97, (byte) 0x11, (byte) 0xB1, (byte) 0xCE, (byte) 0xF7,
+            (byte) 0x47, (byte) 0x53, (byte) 0x53, (byte) 0x68, (byte) 0xE1, (byte) 0x2A,
+            (byte) 0x56, (byte) 0xD5, (byte) 0x3D, (byte) 0xDF, (byte) 0x08, (byte) 0x16,
+            (byte) 0x1F, (byte) 0xAA, (byte) 0x54, (byte) 0x15,
+    };
+
+    final byte[] dsa2048_q = {
+            (byte) 0xAA, (byte) 0xDD, (byte) 0xE2, (byte) 0xCE, (byte) 0x08, (byte) 0xC0,
+            (byte) 0x0E, (byte) 0x91, (byte) 0x8C, (byte) 0xD9, (byte) 0xBC, (byte) 0x1E,
+            (byte) 0x05, (byte) 0x70, (byte) 0x07, (byte) 0x3B, (byte) 0xB5, (byte) 0xA9,
+            (byte) 0xB5, (byte) 0x8B, (byte) 0x21, (byte) 0x68, (byte) 0xA2, (byte) 0x76,
+            (byte) 0x53, (byte) 0x1E, (byte) 0x68, (byte) 0x1B, (byte) 0x4F, (byte) 0x88,
+            (byte) 0x6D, (byte) 0xCF,
+    };
+
+    final byte[] dsa2048_g = {
+            (byte) 0x6B, (byte) 0x4D, (byte) 0x21, (byte) 0x92, (byte) 0x24, (byte) 0x76,
+            (byte) 0xE5, (byte) 0xA2, (byte) 0xCE, (byte) 0x02, (byte) 0x85, (byte) 0x32,
+            (byte) 0x73, (byte) 0x70, (byte) 0xFF, (byte) 0xB9, (byte) 0xD4, (byte) 0x51,
+            (byte) 0xBA, (byte) 0x22, (byte) 0x8B, (byte) 0x75, (byte) 0x29, (byte) 0xE3,
+            (byte) 0xF2, (byte) 0x2E, (byte) 0x20, (byte) 0xF5, (byte) 0x6A, (byte) 0xD9,
+            (byte) 0x75, (byte) 0xA0, (byte) 0xC0, (byte) 0x3B, (byte) 0x12, (byte) 0x2F,
+            (byte) 0x4F, (byte) 0x9A, (byte) 0xF8, (byte) 0x5D, (byte) 0x45, (byte) 0xC5,
+            (byte) 0x80, (byte) 0x6C, (byte) 0x9B, (byte) 0x56, (byte) 0xBE, (byte) 0x8E,
+            (byte) 0x40, (byte) 0xF9, (byte) 0x0A, (byte) 0xF0, (byte) 0x3D, (byte) 0xD7,
+            (byte) 0x7C, (byte) 0xDE, (byte) 0x22, (byte) 0x10, (byte) 0x24, (byte) 0xCC,
+            (byte) 0xAE, (byte) 0x8A, (byte) 0xC0, (byte) 0x05, (byte) 0xCD, (byte) 0xDC,
+            (byte) 0x10, (byte) 0x29, (byte) 0x4D, (byte) 0xFC, (byte) 0xEC, (byte) 0xEF,
+            (byte) 0x51, (byte) 0x4B, (byte) 0xF9, (byte) 0xCC, (byte) 0x99, (byte) 0x84,
+            (byte) 0x1B, (byte) 0x14, (byte) 0x68, (byte) 0xEC, (byte) 0xF0, (byte) 0x5E,
+            (byte) 0x07, (byte) 0x10, (byte) 0x09, (byte) 0xA9, (byte) 0x2C, (byte) 0x04,
+            (byte) 0xD0, (byte) 0x14, (byte) 0xBF, (byte) 0x88, (byte) 0x9E, (byte) 0xBB,
+            (byte) 0xE3, (byte) 0x3F, (byte) 0xDE, (byte) 0x92, (byte) 0xE1, (byte) 0x64,
+            (byte) 0x07, (byte) 0x28, (byte) 0xC1, (byte) 0xCA, (byte) 0x48, (byte) 0xC1,
+            (byte) 0x1D, (byte) 0x33, (byte) 0xE4, (byte) 0x35, (byte) 0xBE, (byte) 0xDF,
+            (byte) 0x5E, (byte) 0x50, (byte) 0xF9, (byte) 0xC2, (byte) 0x0E, (byte) 0x25,
+            (byte) 0x0D, (byte) 0x20, (byte) 0x8C, (byte) 0x01, (byte) 0x0A, (byte) 0x23,
+            (byte) 0xD4, (byte) 0x6E, (byte) 0x42, (byte) 0x47, (byte) 0xE1, (byte) 0x9E,
+            (byte) 0x36, (byte) 0x91, (byte) 0xC8, (byte) 0x65, (byte) 0x44, (byte) 0xE0,
+            (byte) 0x04, (byte) 0x86, (byte) 0x2F, (byte) 0xD4, (byte) 0x90, (byte) 0x16,
+            (byte) 0x09, (byte) 0x14, (byte) 0xB1, (byte) 0xC5, (byte) 0x7D, (byte) 0xB2,
+            (byte) 0x7C, (byte) 0x36, (byte) 0x0D, (byte) 0x9C, (byte) 0x1F, (byte) 0x83,
+            (byte) 0x57, (byte) 0x94, (byte) 0x26, (byte) 0x32, (byte) 0x9C, (byte) 0x86,
+            (byte) 0x8E, (byte) 0xE5, (byte) 0x80, (byte) 0x3A, (byte) 0xA9, (byte) 0xAF,
+            (byte) 0x4A, (byte) 0x95, (byte) 0x78, (byte) 0x8D, (byte) 0xE6, (byte) 0xC3,
+            (byte) 0x0C, (byte) 0x78, (byte) 0x83, (byte) 0x4B, (byte) 0xF5, (byte) 0x40,
+            (byte) 0x04, (byte) 0x20, (byte) 0x90, (byte) 0x5C, (byte) 0xA1, (byte) 0x19,
+            (byte) 0xEB, (byte) 0x95, (byte) 0x70, (byte) 0x2B, (byte) 0x94, (byte) 0xA3,
+            (byte) 0x43, (byte) 0xDD, (byte) 0xEB, (byte) 0xD4, (byte) 0x0C, (byte) 0xBC,
+            (byte) 0xBD, (byte) 0x58, (byte) 0x2D, (byte) 0x75, (byte) 0xB0, (byte) 0x8D,
+            (byte) 0x8B, (byte) 0x70, (byte) 0xB9, (byte) 0xE7, (byte) 0xA3, (byte) 0xCC,
+            (byte) 0x8C, (byte) 0xB4, (byte) 0xCD, (byte) 0xBB, (byte) 0x4B, (byte) 0xB1,
+            (byte) 0x15, (byte) 0x18, (byte) 0x79, (byte) 0xDF, (byte) 0x22, (byte) 0xA6,
+            (byte) 0x5C, (byte) 0x90, (byte) 0x7C, (byte) 0x1F, (byte) 0xEA, (byte) 0x1B,
+            (byte) 0xF2, (byte) 0x89, (byte) 0x87, (byte) 0xB2, (byte) 0xEC, (byte) 0x57,
+            (byte) 0xFF, (byte) 0xB2, (byte) 0xDA, (byte) 0xF5, (byte) 0xAD, (byte) 0x73,
+            (byte) 0xC0, (byte) 0xA0, (byte) 0x20, (byte) 0x8B, (byte) 0x78, (byte) 0xA1,
+            (byte) 0x5D, (byte) 0x04, (byte) 0x0A, (byte) 0x29, (byte) 0xE3, (byte) 0xD7,
+            (byte) 0x37, (byte) 0xF6, (byte) 0xA2, (byte) 0xCA,
+    };
+
+    public void test_DSA_generate_key() throws Exception {
+        final byte[] seed = new byte[20];
+
+        // Real key
+        {
+            long ctx = 0;
+            try {
+                ctx = NativeCrypto.DSA_generate_key(2048, seed, dsa2048_g, dsa2048_p, dsa2048_q);
+                assertTrue(ctx != NULL);
+            } finally {
+                if (ctx != 0) {
+                    NativeCrypto.EVP_PKEY_free(ctx);
+                }
+            }
+        }
+
+        // Real key with minimum bit size (should be 512 bits)
+        {
+            long ctx = 0;
+            try {
+                ctx = NativeCrypto.DSA_generate_key(0, null, null, null, null);
+                assertTrue(ctx != NULL);
+            } finally {
+                if (ctx != 0) {
+                    NativeCrypto.EVP_PKEY_free(ctx);
+                }
+            }
+        }
+
+        // Bad DSA params.
+        {
+            long ctx = 0;
+            try {
+                ctx = NativeCrypto.DSA_generate_key(0, null, new byte[] {}, new byte[] {},
+                        new byte[] {});
+                fail();
+            } catch (RuntimeException expected) {
+            } finally {
+                if (ctx != 0) {
+                    NativeCrypto.EVP_PKEY_free(ctx);
+                }
+            }
+        }
+    }
+
+    /*
+     * Test vector generation:
+     * openssl rand -hex 16
+     */
+    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,
+    };
+
+    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 testEC_GROUP() throws Exception {
+        /* Test using NIST's P-256 curve */
+        check_EC_GROUP(NativeCrypto.EC_CURVE_GFP, "prime256v1",
+                "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
+                "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
+                "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
+                "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
+                "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
+                "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
+                1L);
+
+        check_EC_GROUP(NativeCrypto.EC_CURVE_GF2M, "sect283r1",
+                "0800000000000000000000000000000000000000000000000000000000000000000010A1",
+                "000000000000000000000000000000000000000000000000000000000000000000000001",
+                "027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5",
+                "05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053",
+                "03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4",
+                "03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307",
+                2L);
+    }
+
+    private void check_EC_GROUP(int type, String name, String pStr, String aStr, String bStr,
+            String xStr, String yStr, String nStr, long hLong) throws Exception {
+        long group1 = NULL, group2 = NULL, point1 = NULL, point2 = NULL, key1 = NULL;
+        try {
+            group1 = NativeCrypto.EC_GROUP_new_by_curve_name(name);
+            assertTrue(group1 != NULL);
+            assertEquals(NativeCrypto.OBJ_txt2nid_longName(name),
+                    NativeCrypto.EC_GROUP_get_curve_name(group1));
+            assertEquals(type, NativeCrypto.get_EC_GROUP_type(group1));
+
+            // prime
+            BigInteger p = new BigInteger(pStr, 16);
+            // first coefficient
+            BigInteger a = new BigInteger(aStr, 16);
+            // second coefficient
+            BigInteger b = new BigInteger(bStr, 16);
+            // x affine coordinate of generator
+            BigInteger x = new BigInteger(xStr, 16);
+            // y affine coordinate of generator
+            BigInteger y = new BigInteger(yStr, 16);
+            // order of the generator
+            BigInteger n = new BigInteger(nStr, 16);
+            // cofactor of generator
+            BigInteger h = BigInteger.valueOf(hLong);
+
+            group2 = NativeCrypto.EC_GROUP_new_curve(type, p.toByteArray(),
+                    a.toByteArray(), b.toByteArray());
+            assertEquals(type, NativeCrypto.get_EC_GROUP_type(group2));
+
+            point2 = NativeCrypto.EC_POINT_new(group2);
+
+            NativeCrypto.EC_POINT_set_affine_coordinates(group2, point2, x.toByteArray(),
+                    y.toByteArray());
+
+            NativeCrypto.EC_GROUP_set_generator(group2, point2, n.toByteArray(), h.toByteArray());
+
+            point1 = NativeCrypto.EC_GROUP_get_generator(group2);
+            assertTrue(NativeCrypto.EC_POINT_cmp(group1, point1, point2));
+
+            byte[][] pab = NativeCrypto.EC_GROUP_get_curve(group2);
+            assertEquals(3, pab.length);
+
+            BigInteger p2 = new BigInteger(pab[0]);
+            assertEquals(p, p2);
+
+            BigInteger a2 = new BigInteger(pab[1]);
+            assertEquals(a, a2);
+
+            BigInteger b2 = new BigInteger(pab[2]);
+            assertEquals(b, b2);
+
+            byte[][] xy = NativeCrypto.EC_POINT_get_affine_coordinates(group2, point2);
+            assertEquals(2, xy.length);
+
+            BigInteger x2 = new BigInteger(xy[0]);
+            assertEquals(x, x2);
+
+            BigInteger y2 = new BigInteger(xy[1]);
+            assertEquals(y, y2);
+
+            BigInteger n2 = new BigInteger(NativeCrypto.EC_GROUP_get_order(group1));
+            assertEquals(n, n2);
+
+            BigInteger h2 = new BigInteger(NativeCrypto.EC_GROUP_get_cofactor(group2));
+            assertEquals(h, h2);
+
+            assertTrue(NativeCrypto.EC_GROUP_cmp(group1, group2));
+
+            key1 = NativeCrypto.EC_KEY_generate_key(group1);
+            long groupTmp = NativeCrypto.EC_KEY_get0_group(key1);
+            assertEquals(NativeCrypto.EC_GROUP_get_curve_name(group1),
+                    NativeCrypto.EC_GROUP_get_curve_name(groupTmp));
+
+        } finally {
+            if (group1 != NULL) {
+                NativeCrypto.EC_GROUP_clear_free(group1);
+            }
+
+            if (group2 != NULL) {
+                NativeCrypto.EC_GROUP_clear_free(group2);
+            }
+
+            if (point1 != NULL) {
+                NativeCrypto.EC_POINT_clear_free(point1);
+            }
+
+            if (point2 != NULL) {
+                NativeCrypto.EC_POINT_clear_free(point2);
+            }
+
+            if (key1 != NULL) {
+                NativeCrypto.EVP_PKEY_free(key1);
+            }
+        }
+    }
+
+    public void test_EVP_CipherInit_ex_Null_Failure() throws Exception {
+        final long ctx = NativeCrypto.EVP_CIPHER_CTX_new();
+        try {
+            final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb");
+
+            try {
+                NativeCrypto.EVP_CipherInit_ex(NULL, evpCipher, null, null, true);
+                fail("Null context should throw NullPointerException");
+            } catch (NullPointerException expected) {
+            }
+
+            /* Initialize encrypting. */
+            NativeCrypto.EVP_CipherInit_ex(ctx, evpCipher, null, null, true);
+            NativeCrypto.EVP_CipherInit_ex(ctx, NULL, null, null, true);
+
+            /* Initialize decrypting. */
+            NativeCrypto.EVP_CipherInit_ex(ctx, evpCipher, null, null, false);
+            NativeCrypto.EVP_CipherInit_ex(ctx, NULL, null, null, false);
+        } finally {
+            NativeCrypto.EVP_CIPHER_CTX_cleanup(ctx);
+        }
+    }
+
+    public void test_EVP_CipherInit_ex_Success() throws Exception {
+        final long ctx = NativeCrypto.EVP_CIPHER_CTX_new();
+        try {
+            final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb");
+            NativeCrypto.EVP_CipherInit_ex(ctx, evpCipher, AES_128_KEY, null, true);
+        } finally {
+            NativeCrypto.EVP_CIPHER_CTX_cleanup(ctx);
+        }
+    }
+
+    public void test_EVP_CIPHER_iv_length() throws Exception {
+        long aes128ecb = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb");
+        assertEquals(0, NativeCrypto.EVP_CIPHER_iv_length(aes128ecb));
+
+        long aes128cbc = NativeCrypto.EVP_get_cipherbyname("aes-128-cbc");
+        assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(aes128cbc));
+    }
+
+    public void test_OpenSSLKey_toJava() throws Exception {
+        OpenSSLKey key1;
+
+        BigInteger e = BigInteger.valueOf(65537);
+        key1 = new OpenSSLKey(NativeCrypto.RSA_generate_key_ex(1024, e.toByteArray()));
+        assertTrue(key1.getPublicKey() instanceof RSAPublicKey);
+
+        key1 = new OpenSSLKey(NativeCrypto.DSA_generate_key(1024, null, null, null, null));
+        assertTrue(key1.getPublicKey() instanceof DSAPublicKey);
+
+        long group1 = NULL;
+        try {
+            group1 = NativeCrypto.EC_GROUP_new_by_curve_name("prime256v1");
+            assertTrue(group1 != NULL);
+            key1 = new OpenSSLKey(NativeCrypto.EC_KEY_generate_key(group1));
+        } finally {
+            if (group1 != NULL) {
+                NativeCrypto.EC_GROUP_clear_free(group1);
+            }
+        }
+        assertTrue(key1.getPublicKey() instanceof ECPublicKey);
+    }
+
+    public void test_create_BIO_InputStream() throws Exception {
+        byte[] actual = "Test".getBytes();
+        ByteArrayInputStream is = new ByteArrayInputStream(actual);
+
+        long ctx = NativeCrypto.create_BIO_InputStream(new OpenSSLBIOInputStream(is));
+        try {
+            byte[] buffer = new byte[1024];
+            int numRead = NativeCrypto.BIO_read(ctx, buffer);
+            assertEquals(actual.length, numRead);
+            assertEquals(Arrays.toString(actual),
+                    Arrays.toString(Arrays.copyOfRange(buffer, 0, numRead)));
+        } finally {
+            NativeCrypto.BIO_free(ctx);
+        }
+
+    }
+
+    public void test_create_BIO_OutputStream() throws Exception {
+        byte[] actual = "Test".getBytes();
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+        long ctx = NativeCrypto.create_BIO_OutputStream(os);
+        try {
+            NativeCrypto.BIO_write(ctx, actual, 0, actual.length);
+            assertEquals(actual.length, os.size());
+            assertEquals(Arrays.toString(actual), Arrays.toString(os.toByteArray()));
+        } finally {
+            NativeCrypto.BIO_free(ctx);
+        }
     }
 }
diff --git a/luni/src/test/java/sun/misc/UnsafeTest.java b/luni/src/test/java/sun/misc/UnsafeTest.java
index 462d39d..48939f5 100644
--- a/luni/src/test/java/sun/misc/UnsafeTest.java
+++ b/luni/src/test/java/sun/misc/UnsafeTest.java
@@ -18,6 +18,7 @@
 
 import junit.framework.TestCase;
 
+import java.lang.reflect.Field;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
@@ -48,4 +49,25 @@
         } catch (SecurityException expected) {
         }
     }
+
+    private class AllocateInstanceTestClass {
+        public int i = 123;
+        public String s = "hello";
+        public Object getThis() { return AllocateInstanceTestClass.this; }
+    }
+
+    private static Unsafe getUnsafe() throws Exception {
+        Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+        Field f = unsafeClass.getDeclaredField("theUnsafe");
+        f.setAccessible(true);
+        return (Unsafe) f.get(null);
+    }
+
+    public void test_allocateInstance() throws Exception {
+        AllocateInstanceTestClass i = (AllocateInstanceTestClass)
+                getUnsafe().allocateInstance(AllocateInstanceTestClass.class);
+        assertEquals(0, i.i);
+        assertEquals(null, i.s);
+        assertEquals(i, i.getThis());
+    }
 }
diff --git a/luni/src/test/java/tests/api/java/util/ResourceBundleTest.java b/luni/src/test/java/tests/api/java/util/ResourceBundleTest.java
index f1fd484..587a549 100644
--- a/luni/src/test/java/tests/api/java/util/ResourceBundleTest.java
+++ b/luni/src/test/java/tests/api/java/util/ResourceBundleTest.java
@@ -33,6 +33,12 @@
 
 public class ResourceBundleTest extends junit.framework.TestCase {
 
+    public void test_getCandidateLocales() throws Exception {
+        ResourceBundle.Control c = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
+        assertEquals("[en_US, en, ]", c.getCandidateLocales("base", Locale.US).toString());
+        assertEquals("[de_CH, de, ]", c.getCandidateLocales("base", new Locale("de", "CH")).toString());
+    }
+
     /**
      * java.util.ResourceBundle#getBundle(java.lang.String,
      *        java.util.Locale)
@@ -64,15 +70,19 @@
         assertEquals("Wrong bundle de_FR_var 2", "parentValue4", bundle.getString("parent4")
                 );
 
-        // Test with a security manager
-        Locale.setDefault(new Locale("en", "US"));
-
         try {
-            ResourceBundle.getBundle(null, Locale.getDefault());
+            ResourceBundle.getBundle(null, Locale.US);
             fail("NullPointerException expected");
         } catch (NullPointerException ee) {
             //expected
         }
+        try {
+            ResourceBundle.getBundle("blah", (Locale) null);
+            fail("NullPointerException expected");
+        } catch (NullPointerException ee) {
+            //expected
+        }
+
 
         try {
             ResourceBundle.getBundle("", new Locale("xx", "yy"));
@@ -80,8 +90,6 @@
         } catch (MissingResourceException ee) {
             //expected
         }
-
-        Locale.setDefault(defLocale);
     }
 
     /**
diff --git a/luni/src/test/java/tests/api/java/util/SimpleTimeZoneTest.java b/luni/src/test/java/tests/api/java/util/SimpleTimeZoneTest.java
index 618cbe4..eeb5b30 100644
--- a/luni/src/test/java/tests/api/java/util/SimpleTimeZoneTest.java
+++ b/luni/src/test/java/tests/api/java/util/SimpleTimeZoneTest.java
@@ -442,8 +442,18 @@
         st.setStartRule(0, 1, 1, 1);
         st.setEndRule(11, 1, 1, 1);
         st.setDSTSavings(1);
-        assertEquals("Daylight savings amount not set", 1, st.getDSTSavings());
-    }
+        assertEquals(1, st.getDSTSavings());
+        try {
+            st.setDSTSavings(0);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            st.setDSTSavings(-1);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+      }
 
     /**
      * java.util.SimpleTimeZone#setEndRule(int, int, int)
diff --git a/luni/src/test/java/tests/api/java/util/TimerTest.java b/luni/src/test/java/tests/api/java/util/TimerTest.java
index 26d88ba..b09fa45 100644
--- a/luni/src/test/java/tests/api/java/util/TimerTest.java
+++ b/luni/src/test/java/tests/api/java/util/TimerTest.java
@@ -91,6 +91,15 @@
         }
     }
 
+    private void awaitRun(TimerTestTask task) {
+        while (task.wasRun() == 0) {
+            try {
+                Thread.sleep(150);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
     /**
      * java.util.Timer#Timer(boolean)
      */
@@ -101,14 +110,7 @@
             t = new Timer(true);
             TimerTestTask testTask = new TimerTestTask();
             t.schedule(testTask, 200);
-            synchronized (sync) {
-                try {
-                    sync.wait(1000);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertEquals("TimerTask.run() method not called after 200ms",
-                    1, testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
         } finally {
             if (t != null)
@@ -127,14 +129,7 @@
             t = new Timer();
             TimerTestTask testTask = new TimerTestTask();
             t.schedule(testTask, 200);
-            synchronized (sync) {
-                try {
-                    sync.wait(1000);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertEquals("TimerTask.run() method not called after 200ms",
-                    1, testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
         } finally {
             if (t != null)
@@ -153,13 +148,7 @@
             t = new Timer("test_ConstructorSZThread", true);
             TimerTestTask testTask = new TimerTestTask();
             t.schedule(testTask, 200);
-            synchronized (sync) {
-                try {
-                    sync.wait(1000);
-                } catch (InterruptedException e) {}
-            }
-            assertEquals("TimerTask.run() method not called after 200ms", 1,
-                    testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
         } finally {
             if (t != null)
@@ -191,13 +180,7 @@
             t = new Timer("test_ConstructorSThread");
             TimerTestTask testTask = new TimerTestTask();
             t.schedule(testTask, 200);
-            synchronized (sync) {
-                try {
-                    sync.wait(1000);
-                } catch (InterruptedException e) {}
-            }
-            assertEquals("TimerTask.run() method not called after 200ms", 1,
-                    testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
         } finally {
             if (t != null)
@@ -236,14 +219,7 @@
             t = new Timer();
             testTask = new TimerTestTask();
             t.schedule(testTask, 100, 500);
-            synchronized (sync) {
-                try {
-                    sync.wait(1000);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertEquals("TimerTask.run() method not called after 200ms",
-                    1, testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
             synchronized (sync) {
                 try {
@@ -258,14 +234,7 @@
             t = new Timer();
             testTask = new TimerTestTask();
             t.schedule(testTask, 100, 500);
-            synchronized (sync) {
-                try {
-                    sync.wait(500);
-                } catch (InterruptedException e) {
-                }
-            }
-            assertEquals("TimerTask.run() method not called after 200ms",
-                    1, testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
             t.cancel();
             t.cancel();
@@ -446,12 +415,7 @@
             testTask = new TimerTestTask();
             d = new Date(System.currentTimeMillis() + 200);
             t.schedule(testTask, d);
-            try {
-                Thread.sleep(400);
-            } catch (InterruptedException e) {
-            }
-            assertEquals("TimerTask.run() method not called after 200ms",
-                    1, testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
 
             // Ensure multiple tasks are run
@@ -568,12 +532,7 @@
             t = new Timer();
             testTask = new TimerTestTask();
             t.schedule(testTask, 200);
-            try {
-                Thread.sleep(400);
-            } catch (InterruptedException e) {
-            }
-            assertEquals("TimerTask.run() method not called after 200ms",
-                    1, testTask.wasRun());
+            awaitRun(testTask);
             t.cancel();
 
             // Ensure multiple tasks are run
diff --git a/luni/src/test/java/tests/api/javax/security/cert/X509CertificateTest.java b/luni/src/test/java/tests/api/javax/security/cert/X509CertificateTest.java
index 996c94df..e937db9 100644
--- a/luni/src/test/java/tests/api/javax/security/cert/X509CertificateTest.java
+++ b/luni/src/test/java/tests/api/javax/security/cert/X509CertificateTest.java
@@ -310,10 +310,10 @@
             // Test can not be applied.
             return;
         }
-        Date[] date = new Date[4];
+        Date[] date = new Date[8];
         Calendar calendar = Calendar.getInstance();
         for (int i = 0; i < date.length; i++) {
-            calendar.set(i * 50, Calendar.JANUARY, 1);
+            calendar.set(i * 500, Calendar.JANUARY, 1);
             date[i] = calendar.getTime();
         }
         Date nb_date = tbt_cert.getNotBefore();
diff --git a/luni/src/test/java/tests/security/cert/X509CertSelectorTest.java b/luni/src/test/java/tests/security/cert/X509CertSelectorTest.java
index 1fc6426..049f3a0 100644
--- a/luni/src/test/java/tests/security/cert/X509CertSelectorTest.java
+++ b/luni/src/test/java/tests/security/cert/X509CertSelectorTest.java
@@ -17,8 +17,6 @@
 
 package tests.security.cert;
 
-import junit.framework.TestCase;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.math.BigInteger;
@@ -53,19 +51,17 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-
 import javax.security.auth.x500.X500Principal;
-
-
-import org.apache.harmony.security.tests.support.cert.MyCRL;
-import org.apache.harmony.security.tests.support.cert.TestUtils;
-import org.apache.harmony.security.tests.support.TestKeyPair;
+import junit.framework.TestCase;
 import org.apache.harmony.security.asn1.ASN1Boolean;
 import org.apache.harmony.security.asn1.ASN1Integer;
 import org.apache.harmony.security.asn1.ASN1OctetString;
 import org.apache.harmony.security.asn1.ASN1Oid;
 import org.apache.harmony.security.asn1.ASN1Sequence;
 import org.apache.harmony.security.asn1.ASN1Type;
+import org.apache.harmony.security.tests.support.TestKeyPair;
+import org.apache.harmony.security.tests.support.cert.MyCRL;
+import org.apache.harmony.security.tests.support.cert.TestUtils;
 import org.apache.harmony.security.x501.Name;
 import org.apache.harmony.security.x509.CertificatePolicies;
 import org.apache.harmony.security.x509.GeneralName;
@@ -141,7 +137,7 @@
                 new X509CertSelector().addSubjectAlternativeName(types[i],
                         (byte[]) null);
                 fail("No expected NullPointerException for type: " + types[i]);
-            } catch (NullPointerException e) {
+            } catch (NullPointerException expected) {
             }
         }
     }
@@ -165,7 +161,7 @@
                 new X509CertSelector().addSubjectAlternativeName(types[i],
                         "-0xDFRF");
                 fail("IOException expected for type: " + types[i]);
-            } catch (IOException e) {
+            } catch (IOException expected) {
             }
         }
     }
@@ -188,7 +184,7 @@
             try {
                 new X509CertSelector().addPathToName(types[i], (byte[]) null);
                 fail("No expected NullPointerException for type: " + types[i]);
-            } catch (NullPointerException e) {
+            } catch (NullPointerException expected) {
             }
         }
     }
@@ -201,9 +197,8 @@
         for (int type = 0; type <= 8; type++) {
             try {
                 new X509CertSelector().addPathToName(type, (String) null);
-                fail("IOException expected!");
-            } catch (IOException ioe) {
-                // expected
+                fail();
+            } catch (IOException expected) {
             }
         }
 
@@ -214,12 +209,7 @@
      * java.security.cert.X509CertSelector#X509CertSelector()
      */
     public void test_X509CertSelector() {
-        X509CertSelector selector = null;
-        try {
-            selector = new X509CertSelector();
-        } catch (Exception e) {
-            fail("Unexpected exception " + e.getMessage());
-        }
+        X509CertSelector selector = new X509CertSelector();
         assertEquals(-1, selector.getBasicConstraints());
         assertTrue(selector.getMatchAllSubjectAltNames());
     }
@@ -231,49 +221,34 @@
         X509CertSelector selector = new X509CertSelector();
         X509CertSelector selector1 = (X509CertSelector) selector.clone();
 
-        assertEquals(selector.getMatchAllSubjectAltNames(), selector1
-                .getMatchAllSubjectAltNames());
-        assertEquals(selector.getAuthorityKeyIdentifier(), selector1
-                .getAuthorityKeyIdentifier());
-        assertEquals(selector.getBasicConstraints(), selector1
-                .getBasicConstraints());
+        assertEquals(selector.getMatchAllSubjectAltNames(), selector1.getMatchAllSubjectAltNames());
+        assertEquals(selector.getAuthorityKeyIdentifier(), selector1.getAuthorityKeyIdentifier());
+        assertEquals(selector.getBasicConstraints(), selector1.getBasicConstraints());
         assertEquals(selector.getCertificate(), selector1.getCertificate());
-        assertEquals(selector.getCertificateValid(), selector1
-                .getCertificateValid());
-        assertEquals(selector.getExtendedKeyUsage(), selector1
-                .getExtendedKeyUsage());
+        assertEquals(selector.getCertificateValid(), selector1.getCertificateValid());
+        assertEquals(selector.getExtendedKeyUsage(), selector1.getExtendedKeyUsage());
         assertEquals(selector.getIssuer(), selector1.getIssuer());
         assertEquals(selector.getIssuerAsBytes(), selector1.getIssuerAsBytes());
-        assertEquals(selector.getIssuerAsString(), selector1
-                .getIssuerAsString());
+        assertEquals(selector.getIssuerAsString(), selector1.getIssuerAsString());
         assertEquals(selector.getKeyUsage(), selector1.getKeyUsage());
-        assertEquals(selector.getNameConstraints(), selector1
-                .getNameConstraints());
+        assertEquals(selector.getNameConstraints(), selector1.getNameConstraints());
         assertEquals(selector.getPathToNames(), selector1.getPathToNames());
         assertEquals(selector.getPolicy(), selector1.getPolicy());
-        assertEquals(selector.getPrivateKeyValid(), selector1
-                .getPrivateKeyValid());
+        assertEquals(selector.getPrivateKeyValid(), selector1.getPrivateKeyValid());
         assertEquals(selector.getSerialNumber(), selector1.getSerialNumber());
         assertEquals(selector.getSubject(), selector1.getSubject());
-        assertEquals(selector.getSubjectAlternativeNames(), selector1
-                .getSubjectAlternativeNames());
-        assertEquals(selector.getSubjectAsBytes(), selector1
-                .getSubjectAsBytes());
-        assertEquals(selector.getSubjectAsString(), selector1
-                .getSubjectAsString());
-        assertEquals(selector.getSubjectKeyIdentifier(), selector1
-                .getSubjectKeyIdentifier());
-        assertEquals(selector.getSubjectPublicKey(), selector1
-                .getSubjectPublicKey());
-        assertEquals(selector.getSubjectPublicKeyAlgID(), selector1
-                .getSubjectPublicKeyAlgID());
+        assertEquals(selector.getSubjectAlternativeNames(), selector1.getSubjectAlternativeNames());
+        assertEquals(selector.getSubjectAsBytes(), selector1.getSubjectAsBytes());
+        assertEquals(selector.getSubjectAsString(), selector1.getSubjectAsString());
+        assertEquals(selector.getSubjectKeyIdentifier(), selector1.getSubjectKeyIdentifier());
+        assertEquals(selector.getSubjectPublicKey(), selector1.getSubjectPublicKey());
+        assertEquals(selector.getSubjectPublicKeyAlgID(), selector1.getSubjectPublicKeyAlgID());
 
         selector = null;
         try {
             selector.clone();
-            fail("NullPointerException expected");
-        } catch (NullPointerException e) {
-            // expected
+            fail();
+        } catch (NullPointerException expected) {
         }
     }
 
@@ -285,15 +260,15 @@
         byte[] akid2 = new byte[] { 4, 5, 5, 4, 3, 2, 1 }; // random value
         X509CertSelector selector = new X509CertSelector();
 
-        assertNull("Selector should return null", selector
-                .getAuthorityKeyIdentifier());
+        assertNull("Selector should return null",
+                   selector.getAuthorityKeyIdentifier());
+        assertFalse("The returned keyID should be equal to specified",
+                   Arrays.equals(akid1, selector.getAuthorityKeyIdentifier()));
         selector.setAuthorityKeyIdentifier(akid1);
-        assertTrue("The returned keyID should be equal to specified", Arrays
-                .equals(akid1, selector.getAuthorityKeyIdentifier()));
-        assertTrue("The returned keyID should be equal to specified", Arrays
-                .equals(akid1, selector.getAuthorityKeyIdentifier()));
-        assertFalse("The returned keyID should differ", Arrays.equals(akid2,
-                selector.getAuthorityKeyIdentifier()));
+        assertTrue("The returned keyID should be equal to specified",
+                   Arrays.equals(akid1, selector.getAuthorityKeyIdentifier()));
+        assertFalse("The returned keyID should differ",
+                    Arrays.equals(akid2, selector.getAuthorityKeyIdentifier()));
     }
 
     /**
@@ -311,16 +286,16 @@
     /**
      * java.security.cert.X509CertSelector#getCertificate()
      */
-    public void test_getCertificate() throws CertificateException {
+    public void test_getCertificate() throws Exception {
         X509CertSelector selector = new X509CertSelector();
         CertificateFactory certFact = CertificateFactory.getInstance("X509");
-        X509Certificate cert1 = (X509Certificate) certFact
-                .generateCertificate(new ByteArrayInputStream(TestUtils
-                        .getX509Certificate_v3()));
+        X509Certificate cert1 = (X509Certificate)
+                certFact.generateCertificate(new ByteArrayInputStream(
+                        TestUtils.getX509Certificate_v3()));
 
-        X509Certificate cert2 = (X509Certificate) certFact
-                .generateCertificate(new ByteArrayInputStream(TestUtils
-                        .getX509Certificate_v1()));
+        X509Certificate cert2 = (X509Certificate)
+                certFact.generateCertificate(new ByteArrayInputStream(
+                        TestUtils.getX509Certificate_v1()));
 
         selector.setCertificate(cert1);
         assertEquals(cert1, selector.getCertificate());
@@ -341,19 +316,19 @@
         Date date3 = Calendar.getInstance().getTime();
         X509CertSelector selector = new X509CertSelector();
 
-        assertNull("Selector should return null", selector
-                .getCertificateValid());
+        assertNull("Selector should return null",
+                   selector.getCertificateValid());
         selector.setCertificateValid(date1);
-        assertTrue("The returned date should be equal to specified", date1
-                .equals(selector.getCertificateValid()));
+        assertTrue("The returned date should be equal to specified",
+                   date1.equals(selector.getCertificateValid()));
         selector.getCertificateValid().setTime(200);
-        assertTrue("The returned date should be equal to specified", date1
-                .equals(selector.getCertificateValid()));
-        assertFalse("The returned date should differ", date2.equals(selector
-                .getCertificateValid()));
+        assertTrue("The returned date should be equal to specified",
+                   date1.equals(selector.getCertificateValid()));
+        assertFalse("The returned date should differ",
+                    date2.equals(selector.getCertificateValid()));
         selector.setCertificateValid(date3);
-        assertTrue("The returned date should be equal to specified", date3
-                .equals(selector.getCertificateValid()));
+        assertTrue("The returned date should be equal to specified",
+                   date3.equals(selector.getCertificateValid()));
         selector.setCertificateValid(null);
         assertNull(selector.getCertificateValid());
     }
@@ -361,30 +336,28 @@
     /**
      * java.security.cert.X509CertSelector#getExtendedKeyUsage()
      */
-    public void test_getExtendedKeyUsage() {
-        HashSet<String> ku = new HashSet<String>(Arrays
-                .asList(new String[] { "1.3.6.1.5.5.7.3.1",
-                        "1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.3",
-                        "1.3.6.1.5.5.7.3.4", "1.3.6.1.5.5.7.3.8",
-                        "1.3.6.1.5.5.7.3.9", "1.3.6.1.5.5.7.3.5",
-                        "1.3.6.1.5.5.7.3.6", "1.3.6.1.5.5.7.3.7" }));
+    public void test_getExtendedKeyUsage() throws Exception {
+        HashSet<String> ku = new HashSet<String>(Arrays.asList(new String[] {
+            "1.3.6.1.5.5.7.3.1",
+            "1.3.6.1.5.5.7.3.2",
+            "1.3.6.1.5.5.7.3.3",
+            "1.3.6.1.5.5.7.3.4",
+            "1.3.6.1.5.5.7.3.8",
+            "1.3.6.1.5.5.7.3.9",
+            "1.3.6.1.5.5.7.3.5",
+            "1.3.6.1.5.5.7.3.6",
+            "1.3.6.1.5.5.7.3.7"
+        }));
         X509CertSelector selector = new X509CertSelector();
 
-        assertNull("Selector should return null", selector
-                .getExtendedKeyUsage());
-        try {
-            selector.setExtendedKeyUsage(ku);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
-        assertTrue(
-                "The returned extendedKeyUsage should be equal to specified",
-                ku.equals(selector.getExtendedKeyUsage()));
+        assertNull("Selector should return null", selector.getExtendedKeyUsage());
+        selector.setExtendedKeyUsage(ku);
+        assertTrue("The returned extendedKeyUsage should be equal to specified",
+                   ku.equals(selector.getExtendedKeyUsage()));
         try {
             selector.getExtendedKeyUsage().add("KRIBLEGRABLI");
             fail("The returned Set should be immutable.");
-        } catch (UnsupportedOperationException e) {
-            // expected
+        } catch (UnsupportedOperationException expected) {
         }
     }
 
@@ -398,16 +371,16 @@
 
         assertNull("Selector should return null", selector.getIssuer());
         selector.setIssuer(iss1);
-        assertEquals("The returned issuer should be equal to specified", iss1,
-                selector.getIssuer());
-        assertFalse("The returned issuer should differ", iss2.equals(selector
-                .getIssuer()));
+        assertEquals("The returned issuer should be equal to specified",
+                     iss1, selector.getIssuer());
+        assertFalse("The returned issuer should differ",
+                    iss2.equals(selector.getIssuer()));
     }
 
     /**
      * java.security.cert.X509CertSelector#getIssuerAsBytes()
      */
-    public void test_getIssuerAsBytes() {
+    public void test_getIssuerAsBytes() throws Exception {
         byte[] name1 = new byte[]
         // manually obtained DER encoding of "O=First Org." issuer name;
         { 48, 21, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 70, 105, 114, 115,
@@ -421,20 +394,14 @@
         X500Principal iss2 = new X500Principal(name2);
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            assertNull("Selector should return null", selector
-                    .getIssuerAsBytes());
-            selector.setIssuer(iss1);
-            assertTrue("The returned issuer should be equal to specified",
-                    Arrays.equals(name1, selector.getIssuerAsBytes()));
-            assertFalse("The returned issuer should differ", name2
-                    .equals(selector.getIssuerAsBytes()));
-            selector.setIssuer(iss2);
-            assertTrue("The returned issuer should be equal to specified",
-                    Arrays.equals(name2, selector.getIssuerAsBytes()));
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        assertNull("Selector should return null", selector.getIssuerAsBytes());
+        selector.setIssuer(iss1);
+        assertTrue("The returned issuer should be equal to specified",
+                   Arrays.equals(name1, selector.getIssuerAsBytes()));
+        assertFalse("The returned issuer should differ", name2.equals(selector.getIssuerAsBytes()));
+        selector.setIssuer(iss2);
+        assertTrue("The returned issuer should be equal to specified",
+                   Arrays.equals(name2, selector.getIssuerAsBytes()));
     }
 
     /**
@@ -450,12 +417,12 @@
         assertNull("Selector should return null", selector.getIssuerAsString());
         selector.setIssuer(iss1);
         assertEquals("The returned issuer should be equal to specified", name1,
-                selector.getIssuerAsString());
-        assertFalse("The returned issuer should differ", name2.equals(selector
-                .getIssuerAsString()));
+                     selector.getIssuerAsString());
+        assertFalse("The returned issuer should differ",
+                    name2.equals(selector.getIssuerAsString()));
         selector.setIssuer(iss2);
         assertEquals("The returned issuer should be equal to specified", name2,
-                selector.getIssuerAsString());
+                     selector.getIssuerAsString());
     }
 
     /**
@@ -468,12 +435,12 @@
 
         assertNull("Selector should return null", selector.getKeyUsage());
         selector.setKeyUsage(ku);
-        assertTrue("The returned date should be equal to specified", Arrays
-                .equals(ku, selector.getKeyUsage()));
+        assertTrue("The returned date should be equal to specified",
+                   Arrays.equals(ku, selector.getKeyUsage()));
         boolean[] result = selector.getKeyUsage();
         result[0] = !result[0];
-        assertTrue("The returned keyUsage should be equal to specified", Arrays
-                .equals(ku, selector.getKeyUsage()));
+        assertTrue("The returned keyUsage should be equal to specified",
+                   Arrays.equals(ku, selector.getKeyUsage()));
     }
 
     /**
@@ -481,11 +448,11 @@
      */
     public void test_getMatchAllSubjectAltNames() {
         X509CertSelector selector = new X509CertSelector();
-        assertTrue("The matchAllNames initially should be true", selector
-                .getMatchAllSubjectAltNames());
+        assertTrue("The matchAllNames initially should be true",
+                   selector.getMatchAllSubjectAltNames());
         selector.setMatchAllSubjectAltNames(false);
-        assertFalse("The value should be false", selector
-                .getMatchAllSubjectAltNames());
+        assertFalse("The value should be false",
+                    selector.getMatchAllSubjectAltNames());
     }
 
     /**
@@ -523,67 +490,68 @@
 
         for (int i = 0; i < constraintBytes.length; i++) {
             selector.setNameConstraints(constraintBytes[i]);
-            assertTrue(Arrays.equals(constraintBytes[i], selector
-                    .getNameConstraints()));
+            assertTrue(Arrays.equals(constraintBytes[i],
+                                     selector.getNameConstraints()));
         }
     }
 
     /**
      * java.security.cert.X509CertSelector#getPathToNames()
      */
-    public void test_getPathToNames() {
-        try {
-            GeneralName san0 = new GeneralName(new OtherName("1.2.3.4.5",
-                    new byte[] { 1, 2, 0, 1 }));
-            GeneralName san1 = new GeneralName(1, "rfc@822.Name");
-            GeneralName san2 = new GeneralName(2, "dNSName");
-            GeneralName san3 = new GeneralName(new ORAddress());
-            GeneralName san4 = new GeneralName(new Name("O=Organization"));
-            GeneralName san6 = new GeneralName(6, "http://uniform.Resource.Id");
-            GeneralName san7 = new GeneralName(7, "1.1.1.1");
-            GeneralName san8 = new GeneralName(8, "1.2.3.4444.55555");
+    public void test_getPathToNames() throws Exception {
+        GeneralName san0 = new GeneralName(new OtherName("1.2.3.4.5",
+                                                         new byte[] { 1, 2, 0, 1 }));
+        GeneralName san1 = new GeneralName(1, "rfc@822.Name");
+        GeneralName san2 = new GeneralName(2, "dNSName");
+        GeneralName san3 = new GeneralName(new ORAddress());
+        GeneralName san4 = new GeneralName(new Name("O=Organization"));
+        GeneralName san6 = new GeneralName(6, "http://uniform.Resource.Id");
+        GeneralName san7 = new GeneralName(7, "1.1.1.1");
+        GeneralName san8 = new GeneralName(8, "1.2.3.4444.55555");
 
-            GeneralNames sans1 = new GeneralNames();
-            sans1.addName(san0);
-            sans1.addName(san1);
-            sans1.addName(san2);
-            sans1.addName(san3);
-            sans1.addName(san4);
-            sans1.addName(san6);
-            sans1.addName(san7);
-            sans1.addName(san8);
-            GeneralNames sans2 = new GeneralNames();
-            sans2.addName(san0);
+        GeneralNames sans1 = new GeneralNames();
+        sans1.addName(san0);
+        sans1.addName(san1);
+        sans1.addName(san2);
+        sans1.addName(san3);
+        sans1.addName(san4);
+        sans1.addName(san6);
+        sans1.addName(san7);
+        sans1.addName(san8);
+        GeneralNames sans2 = new GeneralNames();
+        sans2.addName(san0);
 
-            TestCert cert1 = new TestCert(sans1);
-            TestCert cert2 = new TestCert(sans2);
-            X509CertSelector selector = new X509CertSelector();
-            selector.setMatchAllSubjectAltNames(true);
+        TestCert cert1 = new TestCert(sans1);
+        TestCert cert2 = new TestCert(sans2);
+        X509CertSelector selector = new X509CertSelector();
+        selector.setMatchAllSubjectAltNames(true);
 
-            selector.setPathToNames(null);
-            assertTrue("Any certificate should match in the case of null "
-                    + "subjectAlternativeNames criteria.", selector
-                    .match(cert1)
-                    && selector.match(cert2));
+        selector.setPathToNames(null);
+        assertTrue("Any certificate should match in the case of null "
+                   + "subjectAlternativeNames criteria.",
+                   selector.match(cert1) && selector.match(cert2));
 
-            Collection<List<?>> sans = sans1.getPairsList();
+        Collection<List<?>> sans = sans1.getPairsList();
 
-            selector.setPathToNames(sans);
-            selector.getPathToNames();
-        } catch (IOException e) {
-            e.printStackTrace();
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setPathToNames(sans);
+        selector.getPathToNames();
     }
 
     /**
      * java.security.cert.X509CertSelector#getPolicy()
      */
     public void test_getPolicy() throws IOException {
-        String[] policies1 = new String[] { "1.3.6.1.5.5.7.3.1",
-                "1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.3", "1.3.6.1.5.5.7.3.4",
-                "1.3.6.1.5.5.7.3.8", "1.3.6.1.5.5.7.3.9", "1.3.6.1.5.5.7.3.5",
-                "1.3.6.1.5.5.7.3.6", "1.3.6.1.5.5.7.3.7" };
+        String[] policies1 = new String[] {
+            "1.3.6.1.5.5.7.3.1",
+            "1.3.6.1.5.5.7.3.2",
+            "1.3.6.1.5.5.7.3.3",
+            "1.3.6.1.5.5.7.3.4",
+            "1.3.6.1.5.5.7.3.8",
+            "1.3.6.1.5.5.7.3.9",
+            "1.3.6.1.5.5.7.3.5",
+            "1.3.6.1.5.5.7.3.6",
+            "1.3.6.1.5.5.7.3.7"
+        };
 
         String[] policies2 = new String[] { "1.3.6.7.3.1" };
 
@@ -612,13 +580,13 @@
 
         assertNull("Selector should return null", selector.getPrivateKeyValid());
         selector.setPrivateKeyValid(date1);
-        assertTrue("The returned date should be equal to specified", date1
-                .equals(selector.getPrivateKeyValid()));
+        assertTrue("The returned date should be equal to specified",
+                   date1.equals(selector.getPrivateKeyValid()));
         selector.getPrivateKeyValid().setTime(200);
-        assertTrue("The returned date should be equal to specified", date1
-                .equals(selector.getPrivateKeyValid()));
-        assertFalse("The returned date should differ", date2.equals(selector
-                .getPrivateKeyValid()));
+        assertTrue("The returned date should be equal to specified",
+                   date1.equals(selector.getPrivateKeyValid()));
+        assertFalse("The returned date should differ",
+                    date2.equals(selector.getPrivateKeyValid()));
     }
 
     /**
@@ -632,9 +600,9 @@
         assertNull("Selector should return null", selector.getSerialNumber());
         selector.setSerialNumber(ser1);
         assertEquals("The returned serial number should be equal to specified",
-                ser1, selector.getSerialNumber());
-        assertFalse("The returned serial number should differ", ser2
-                .equals(selector.getSerialNumber()));
+                     ser1, selector.getSerialNumber());
+        assertFalse("The returned serial number should differ",
+                    ser2.equals(selector.getSerialNumber()));
     }
 
     /**
@@ -648,73 +616,65 @@
         assertNull("Selector should return null", selector.getSubject());
         selector.setSubject(sub1);
         assertEquals("The returned subject should be equal to specified", sub1,
-                selector.getSubject());
-        assertFalse("The returned subject should differ", sub2.equals(selector
-                .getSubject()));
+                     selector.getSubject());
+        assertFalse("The returned subject should differ",
+                    sub2.equals(selector.getSubject()));
     }
 
     /**
      * java.security.cert.X509CertSelector#getSubjectAlternativeNames()
      */
-    public void test_getSubjectAlternativeNames() {
-        try {
-            GeneralName san1 = new GeneralName(1, "rfc@822.Name");
-            GeneralName san2 = new GeneralName(2, "dNSName");
+    public void test_getSubjectAlternativeNames() throws Exception {
+        GeneralName san1 = new GeneralName(1, "rfc@822.Name");
+        GeneralName san2 = new GeneralName(2, "dNSName");
 
-            GeneralNames sans = new GeneralNames();
-            sans.addName(san1);
-            sans.addName(san2);
+        GeneralNames sans = new GeneralNames();
+        sans.addName(san1);
+        sans.addName(san2);
 
-            TestCert cert_1 = new TestCert(sans);
-            X509CertSelector selector = new X509CertSelector();
+        TestCert cert_1 = new TestCert(sans);
+        X509CertSelector selector = new X509CertSelector();
 
-            assertNull("Selector should return null", selector
-                    .getSubjectAlternativeNames());
+        assertNull("Selector should return null",
+                   selector.getSubjectAlternativeNames());
 
-            selector.setSubjectAlternativeNames(sans.getPairsList());
-            assertTrue("The certificate should match the selection criteria.",
-                    selector.match(cert_1));
-            selector.getSubjectAlternativeNames().clear();
-            assertTrue("The modification of initialization object "
-                    + "should not affect the modification "
-                    + "of internal object.", selector.match(cert_1));
-        } catch (IOException e) {
-            e.printStackTrace();
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setSubjectAlternativeNames(sans.getPairsList());
+        assertTrue("The certificate should match the selection criteria.",
+                   selector.match(cert_1));
+        selector.getSubjectAlternativeNames().clear();
+        assertTrue("The modification of initialization object "
+                   + "should not affect the modification "
+                   + "of internal object.",
+                   selector.match(cert_1));
     }
 
     /**
      * java.security.cert.X509CertSelector#getSubjectAsBytes()
      */
-    public void test_getSubjectAsBytes() {
+    public void test_getSubjectAsBytes() throws Exception {
         byte[] name1 = new byte[]
         // manually obtained DER encoding of "O=First Org." issuer name;
-        { 48, 21, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 70, 105, 114, 115,
-                116, 32, 79, 114, 103, 46 };
+                { 48, 21, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 70, 105, 114, 115,
+                  116, 32, 79, 114, 103, 46 };
         byte[] name2 = new byte[]
         // manually obtained DER encoding of "O=Second Org." issuer name;
-        { 48, 22, 49, 20, 48, 18, 6, 3, 85, 4, 10, 19, 11, 83, 101, 99, 111,
-                110, 100, 32, 79, 114, 103, 46 };
+                { 48, 22, 49, 20, 48, 18, 6, 3, 85, 4, 10, 19, 11, 83, 101, 99, 111,
+                  110, 100, 32, 79, 114, 103, 46 };
 
         X500Principal sub1 = new X500Principal(name1);
         X500Principal sub2 = new X500Principal(name2);
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            assertNull("Selector should return null", selector
-                    .getSubjectAsBytes());
-            selector.setSubject(sub1);
-            assertTrue("The returned issuer should be equal to specified",
-                    Arrays.equals(name1, selector.getSubjectAsBytes()));
-            assertFalse("The returned issuer should differ", name2
-                    .equals(selector.getSubjectAsBytes()));
-            selector.setSubject(sub2);
-            assertTrue("The returned issuer should be equal to specified",
-                    Arrays.equals(name2, selector.getSubjectAsBytes()));
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        assertNull("Selector should return null",
+                   selector.getSubjectAsBytes());
+        selector.setSubject(sub1);
+        assertTrue("The returned issuer should be equal to specified",
+                   Arrays.equals(name1, selector.getSubjectAsBytes()));
+        assertFalse("The returned issuer should differ",
+                    name2.equals(selector.getSubjectAsBytes()));
+        selector.setSubject(sub2);
+        assertTrue("The returned issuer should be equal to specified",
+                   Arrays.equals(name2, selector.getSubjectAsBytes()));
     }
 
     /**
@@ -730,12 +690,12 @@
         assertNull("Selector should return null", selector.getSubjectAsString());
         selector.setSubject(sub1);
         assertEquals("The returned subject should be equal to specified",
-                name1, selector.getSubjectAsString());
-        assertFalse("The returned subject should differ", name2.equals(selector
-                .getSubjectAsString()));
+                     name1, selector.getSubjectAsString());
+        assertFalse("The returned subject should differ",
+                    name2.equals(selector.getSubjectAsString()));
         selector.setSubject(sub2);
         assertEquals("The returned subject should be equal to specified",
-                name2, selector.getSubjectAsString());
+                     name2, selector.getSubjectAsString());
     }
 
     /**
@@ -746,16 +706,15 @@
         byte[] skid2 = new byte[] { 4, 5, 5, 4, 3, 2, 1 }; // random value
         X509CertSelector selector = new X509CertSelector();
 
-        assertNull("Selector should return null", selector
-                .getSubjectKeyIdentifier());
+        assertNull("Selector should return null", selector.getSubjectKeyIdentifier());
         selector.setSubjectKeyIdentifier(skid1);
-        assertTrue("The returned keyID should be equal to specified", Arrays
-                .equals(skid1, selector.getSubjectKeyIdentifier()));
+        assertTrue("The returned keyID should be equal to specified",
+                   Arrays.equals(skid1, selector.getSubjectKeyIdentifier()));
         selector.getSubjectKeyIdentifier()[0]++;
-        assertTrue("The returned keyID should be equal to specified", Arrays
-                .equals(skid1, selector.getSubjectKeyIdentifier()));
-        assertFalse("The returned keyID should differ", Arrays.equals(skid2,
-                selector.getSubjectKeyIdentifier()));
+        assertTrue("The returned keyID should be equal to specified",
+                   Arrays.equals(skid1, selector.getSubjectKeyIdentifier()));
+        assertFalse("The returned keyID should differ",
+                    Arrays.equals(skid2, selector.getSubjectKeyIdentifier()));
     }
 
     /**
@@ -793,14 +752,12 @@
     /**
      * java.security.cert.X509CertSelector#getSubjectPublicKeyAlgID()
      */
-    public void test_getSubjectPublicKeyAlgID() {
+    public void test_getSubjectPublicKeyAlgID() throws Exception {
 
         X509CertSelector selector = new X509CertSelector();
-        String[] validOIDs = { "0.0.20", "1.25.0", "2.0.39", "0.2.10", "1.35.15",
-                "2.17.89" };
+        String[] validOIDs = { "0.0.20", "1.25.0", "2.0.39", "0.2.10", "1.35.15", "2.17.89" };
 
-        assertNull("Selector should return null", selector
-                .getSubjectPublicKeyAlgID());
+        assertNull("Selector should return null", selector.getSubjectPublicKeyAlgID());
 
         for (int i = 0; i < validOIDs.length; i++) {
             try {
@@ -815,33 +772,28 @@
         String pkaid1 = "1.2.840.113549.1.1.1"; // RSA encryption
         String pkaid2 = "1.2.840.113549.1.1.4"; // MD5 with RSA encryption
 
-        try {
-            selector.setSubjectPublicKeyAlgID(pkaid1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
-        assertTrue("The returned oid should be equal to specified", pkaid1
-                .equals(selector.getSubjectPublicKeyAlgID()));
-        assertFalse("The returned oid should differ", pkaid2.equals(selector
-                .getSubjectPublicKeyAlgID()));
+        selector.setSubjectPublicKeyAlgID(pkaid1);
+        assertTrue("The returned oid should be equal to specified",
+                   pkaid1.equals(selector.getSubjectPublicKeyAlgID()));
+        assertFalse("The returned oid should differ",
+                    pkaid2.equals(selector.getSubjectPublicKeyAlgID()));
     }
 
     /**
      * java.security.cert.X509CertSelector#match(java.security.cert.Certificate)
      */
-    public void test_matchLjava_security_cert_Certificate()
-            throws CertificateException {
+    public void test_matchLjava_security_cert_Certificate() throws Exception {
         X509CertSelector selector = new X509CertSelector();
         assertFalse(selector.match(null));
 
         CertificateFactory certFact = CertificateFactory.getInstance("X509");
-        X509Certificate cert1 = (X509Certificate) certFact
-                .generateCertificate(new ByteArrayInputStream(TestUtils
-                        .getX509Certificate_v3()));
+        X509Certificate cert1 = (X509Certificate)
+                certFact.generateCertificate(new ByteArrayInputStream(
+                        TestUtils.getX509Certificate_v3()));
 
-        X509Certificate cert2 = (X509Certificate) certFact
-                .generateCertificate(new ByteArrayInputStream(TestUtils
-                        .getX509Certificate_v1()));
+        X509Certificate cert2 = (X509Certificate)
+                certFact.generateCertificate(new ByteArrayInputStream(
+                        TestUtils.getX509Certificate_v1()));
 
         selector.setCertificate(cert1);
         assertTrue(selector.match(cert1));
@@ -855,7 +807,7 @@
     /**
      * java.security.cert.X509CertSelector#setAuthorityKeyIdentifier(byte[])
      */
-    public void test_setAuthorityKeyIdentifierLB$() throws CertificateException {
+    public void test_setAuthorityKeyIdentifierLB$() throws Exception {
         X509CertSelector selector = new X509CertSelector();
 
         byte[] akid1 = new byte[] { 1, 2, 3, 4, 5 }; // random value
@@ -895,9 +847,7 @@
         for (int i = 0; i < invalidValues.length; i++) {
             try {
                 selector.setBasicConstraints(-3);
-                fail("IllegalArgumentException expected");
-            } catch (IllegalArgumentException e) {
-                // expected
+            } catch (IllegalArgumentException expected) {
             }
         }
 
@@ -912,7 +862,7 @@
      * java.security.cert.X509CertSelector#setCertificate(java.security.cert.Certificate)
      */
     public void test_setCertificateLjava_security_cert_X509Certificate()
-            throws CertificateException {
+            throws Exception {
 
         TestCert cert1 = new TestCert("same certificate");
         TestCert cert2 = new TestCert("other certificate");
@@ -920,16 +870,16 @@
 
         selector.setCertificate(null);
         assertTrue("Any certificates should match in the case of null "
-                + "certificateEquals criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                + "certificateEquals criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setCertificate(cert1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setCertificate(cert2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
         selector.setCertificate(null);
         assertNull(selector.getCertificate());
     }
@@ -938,7 +888,7 @@
      * java.security.cert.X509CertSelector#setCertificateValid(java.util.Date)
      */
     public void test_setCertificateValidLjava_util_Date()
-            throws CertificateException {
+            throws Exception {
         X509CertSelector selector = new X509CertSelector();
 
         Date date1 = new Date(100);
@@ -962,50 +912,48 @@
     /**
      * java.security.cert.X509CertSelector#setExtendedKeyUsage(Set<String>)
      */
-    public void test_setExtendedKeyUsageLjava_util_Set()
-            throws CertificateException {
-        HashSet<String> ku1 = new HashSet<String>(Arrays
-                .asList(new String[] { "1.3.6.1.5.5.7.3.1",
-                        "1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.3",
-                        "1.3.6.1.5.5.7.3.4", "1.3.6.1.5.5.7.3.8",
-                        "1.3.6.1.5.5.7.3.9", "1.3.6.1.5.5.7.3.5",
-                        "1.3.6.1.5.5.7.3.6", "1.3.6.1.5.5.7.3.7" }));
+    public void test_setExtendedKeyUsageLjava_util_Set() throws Exception {
+        HashSet<String> ku1 = new HashSet<String>(Arrays.asList(new String[] {
+            "1.3.6.1.5.5.7.3.1",
+            "1.3.6.1.5.5.7.3.2",
+            "1.3.6.1.5.5.7.3.3",
+            "1.3.6.1.5.5.7.3.4",
+            "1.3.6.1.5.5.7.3.8",
+            "1.3.6.1.5.5.7.3.9",
+            "1.3.6.1.5.5.7.3.5",
+            "1.3.6.1.5.5.7.3.6",
+            "1.3.6.1.5.5.7.3.7"
+        }));
         HashSet<String> ku2 = new HashSet<String>(Arrays.asList(new String[] {
-                "1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.3",
-                "1.3.6.1.5.5.7.3.4", "1.3.6.1.5.5.7.3.8", "1.3.6.1.5.5.7.3.9",
-                "1.3.6.1.5.5.7.3.5", "1.3.6.1.5.5.7.3.6" }));
+            "1.3.6.1.5.5.7.3.1",
+            "1.3.6.1.5.5.7.3.2",
+            "1.3.6.1.5.5.7.3.3",
+            "1.3.6.1.5.5.7.3.4",
+            "1.3.6.1.5.5.7.3.8",
+            "1.3.6.1.5.5.7.3.9",
+            "1.3.6.1.5.5.7.3.5",
+            "1.3.6.1.5.5.7.3.6"
+        }));
         TestCert cert1 = new TestCert(ku1);
         TestCert cert2 = new TestCert(ku2);
 
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            selector.setExtendedKeyUsage(null);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setExtendedKeyUsage(null);
         assertTrue("Any certificate should match in the case of null "
-                + "extendedKeyUsage criteria.", selector.match(cert1)
-                && selector.match(cert2));
-        try {
-            selector.setExtendedKeyUsage(ku1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                   + "extendedKeyUsage criteria.",
+                   selector.match(cert1)&& selector.match(cert2));
+        selector.setExtendedKeyUsage(ku1);
         assertEquals(ku1, selector.getExtendedKeyUsage());
 
-        try {
-            selector.setExtendedKeyUsage(ku2);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setExtendedKeyUsage(ku2);
         assertEquals(ku2, selector.getExtendedKeyUsage());
     }
 
     /**
      * java.security.cert.X509CertSelector#setIssuer(byte[])
      */
-    public void test_setIssuerLB$() throws CertificateException {
+    public void test_setIssuerLB$() throws Exception {
         byte[] name1 = new byte[]
         // manually obtained DER encoding of "O=First Org." issuer name;
         { 48, 21, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 70, 105, 114, 115,
@@ -1021,36 +969,24 @@
 
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            selector.setIssuer((byte[]) null);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setIssuer((byte[]) null);
         assertTrue("Any certificates should match "
-                + "in the case of null issuer criteria.", selector.match(cert1)
-                && selector.match(cert2));
-        try {
-            selector.setIssuer(name1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                   + "in the case of null issuer criteria.", selector.match(cert1)
+                   && selector.match(cert2));
+        selector.setIssuer(name1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
-        try {
-            selector.setIssuer(name2);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                    selector.match(cert2));
+        selector.setIssuer(name2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setIssuer(java.lang.String)
      */
-    public void test_setIssuerLjava_lang_String() throws CertificateException {
+    public void test_setIssuerLjava_lang_String() throws Exception {
 
         String name1 = "O=First Org.";
         String name2 = "O=Second Org.";
@@ -1061,37 +997,25 @@
 
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            selector.setIssuer((String) null);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setIssuer((String) null);
         assertTrue("Any certificates should match "
-                + "in the case of null issuer criteria.", selector.match(cert1)
-                && selector.match(cert2));
-        try {
-            selector.setIssuer(name1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                   + "in the case of null issuer criteria.",
+                   selector.match(cert1) && selector.match(cert2));
+        selector.setIssuer(name1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
-        try {
-            selector.setIssuer(name2);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                    selector.match(cert2));
+        selector.setIssuer(name2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setIssuer(javax.security.auth.x500.X500Principal)
      */
     public void test_setIssuerLjavax_security_auth_x500_X500Principal()
-            throws CertificateException {
+            throws Exception {
         X500Principal iss1 = new X500Principal("O=First Org.");
         X500Principal iss2 = new X500Principal("O=Second Org.");
         TestCert cert1 = new TestCert(iss1);
@@ -1100,22 +1024,22 @@
 
         selector.setIssuer((X500Principal) null);
         assertTrue("Any certificates should match "
-                + "in the case of null issuer criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                   + "in the case of null issuer criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setIssuer(iss1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setIssuer(iss2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setKeyUsage(boolean)
      */
-    public void test_setKeyUsageZ() throws CertificateException {
+    public void test_setKeyUsageZ() throws Exception {
         boolean[] ku1 = new boolean[] { true, true, true, true, true, true,
                 true, true, true };
         // decipherOnly is disallowed
@@ -1128,17 +1052,16 @@
         X509CertSelector selector = new X509CertSelector();
 
         selector.setKeyUsage(null);
-        assertTrue("Any certificate should match in the case of null "
-                + "keyUsage criteria.", selector.match(cert1)
-                && selector.match(cert2));
+        assertTrue("Any certificate should match in the case of null keyUsage criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setKeyUsage(ku1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         assertTrue("The certificate which does not have a keyUsage extension "
-                + "implicitly allows all keyUsage values.", selector
-                .match(cert3));
+                   + "implicitly allows all keyUsage values.",
+                   selector.match(cert3));
         selector.setKeyUsage(ku2);
         ku2[0] = !ku2[0];
         assertTrue("The certificate should match the selection criteria.",
@@ -1186,67 +1109,67 @@
 
         for (int i = 0; i < constraintBytes.length; i++) {
             selector.setNameConstraints(constraintBytes[i]);
-            assertTrue(Arrays.equals(constraintBytes[i], selector
-                    .getNameConstraints()));
+            assertTrue(Arrays.equals(constraintBytes[i], selector.getNameConstraints()));
         }
     }
 
     /**
      * java.security.cert.X509CertSelector#setPathToNames(Collection<List<?>>)
      */
-    public void test_setPathToNamesLjava_util_Collection() {
-        try {
-            GeneralName san0 = new GeneralName(new OtherName("1.2.3.4.5",
-                    new byte[] { 1, 2, 0, 1 }));
-            GeneralName san1 = new GeneralName(1, "rfc@822.Name");
-            GeneralName san2 = new GeneralName(2, "dNSName");
-            GeneralName san3 = new GeneralName(new ORAddress());
-            GeneralName san4 = new GeneralName(new Name("O=Organization"));
-            GeneralName san6 = new GeneralName(6, "http://uniform.Resource.Id");
-            GeneralName san7 = new GeneralName(7, "1.1.1.1");
-            GeneralName san8 = new GeneralName(8, "1.2.3.4444.55555");
+    public void test_setPathToNamesLjava_util_Collection() throws Exception {
+        GeneralName san0 = new GeneralName(new OtherName("1.2.3.4.5",
+                                                         new byte[] { 1, 2, 0, 1 }));
+        GeneralName san1 = new GeneralName(1, "rfc@822.Name");
+        GeneralName san2 = new GeneralName(2, "dNSName");
+        GeneralName san3 = new GeneralName(new ORAddress());
+        GeneralName san4 = new GeneralName(new Name("O=Organization"));
+        GeneralName san6 = new GeneralName(6, "http://uniform.Resource.Id");
+        GeneralName san7 = new GeneralName(7, "1.1.1.1");
+        GeneralName san8 = new GeneralName(8, "1.2.3.4444.55555");
 
-            GeneralNames sans1 = new GeneralNames();
-            sans1.addName(san0);
-            sans1.addName(san1);
-            sans1.addName(san2);
-            sans1.addName(san3);
-            sans1.addName(san4);
-            sans1.addName(san6);
-            sans1.addName(san7);
-            sans1.addName(san8);
-            GeneralNames sans2 = new GeneralNames();
-            sans2.addName(san0);
+        GeneralNames sans1 = new GeneralNames();
+        sans1.addName(san0);
+        sans1.addName(san1);
+        sans1.addName(san2);
+        sans1.addName(san3);
+        sans1.addName(san4);
+        sans1.addName(san6);
+        sans1.addName(san7);
+        sans1.addName(san8);
+        GeneralNames sans2 = new GeneralNames();
+        sans2.addName(san0);
 
-            TestCert cert1 = new TestCert(sans1);
-            TestCert cert2 = new TestCert(sans2);
-            X509CertSelector selector = new X509CertSelector();
-            selector.setMatchAllSubjectAltNames(true);
+        TestCert cert1 = new TestCert(sans1);
+        TestCert cert2 = new TestCert(sans2);
+        X509CertSelector selector = new X509CertSelector();
+        selector.setMatchAllSubjectAltNames(true);
 
-            selector.setPathToNames(null);
-            assertTrue("Any certificate should match in the case of null "
-                    + "subjectAlternativeNames criteria.", selector
-                    .match(cert1)
-                    && selector.match(cert2));
+        selector.setPathToNames(null);
+        assertTrue("Any certificate should match in the case of null "
+                   + "subjectAlternativeNames criteria.",
+                   selector.match(cert1) && selector.match(cert2));
 
-            Collection<List<?>> sans = sans1.getPairsList();
+        Collection<List<?>> sans = sans1.getPairsList();
 
-            selector.setPathToNames(sans);
-            selector.getPathToNames();
-        } catch (IOException e) {
-            e.printStackTrace();
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setPathToNames(sans);
+        selector.getPathToNames();
     }
 
     /**
      * java.security.cert.X509CertSelector#setPolicy(Set<String>)
      */
     public void test_setPolicyLjava_util_Set() throws IOException {
-        String[] policies1 = new String[] { "1.3.6.1.5.5.7.3.1",
-                "1.3.6.1.5.5.7.3.2", "1.3.6.1.5.5.7.3.3", "1.3.6.1.5.5.7.3.4",
-                "1.3.6.1.5.5.7.3.8", "1.3.6.1.5.5.7.3.9", "1.3.6.1.5.5.7.3.5",
-                "1.3.6.1.5.5.7.3.6", "1.3.6.1.5.5.7.3.7" };
+        String[] policies1 = new String[] { 
+            "1.3.6.1.5.5.7.3.1",
+            "1.3.6.1.5.5.7.3.2",
+            "1.3.6.1.5.5.7.3.3",
+            "1.3.6.1.5.5.7.3.4",
+            "1.3.6.1.5.5.7.3.8",
+            "1.3.6.1.5.5.7.3.9",
+            "1.3.6.1.5.5.7.3.5",
+            "1.3.6.1.5.5.7.3.6",
+            "1.3.6.1.5.5.7.3.7"
+        };
 
         String[] policies2 = new String[] { "1.3.6.7.3.1" };
 
@@ -1260,27 +1183,27 @@
 
         selector.setPolicy(null);
         assertTrue("Any certificate should match in the case of null "
-                + "privateKeyValid criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                + "privateKeyValid criteria.",
+                   selector.match(cert1) && selector.match(cert2));
 
         selector.setPolicy(p1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
 
         selector.setPolicy(p2);
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert1));
+                    selector.match(cert1));
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setPrivateKeyValid(java.util.Date)
      */
     public void test_setPrivateKeyValidLjava_util_Date()
-            throws CertificateException {
+            throws Exception {
         Date date1 = new Date(100000000);
         Date date2 = new Date(200000000);
         Date date3 = new Date(300000000);
@@ -1293,24 +1216,24 @@
 
         selector.setPrivateKeyValid(null);
         assertTrue("Any certificate should match in the case of null "
-                + "privateKeyValid criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                + "privateKeyValid criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setPrivateKeyValid(date4);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setPrivateKeyValid(date5);
         date5.setTime(date4.getTime());
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setSerialNumber(java.math.BigInteger)
      */
     public void test_setSerialNumberLjava_math_BigInteger()
-            throws CertificateException {
+            throws Exception {
         BigInteger ser1 = new BigInteger("10000");
         BigInteger ser2 = new BigInteger("10001");
         TestCert cert1 = new TestCert(ser1);
@@ -1319,22 +1242,22 @@
 
         selector.setSerialNumber(null);
         assertTrue("Any certificate should match in the case of null "
-                + "serialNumber criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                   + "serialNumber criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setSerialNumber(ser1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setSerialNumber(ser2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setSubject(byte[])
      */
-    public void test_setSubjectLB$() throws CertificateException {
+    public void test_setSubjectLB$() throws Exception {
         byte[] name1 = new byte[]
         // manually obtained DER encoding of "O=First Org." issuer name;
         { 48, 21, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 70, 105, 114, 115,
@@ -1350,36 +1273,24 @@
 
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            selector.setSubject((byte[]) null);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setSubject((byte[]) null);
         assertTrue("Any certificates should match "
-                + "in the case of null issuer criteria.", selector.match(cert1)
-                && selector.match(cert2));
-        try {
-            selector.setSubject(name1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                   + "in the case of null issuer criteria.",
+                   selector.match(cert1) && selector.match(cert2));
+        selector.setSubject(name1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
-        try {
-            selector.setSubject(name2);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                    selector.match(cert2));
+        selector.setSubject(name2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setSubject(java.lang.String)
      */
-    public void test_setSubjectLjava_lang_String() throws CertificateException {
+    public void test_setSubjectLjava_lang_String() throws Exception {
         String name1 = "O=First Org.";
         String name2 = "O=Second Org.";
         X500Principal sub1 = new X500Principal(name1);
@@ -1388,38 +1299,25 @@
         TestCert cert2 = new TestCert(sub2);
         X509CertSelector selector = new X509CertSelector();
 
-        try {
-            selector.setSubject((String) null);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setSubject((String) null);
         assertTrue("Any certificates should match "
-                + "in the case of null subject criteria.", selector
-                .match(cert1)
-                && selector.match(cert2));
-        try {
-            selector.setSubject(name1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                   + "in the case of null subject criteria.",
+                   selector.match(cert1) && selector.match(cert2));
+        selector.setSubject(name1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
-        try {
-            selector.setSubject(name2);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                    selector.match(cert2));
+        selector.setSubject(name2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setSubject(javax.security.auth.x500.X500Principal)
      */
     public void test_setSubjectLjavax_security_auth_x500_X500Principal()
-            throws CertificateException {
+            throws Exception {
         X500Principal sub1 = new X500Principal("O=First Org.");
         X500Principal sub2 = new X500Principal("O=Second Org.");
         TestCert cert1 = new TestCert(sub1);
@@ -1428,73 +1326,66 @@
 
         selector.setSubject((X500Principal) null);
         assertTrue("Any certificates should match "
-                + "in the case of null subjcet criteria.", selector
-                .match(cert1)
-                && selector.match(cert2));
+                   + "in the case of null subjcet criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setSubject(sub1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setSubject(sub2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setSubjectAlternativeNames(Collection<List<?>>)
      */
-    public void test_setSubjectAlternativeNamesLjava_util_Collection() {
+    public void test_setSubjectAlternativeNamesLjava_util_Collection() throws Exception {
 
-        try {
-            GeneralName san0 = new GeneralName(new OtherName("1.2.3.4.5",
-                    new byte[] { 1, 2, 0, 1 }));
-            GeneralName san1 = new GeneralName(1, "rfc@822.Name");
-            GeneralName san2 = new GeneralName(2, "dNSName");
-            GeneralName san3 = new GeneralName(new ORAddress());
-            GeneralName san4 = new GeneralName(new Name("O=Organization"));
-            GeneralName san6 = new GeneralName(6, "http://uniform.Resource.Id");
-            GeneralName san7 = new GeneralName(7, "1.1.1.1");
-            GeneralName san8 = new GeneralName(8, "1.2.3.4444.55555");
+        GeneralName san0 = new GeneralName(new OtherName("1.2.3.4.5",
+                                                         new byte[] { 1, 2, 0, 1 }));
+        GeneralName san1 = new GeneralName(1, "rfc@822.Name");
+        GeneralName san2 = new GeneralName(2, "dNSName");
+        GeneralName san3 = new GeneralName(new ORAddress());
+        GeneralName san4 = new GeneralName(new Name("O=Organization"));
+        GeneralName san6 = new GeneralName(6, "http://uniform.Resource.Id");
+        GeneralName san7 = new GeneralName(7, "1.1.1.1");
+        GeneralName san8 = new GeneralName(8, "1.2.3.4444.55555");
 
-            GeneralNames sans1 = new GeneralNames();
-            sans1.addName(san0);
-            sans1.addName(san1);
-            sans1.addName(san2);
-            sans1.addName(san3);
-            sans1.addName(san4);
-            sans1.addName(san6);
-            sans1.addName(san7);
-            sans1.addName(san8);
-            GeneralNames sans2 = new GeneralNames();
-            sans2.addName(san0);
+        GeneralNames sans1 = new GeneralNames();
+        sans1.addName(san0);
+        sans1.addName(san1);
+        sans1.addName(san2);
+        sans1.addName(san3);
+        sans1.addName(san4);
+        sans1.addName(san6);
+        sans1.addName(san7);
+        sans1.addName(san8);
+        GeneralNames sans2 = new GeneralNames();
+        sans2.addName(san0);
 
-            TestCert cert1 = new TestCert(sans1);
-            TestCert cert2 = new TestCert(sans2);
-            X509CertSelector selector = new X509CertSelector();
-            selector.setMatchAllSubjectAltNames(true);
+        TestCert cert1 = new TestCert(sans1);
+        TestCert cert2 = new TestCert(sans2);
+        X509CertSelector selector = new X509CertSelector();
+        selector.setMatchAllSubjectAltNames(true);
 
-            selector.setSubjectAlternativeNames(null);
-            assertTrue("Any certificate should match in the case of null "
-                    + "subjectAlternativeNames criteria.", selector
-                    .match(cert1)
-                    && selector.match(cert2));
+        selector.setSubjectAlternativeNames(null);
+        assertTrue("Any certificate should match in the case of null "
+                   + "subjectAlternativeNames criteria.",
+                   selector.match(cert1) && selector.match(cert2));
 
-            Collection<List<?>> sans = sans1.getPairsList();
+        Collection<List<?>> sans = sans1.getPairsList();
 
-            selector.setSubjectAlternativeNames(sans);
+        selector.setSubjectAlternativeNames(sans);
 
-            selector.getSubjectAlternativeNames();
-        } catch (IOException e) {
-            e.printStackTrace();
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.getSubjectAlternativeNames();
     }
 
     /**
      * java.security.cert.X509CertSelector#setSubjectKeyIdentifier(byte[])
      */
-    public void test_setSubjectKeyIdentifierLB$() throws CertificateException {
+    public void test_setSubjectKeyIdentifierLB$() throws Exception {
         byte[] skid1 = new byte[] { 1, 2, 3, 4, 5 }; // random value
         byte[] skid2 = new byte[] { 5, 4, 3, 2, 1 }; // random value
         TestCert cert1 = new TestCert(skid1);
@@ -1503,17 +1394,17 @@
 
         selector.setSubjectKeyIdentifier(null);
         assertTrue("Any certificate should match in the case of null "
-                + "serialNumber criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                + "serialNumber criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setSubjectKeyIdentifier(skid1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setSubjectKeyIdentifier(skid2);
         skid2[0]++;
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
@@ -1545,15 +1436,9 @@
      * java.security.cert.X509CertSelector#setSubjectPublicKey(java.security.PublicKey key)
      */
     public void test_setSubjectPublicKeyLjava_security_PublicKey()
-            throws CertificateException {
-        PublicKey pkey1 = null;
-        PublicKey pkey2 = null;
-        try {
-            pkey1 = new TestKeyPair("RSA").getPublic();
-            pkey2 = new TestKeyPair("DSA").getPublic();
-        } catch (Exception e) {
-            fail("Unexpected Exception was thrown: " + e.getMessage());
-        }
+            throws Exception {
+        PublicKey pkey1 = new TestKeyPair("RSA").getPublic();
+        PublicKey pkey2 = new TestKeyPair("DSA").getPublic();
 
         TestCert cert1 = new TestCert(pkey1);
         TestCert cert2 = new TestCert(pkey2);
@@ -1561,90 +1446,75 @@
 
         selector.setSubjectPublicKey((PublicKey) null);
         assertTrue("Any certificate should match in the case of null "
-                + "subjectPublicKey criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                   + "subjectPublicKey criteria.",
+                   selector.match(cert1) && selector.match(cert2));
         selector.setSubjectPublicKey(pkey1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
+                    selector.match(cert2));
         selector.setSubjectPublicKey(pkey2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
      * java.security.cert.X509CertSelector#setSubjectPublicKeyAlgID(java.lang.String)
      */
-    public void test_setSubjectPublicKeyAlgIDLjava_lang_String()
-            throws CertificateException {
+    public void test_setSubjectPublicKeyAlgIDLjava_lang_String() throws Exception {
 
         X509CertSelector selector = new X509CertSelector();
         String pkaid1 = "1.2.840.113549.1.1.1"; // RSA (source:
         // http://asn1.elibel.tm.fr)
         String pkaid2 = "1.2.840.10040.4.1"; // DSA (source:
         // http://asn1.elibel.tm.fr)
-        PublicKey pkey1;
-        PublicKey pkey2;
-        try {
-            pkey1 = new TestKeyPair("RSA").getPublic();
-            pkey2 = new TestKeyPair("DSA").getPublic();
-        } catch (Exception e) {
-            e.printStackTrace();
-            fail("Unexpected Exception was thrown: " + e.getMessage());
-            return;
-        }
+        PublicKey pkey1 = new TestKeyPair("RSA").getPublic();;
+        PublicKey pkey2 = new TestKeyPair("DSA").getPublic();;
+
         TestCert cert1 = new TestCert(pkey1);
         TestCert cert2 = new TestCert(pkey2);
 
-        try {
-            selector.setSubjectPublicKeyAlgID(null);
-        } catch (IOException e) {
-
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setSubjectPublicKeyAlgID(null);
         assertTrue("Any certificate should match in the case of null "
-                + "subjectPublicKeyAlgID criteria.", selector.match(cert1)
-                && selector.match(cert2));
+                   + "subjectPublicKeyAlgID criteria.",
+                   selector.match(cert1) && selector.match(cert2));
 
-        String[] validOIDs = { "0.0.20", "1.25.0", "2.0.39", "0.2.10", "1.35.15",
-                "2.17.89", "2.5.29.16", "2.5.29.17", "2.5.29.30", "2.5.29.32",
-                "2.5.29.37" };
+        String[] validOIDs = {
+            "0.0.20",
+            "1.25.0",
+            "2.0.39",
+            "0.2.10",
+            "1.35.15",
+            "2.17.89",
+            "2.5.29.16",
+            "2.5.29.17",
+            "2.5.29.30",
+            "2.5.29.32",
+            "2.5.29.37"
+        };
 
         for (int i = 0; i < validOIDs.length; i++) {
-            try {
-                selector.setSubjectPublicKeyAlgID(validOIDs[i]);
-                assertEquals(validOIDs[i], selector.getSubjectPublicKeyAlgID());
-            } catch (IOException e) {
-                fail("Unexpected exception " + e.getMessage());
-            }
+            selector.setSubjectPublicKeyAlgID(validOIDs[i]);
+            assertEquals(validOIDs[i], selector.getSubjectPublicKeyAlgID());
         }
 
-        String[] invalidOIDs = { "0.20", "1.25", "2.39", "3.10"};
+        String[] invalidOIDs = { "0.20", "1.25", "2.39", "3.10" };
         for (int i = 0; i < invalidOIDs.length; i++) {
             try {
                 selector.setSubjectPublicKeyAlgID(invalidOIDs[i]);
                 fail("IOException wasn't thrown for " + invalidOIDs[i]);
-            } catch (IOException e) {
+            } catch (IOException expected) {
             }
         }
 
-        try {
-            selector.setSubjectPublicKeyAlgID(pkaid1);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+        selector.setSubjectPublicKeyAlgID(pkaid1);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert1));
+                   selector.match(cert1));
         assertFalse("The certificate should not match the selection criteria.",
-                selector.match(cert2));
-        try {
-            selector.setSubjectPublicKeyAlgID(pkaid2);
-        } catch (IOException e) {
-            fail("Unexpected IOException was thrown.");
-        }
+                    selector.match(cert2));
+        selector.setSubjectPublicKeyAlgID(pkaid2);
         assertTrue("The certificate should match the selection criteria.",
-                selector.match(cert2));
+                   selector.match(cert2));
     }
 
     /**
@@ -1792,13 +1662,11 @@
         }
 
         public void setExtendedKeyUsage(Set<String> extKeyUsage) {
-            this.extKeyUsage = (extKeyUsage == null) ? null : new ArrayList<String>(
-                    extKeyUsage);
+            this.extKeyUsage = (extKeyUsage == null) ? null : new ArrayList<String>(extKeyUsage);
         }
 
         public void setKeyUsage(boolean[] keyUsage) {
-            this.keyUsage = (keyUsage == null) ? null : (boolean[]) keyUsage
-                    .clone();
+            this.keyUsage = (keyUsage == null) ? null : (boolean[]) keyUsage.clone();
         }
 
         public void setPublicKey(PublicKey key) {
@@ -2009,8 +1877,7 @@
                                 ASN1Integer.getInstance() })
                                 .encode(new Object[] {
                                         new Boolean(pathLen != 1),
-                                        BigInteger.valueOf(pathLen)
-                                                .toByteArray() }));
+                                        BigInteger.valueOf(pathLen).toByteArray() }));
             }
             if ("2.5.29.17".equals(oid) && (sans != null)) {
                 if (sans.getNames() == null) {
@@ -2074,8 +1941,9 @@
         BigInteger revokedSerialNumber = BigInteger.valueOf(1);
         crl = new MyCRL("X.509");
 //        X509CRL rootCRL = X509CRL;
-//        X509CRL interCRL = X509CRLExample.createCRL(interCert, interPair
-//                .getPrivate(), revokedSerialNumber);
+//        X509CRL interCRL = X509CRLExample.createCRL(interCert,
+//                                                    interPair.getPrivate(),
+//                                                    revokedSerialNumber);
 
         // create CertStore to support path building
         List<Object> list = new ArrayList<Object>();
@@ -2083,16 +1951,14 @@
         list.add(rootCertificate);
         list.add(endCertificate);
 
-//        CollectionCertStoreParameters params = new CollectionCertStoreParameters(
-//                list);
+//        CollectionCertStoreParameters params = new CollectionCertStoreParameters(list);
 //        CertStore store = CertStore.getInstance("Collection", params);
 //
         theCertSelector = new X509CertSelector();
         theCertSelector.setCertificate(endCertificate);
-        theCertSelector.setIssuer(endCertificate.getIssuerX500Principal()
-                .getEncoded());
+        theCertSelector.setIssuer(endCertificate.getIssuerX500Principal().getEncoded());
 
-     // build the path
+        // build the path
         builder = CertPathBuilder.getInstance("PKIX");
 
     }
@@ -2103,8 +1969,7 @@
                 Collections.singleton(new TrustAnchor(rootCertificate, null)),
                 theCertSelector);
         try {
-        result = (PKIXCertPathBuilderResult) builder
-        .build(buildParams);
+        result = (PKIXCertPathBuilderResult) builder.build(buildParams);
         } catch(CertPathBuilderException e) {
             return null;
         }
diff --git a/luni/src/test/java/tests/security/cert/X509Certificate2Test.java b/luni/src/test/java/tests/security/cert/X509Certificate2Test.java
index 5578234..03b0243 100644
--- a/luni/src/test/java/tests/security/cert/X509Certificate2Test.java
+++ b/luni/src/test/java/tests/security/cert/X509Certificate2Test.java
@@ -51,15 +51,13 @@
 
         // extension value is empty sequence
         byte[] extnValue = pemCert.getExtensionValue("2.5.29.35");
-        assertTrue(Arrays
-                .equals(new byte[] {0x04, 0x02, 0x30, 0x00}, extnValue));
+        assertEquals(
+                Arrays.toString(new byte[] { 0x04, 0x02, 0x30, 0x00 }),
+                Arrays.toString(extnValue));
         assertNotNull(pemCert.toString());
         // End regression for HARMONY-3384
     }
 
-    /**
-     * java.security.cert.X509Certificate#X509Certificate()
-     */
     public void test_X509Certificate() {
         MyX509Certificate s = null;
         try {
@@ -99,7 +97,7 @@
     // (see RFC 3280 at http://www.ietf.org/rfc/rfc3280.txt)
     // (generated by using of classes from
     // org.apache.harmony.security.x509 package)
-    static String base64cert =
+    private static String CERT =
         "MIIByzCCATagAwIBAgICAiswCwYJKoZIhvcNAQEFMB0xGzAZBgNVBAoT"
             + "EkNlcnRpZmljYXRlIElzc3VlcjAeFw0wNjA0MjYwNjI4MjJaFw0zMzAz"
             + "MDExNjQ0MDlaMB0xGzAZBgNVBAoTEkNlcnRpZmljYXRlIElzc3VlcjCB"
@@ -112,7 +110,7 @@
             + "XEa7ONzcHQTYTG10poHfOK/a0BaULF3GlctDESilwQYbW5BdfpAlZpbH"
             + "AFLcUDh6Eq50kc0A/anh/j3mgBNuvbIMo7hHNnZB6k/prswm2BszyLD"
             + "yw==";
-    static String base64certCorrect =
+    private static String CERT_CORRECT =
         "-----BEGIN CERTIFICATE-----\n"
         + "MIIC+jCCAragAwIBAgICAiswDAYHKoZIzjgEAwEBADAdMRswGQYDVQQKExJDZXJ0a"
         + "WZpY2F0ZSBJc3N1ZXIwIhgPMTk3MDAxMTIxMzQ2NDBaGA8xOTcwMDEyNDAzMzMyMF"
@@ -132,9 +130,7 @@
         + "7jrj84/GZlhm09DsCFQCBKGKCGbrP64VtUt4JPmLjW1VxQA==\n"
         + "-----END CERTIFICATE-----";
 
-    private X509Certificate cert;
-
-    static String base64certTampered = "-----BEGIN CERTIFICATE-----\n"
+    private static String CERT_TAMPERED = "-----BEGIN CERTIFICATE-----\n"
         + "MIIC+jCCAragAwIBAgICAiswDAYHKoZIzjgEAwEBADAdMRswGQYDVQQKExJDZXJ0a"
         + "WZpY2F0ZSBJc3N1ZXIwIhgPMTk3MDAxMTIxMzQ2NDBaGA8xOTcwMDEyNDAzMzMyMF"
         + "owHzEdMBsGA1UEChMUU3ViamVjdCBPcmdhbml6YXRpb24wGTAMBgcqhkjOOAQDAQE"
@@ -157,7 +153,7 @@
     // (see RFC 3280 at http://www.ietf.org/rfc/rfc3280.txt)
     // (generated by using of classes from
     // org.apache.harmony.security.x509 package)
-    static String base64crl =
+    private static String CRL =
         "MIHXMIGXAgEBMAkGByqGSM44BAMwFTETMBEGA1UEChMKQ1JMIElzc3Vl"
             + "chcNMDYwNDI3MDYxMzQ1WhcNMDYwNDI3MDYxNTI1WjBBMD8CAgIrFw0w"
             + "NjA0MjcwNjEzNDZaMCowCgYDVR0VBAMKAQEwHAYDVR0YBBUYEzIwMDYw"
@@ -272,16 +268,10 @@
         }
     }
 
-    /**
-     * java.security.cert.X509Certificate#getType()
-     */
     public void testGetType() {
         assertEquals("X.509", new MyX509Certificate().getType());
     }
 
-    /**
-     * java.security.cert.X509Certificate#getIssuerX500Principal()
-     */
     public void testGetIssuerX500Principal() {
         // return valid encoding
         MyX509Certificate cert = new MyX509Certificate() {
@@ -295,9 +285,6 @@
         assertEquals(new X500Principal("CN=Z"), cert.getIssuerX500Principal());
     }
 
-    /**
-     * java.security.cert.X509Certificate#getSubjectX500Principal()
-     */
     public void testGetSubjectX500Principal() {
         // return valid encoding
         MyX509Certificate cert = new MyX509Certificate() {
@@ -311,123 +298,213 @@
         assertEquals(new X500Principal("CN=Y"), cert.getSubjectX500Principal());
     }
 
-    /**
-     * @throws CertificateException
-     * java.security.cert.X509Certificate#getExtendedKeyUsage()
-     */
-    public void testGetExtendedKeyUsage() throws CertificateException {
+    public void testGetExtendedKeyUsage() throws Exception {
         assertNull(new MyX509Certificate().getExtendedKeyUsage());
-
+        X509Certificate cert = generateCert(CERT_CORRECT);
         List<String> l = cert.getExtendedKeyUsage();
         assertNotNull(l);
 
         try {
             l.clear();
-        } catch (Exception e) {
-            // ok
+        } catch (UnsupportedOperationException expected) {
         }
 
         try {
             l.add("Test");
-        } catch (Exception e) {
-            // ok
+        } catch (UnsupportedOperationException expected) {
         }
 
         try {
-            if (l.size() > 0) {
-                l.remove(0);
-            }
-        } catch (Exception e) {
-            // ok
-        }
-
-    }
-
-    /**
-     * java.security.cert.X509Certificate#getSubjectAlternativeNames()
-     */
-    public void testGetSubjectAlternativeNames()
-            throws CertificateParsingException {
-
-        assertNull(new MyX509Certificate().getSubjectAlternativeNames());
-
-        Collection<List<?>> coll = cert.getSubjectAlternativeNames();
-        //getSubjectAlternativeNames method is not supported
-        assertNotNull(coll);
-
-        try {
-            coll.clear();
-        } catch (Exception e) {
-            // ok
-        }
-
-        try {
-            if (coll.size() > 0) {
-                coll.remove(0);
-            }
-        } catch (Exception e) {
-            // ok
-        }
-
-        assertTrue(coll.size() < 10);
-
-    }
-
-    /**
-     * java.security.cert.X509Certificate#getIssuerAlternativeNames()
-     */
-    public void testGetIssuerAlternativeNames()
-            throws CertificateParsingException {
-
-        assertNull(new MyX509Certificate().getIssuerAlternativeNames());
-
-        Collection<List<?>> coll = cert.getIssuerAlternativeNames();
-        // getIssuerAlternativeNames returns null.
-        assertNotNull(coll);
-
-        try {
-            coll.clear();
-        } catch (Exception e) {
-            // ok
-        }
-
-        try {
-            if (coll.size() > 0) {
-                coll.remove(0);
-            }
-        } catch (Exception e) {
-            // ok
-        }
-
-        assertTrue(coll.size() < 10);
-    }
-
-    public void testCerficateException() {
-        try {
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            ByteArrayInputStream bais = new ByteArrayInputStream(
-                    base64certTampered.getBytes());
-            cert = (X509Certificate) cf.generateCertificate(bais);
-        } catch (CertificateException e) {
-            // ok
-        }
-
-        try {
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            ByteArrayInputStream bais = new ByteArrayInputStream(base64cert
-                    .getBytes());
-            cert = (X509Certificate) cf.generateCertificate(bais);
-        } catch (CertificateException e) {
-            // ok
+            l.remove(0);
+        } catch (UnsupportedOperationException expected) {
         }
     }
 
-    public void setUp() throws Exception {
-        super.setUp();
+    private static final String CERT_WITHOUT_BASIC
+        = ("-----BEGIN CERTIFICATE-----\n"
+           + "MIIG9TCCBd2gAwIBAgIPLXR4AWpp9+O6Jn4rZpkgMA0GCSqGSIb3DQEBBQUAME0x\n"
+           + "CzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxJzAlBgNVBAMTHlN3\n"
+           + "aXNzU2lnbiBFViBHb2xkIENBIDIwMDkgLSBHMjAeFw0xMjA3MjYwODU4MTNaFw0x\n"
+           + "NDA3MjYwODU4MTNaMIIBITELMAkGA1UEBhMCQ0gxEDAOBgNVBAgMB1rDvHJpY2gx\n"
+           + "EzARBgNVBAcTCkdsYXR0YnJ1Z2cxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEWMBQG\n"
+           + "A1UEAxMNc3dpc3NzaWduLmNvbTEnMCUGCSqGSIb3DQEJARYYb3BlcmF0aW9uc0Bz\n"
+           + "d2lzc3NpZ24uY29tMRswGQYDVQQJDBJTw6RnZXJlaXN0cmFzc2UgMjUxDTALBgNV\n"
+           + "BBETBDgxNTIxEzARBgsrBgEEAYI3PAIBAxMCQ0gxGDAWBgsrBgEEAYI3PAIBAgwH\n"
+           + "WsO8cmljaDEbMBkGA1UEBRMSQ0gtMDIwLjMuMDI1LjExMC03MRswGQYDVQQPExJW\n"
+           + "MS4wLCBDbGF1c2UgNS4oYikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\n"
+           + "AQDLjzHfEcDeIwdEatC73JRs/xRaDLDmzwwHSZCjCvIKe8/yXxLR3cUIBG8mKrql\n"
+           + "1yICAMEpNM7J/fwN248OV6X/UosJpC4vmbpzgAN8y2q1DGnOyX7Eyi3UDXLTXtfA\n"
+           + "4294BMqCym5zzdS932aQPYBayFkzcsQSp6DHRAuj2Xxd9bly/urNKTumO8ZE0RFR\n"
+           + "wVgNU7o3OQepsH3bhe060Jlr6EBLFas0scH6ll8fREI8g+xhs8yHBOL/meE3zVQC\n"
+           + "/3KTyhY82R4xJy38YHCFPrwrtz5ZHpJqQ1LjiG+cX+FReoHp5VoV7LBNj+eL8oZb\n"
+           + "G6Zn5xlsBQgTlOxEIbXLVV13AgMBAAGjggL6MIIC9jBLBgNVHREERDBCgg1zd2lz\n"
+           + "c3NpZ24uY29tghF3d3cuc3dpc3NzaWduLmNvbYIMc3dpc3NzaWduLmNoghB3d3cu\n"
+           + "c3dpc3NzaWduLmNoMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\n"
+           + "AQYIKwYBBQUHAwIwHQYDVR0OBBYEFPDLqEiSWR5JiwPlqngv2n2WJwVRMB8GA1Ud\n"
+           + "IwQYMBaAFIh0Rm3HfLX6cnEZ3r8nXg1o4PcnMIH/BgNVHR8EgfcwgfQwR6BFoEOG\n"
+           + "QWh0dHA6Ly9jcmwuc3dpc3NzaWduLm5ldC84ODc0NDY2REM3N0NCNUZBNzI3MTE5\n"
+           + "REVCRjI3NUUwRDY4RTBGNzI3MIGooIGloIGihoGfbGRhcDovL2RpcmVjdG9yeS5z\n"
+           + "d2lzc3NpZ24ubmV0L0NOPTg4NzQ0NjZEQzc3Q0I1RkE3MjcxMTlERUJGMjc1RTBE\n"
+           + "NjhFMEY3MjclMkNPPVN3aXNzU2lnbiUyQ0M9Q0g/Y2VydGlmaWNhdGVSZXZvY2F0\n"
+           + "aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MGIG\n"
+           + "A1UdIARbMFkwVwYJYIV0AVkBAgEBMEowSAYIKwYBBQUHAgEWPGh0dHA6Ly9yZXBv\n"
+           + "c2l0b3J5LnN3aXNzc2lnbi5jb20vU3dpc3NTaWduLUdvbGQtQ1AtQ1BTLVI1LnBk\n"
+           + "ZjCB0QYIKwYBBQUHAQEEgcQwgcEwZAYIKwYBBQUHMAKGWGh0dHA6Ly9zd2lzc3Np\n"
+           + "Z24ubmV0L2NnaS1iaW4vYXV0aG9yaXR5L2Rvd25sb2FkLzg4NzQ0NjZEQzc3Q0I1\n"
+           + "RkE3MjcxMTlERUJGMjc1RTBENjhFMEY3MjcwWQYIKwYBBQUHMAGGTWh0dHA6Ly9n\n"
+           + "b2xkLWV2LWcyLm9jc3Auc3dpc3NzaWduLm5ldC84ODc0NDY2REM3N0NCNUZBNzI3\n"
+           + "MTE5REVCRjI3NUUwRDY4RTBGNzI3MA0GCSqGSIb3DQEBBQUAA4IBAQA8kdxUZdXa\n"
+           + "qu1EATZM77OhA4jw4rmrVNA+iQDb1NdlPldbc5PyQoIWdn7dJgzZrmupgOurRsol\n"
+           + "kUoXb2GrZDaiSK+2sW7VQAcS3p4yK1MawGpcekVcOiFkCjFvuqkwdgnOeZpFIJzP\n"
+           + "Nh6W0wkAxbAVwP/cAOFSoCKTdTfxLMU2g8g+7J49BagYm/b3h1UmvL+B4s7XzL+D\n"
+           + "QDiKzIUvb4xwmbDYksgflkOBwliG3sC8H6LDD+2n3ukFOOKyiXQnoz2QJ57R/Jhj\n"
+           + "kgKyXcr7+6RxatGM7K1u7RlfhuxQxvvrb0NTS8ojLwx6fZL1qYqRGjDWhTv36aRu\n"
+           + "nbZMIuE5QJQs\n"
+           + "-----END CERTIFICATE-----\n");
+
+    private static final String CERT_WITH_BASIC_NON_CA
+        = ("-----BEGIN CERTIFICATE-----\n"
+           + "MIIGwDCCBaigAwIBAgIQBXBpbXU7lyKUBaP2n+mqwjANBgkqhkiG9w0BAQUFADCB\n"
+           + "vjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\n"
+           + "ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug\n"
+           + "YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMv\n"
+           + "VmVyaVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0Ew\n"
+           + "HhcNMTIxMDEwMDAwMDAwWhcNMTQxMDEwMjM1OTU5WjCCASUxEzARBgsrBgEEAYI3\n"
+           + "PAIBAxMCVVMxGTAXBgsrBgEEAYI3PAIBAhMIRGVsYXdhcmUxHTAbBgNVBA8TFFBy\n"
+           + "aXZhdGUgT3JnYW5pemF0aW9uMRAwDgYDVQQFEwcyMTU4MTEzMQswCQYDVQQGEwJV\n"
+           + "UzEOMAwGA1UEERQFOTQwNDMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcU\n"
+           + "DU1vdW50YWluIFZpZXcxGTAXBgNVBAkUEDM1MCBFbGxpcyBTdHJlZXQxHTAbBgNV\n"
+           + "BAoUFFN5bWFudGVjIENvcnBvcmF0aW9uMSMwIQYDVQQLFBpJbmZyYXN0cnVjdHVy\n"
+           + "ZSAgT3BlcmF0aW9uczEZMBcGA1UEAxQQd3d3LnZlcmlzaWduLmNvbTCCASIwDQYJ\n"
+           + "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAM0oFzrY8FaYnXJSzme9WCwB3wPB+HS8\n"
+           + "blBuW6DbI11w7In0P6BCVwt/WqI1a+VwSfliKv7pD2P6eHvu6eb8ipPGF3xBRmtr\n"
+           + "Ttg9am77taHkB+w1trx9xXio0viFOPYf9mt2yNhCatjKeXnPRH8IoLoI5bqBhv8V\n"
+           + "u/Mg9s1Wwe8mW1zxztD3D0fVkWqMpQRLFLrs3Us58SbnaxbFLEmAQHPgrDwi+IC4\n"
+           + "aQWcf4UbCkA5P0at+svsu/G+KwYBrsVFL6NaoATcyqimckyCVxeKK6QEPRPM34ae\n"
+           + "7HpT9OWmCu+r4GhM7AQS2mY3wF1EhtigXyUUteU/H06kWyVybpy2VwcCAwEAAaOC\n"
+           + "Ak4wggJKMIHEBgNVHREEgbwwgbmCEHd3dy52ZXJpc2lnbi5jb22CDHZlcmlzaWdu\n"
+           + "LmNvbYIQd3d3LnZlcmlzaWduLm5ldIIMdmVyaXNpZ24ubmV0ghF3d3cudmVyaXNp\n"
+           + "Z24ubW9iaYINdmVyaXNpZ24ubW9iaYIPd3d3LnZlcmlzaWduLmV1ggt2ZXJpc2ln\n"
+           + "bi5ldYIVZm9ybXMud3Muc3ltYW50ZWMuY29tgg1zc2xyZXZpZXcuY29tghF3d3cu\n"
+           + "c3NscmV2aWV3LmNvbTAJBgNVHRMEAjAAMB0GA1UdDgQWBBSFo5HyhWbCi1NFKniM\n"
+           + "6xYHuroUUDAOBgNVHQ8BAf8EBAMCBaAwPgYDVR0fBDcwNTAzoDGgL4YtaHR0cDov\n"
+           + "L0VWSW50bC1jcmwudmVyaXNpZ24uY29tL0VWSW50bDIwMDYuY3JsMEQGA1UdIAQ9\n"
+           + "MDswOQYLYIZIAYb4RQEHFwYwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy\n"
+           + "aXNpZ24uY29tL2NwczAoBgNVHSUEITAfBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCG\n"
+           + "SAGG+EIEATAfBgNVHSMEGDAWgBROQ8gddu83U3pP8lhvlPM44tW93zB2BggrBgEF\n"
+           + "BQcBAQRqMGgwKwYIKwYBBQUHMAGGH2h0dHA6Ly9FVkludGwtb2NzcC52ZXJpc2ln\n"
+           + "bi5jb20wOQYIKwYBBQUHMAKGLWh0dHA6Ly9FVkludGwtYWlhLnZlcmlzaWduLmNv\n"
+           + "bS9FVkludGwyMDA2LmNlcjANBgkqhkiG9w0BAQUFAAOCAQEAUh48IWs1csaAU3kK\n"
+           + "hOZV4vde2ECxgVc0gRNz4V5fVdLsFv04S0V4pSZX77rQn56CFNkj6eImdAaTJVbd\n"
+           + "Wk8bB2FIwhjNnWScPXuNxzigVOpfRGuNRJymvkqG1+wq4BlG6aXa8aGu7aiuBCqN\n"
+           + "rmRSCj5WZQ94K3NCBUIiQQ9Ll1OGOYO3EM/rylGqUcnPf5aSET2kCIBfN3sG6veH\n"
+           + "wex+op4GuETJ48+PCoP0d1WrGGGs++nAgBYjjGCZciYfIxoqyrVaC5Yt5iYpXZA0\n"
+           + "ZzqJNbzmUD/l2rJeakdAHK0XYPwbQqvNvI1+dUNR9jlRxSKR8XX6mPe5ZgzMqYu+\n"
+           + "CQTDhg==\n"
+           + "-----END CERTIFICATE-----\n");
+
+    private static final String CERT_WITH_BASIC_CA_ZERO_PATH_LENGTH
+        = ("-----BEGIN CERTIFICATE-----\n"
+           + "MIIGqTCCBJGgAwIBAgIJAPeSt8SBjARYMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\n"
+           + "BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln\n"
+           + "biBHb2xkIENBIC0gRzIwHhcNMDkwNjEwMDkyOTM5WhcNMjQwNjA2MDkyOTM5WjBN\n"
+           + "MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMScwJQYDVQQDEx5T\n"
+           + "d2lzc1NpZ24gRVYgR29sZCBDQSAyMDA5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUA\n"
+           + "A4IBDwAwggEKAoIBAQDQnYs8uZZJHHloM5ucf7q7XcRN1Bl8QMoZiruC8oPmghom\n"
+           + "gZyb1qF0nAU/qx13UhcGWrV0goF/2Z8nMUGHjSeHuU65AS6rxm83XvnyI7rLKEcg\n"
+           + "4XXgibW3+bKldwjYfgPujGrZXC8gwx3jA+uF35VMIYpkWayAbl6kmoIsN7s7ZOVw\n"
+           + "T9gRIyZ+GVhFGgmeYGlUYEY1dQ66nMhwQQtTfVcMIiJPbBnppxU+5D0LM7vOwRX8\n"
+           + "tsEOVZyojP3bDqtHo/iWkeMPYSazOEdq4BB0QSc1mXVnu9Vh/NjBm00d0Agd/KsQ\n"
+           + "Nn/pR+tbgUYkiBhnu3oJ+XFNBsyFrOxGLJkg9P6fAgMBAAGjggKSMIICjjAOBgNV\n"
+           + "HQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUiHRGbcd8\n"
+           + "tfpycRnevydeDWjg9ycwHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn8O4w\n"
+           + "gf8GA1UdHwSB9zCB9DBHoEWgQ4ZBaHR0cDovL2NybC5zd2lzc3NpZ24ubmV0LzVC\n"
+           + "MjU3Qjk2QTQ2NTUxN0VCODM5RjNDMDc4NjY1RUU4M0FFN0YwRUUwgaiggaWggaKG\n"
+           + "gZ9sZGFwOi8vZGlyZWN0b3J5LnN3aXNzc2lnbi5uZXQvQ049NUIyNTdCOTZBNDY1\n"
+           + "NTE3RUI4MzlGM0MwNzg2NjVFRTgzQUU3RjBFRSUyQ089U3dpc3NTaWduJTJDQz1D\n"
+           + "SD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JM\n"
+           + "RGlzdHJpYnV0aW9uUG9pbnQwXQYDVR0gBFYwVDBSBgRVHSAAMEowSAYIKwYBBQUH\n"
+           + "AgEWPGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vU3dpc3NTaWduLUdv\n"
+           + "bGQtQ1AtQ1BTLVI0LnBkZjCBxgYIKwYBBQUHAQEEgbkwgbYwZAYIKwYBBQUHMAKG\n"
+           + "WGh0dHA6Ly9zd2lzc3NpZ24ubmV0L2NnaS1iaW4vYXV0aG9yaXR5L2Rvd25sb2Fk\n"
+           + "LzVCMjU3Qjk2QTQ2NTUxN0VCODM5RjNDMDc4NjY1RUU4M0FFN0YwRUUwTgYIKwYB\n"
+           + "BQUHMAGGQmh0dHA6Ly9vY3NwLnN3aXNzc2lnbi5uZXQvNUIyNTdCOTZBNDY1NTE3\n"
+           + "RUI4MzlGM0MwNzg2NjVFRTgzQUU3RjBFRTANBgkqhkiG9w0BAQUFAAOCAgEARJJo\n"
+           + "SpTCFSg5U+D4W8Cdc7vxEr83McOZY+D1fX490SAv3sDJ7XcbdXODL5m4UeK4s4bg\n"
+           + "UR1ZgCFiK8A4GRFpIvD4qse8E+Z20PGbQmtlSUIJztL3y3y4hLcM2Vt+mZz7M+aN\n"
+           + "xVlFbIrje+3PwgnvDTrIOLNt+LtV/uonA4A9SpAxlUCroFfSpfA71a3SJll/C4OG\n"
+           + "uvPZjHuX1ResF91+JJoyCiHcdi9h6w0yEf29zXdzKkUsaOZ0CikPTKdCZQ4MbIGX\n"
+           + "D5qMY65PK0mpT7uAt93ZIITXfQs93RWJWZtF7HrHGjIeloeKkXofsylmqP3JfgeV\n"
+           + "/mjuYz/9HS5MAxVE5+Wcb08tMGaoqSRxYhnv2Tmx2s8mPHyCXocgxMhXJtCN++Ba\n"
+           + "oO7JQRXeoiUZzIMac67dWb3rScOtEdF4lkIWB0yyts6LUPJtXXbRog3EI3i65ofc\n"
+           + "nW3ZdQijbE5t3F03yY/qRoHO8I/Be3qe1zk+7FCpjx7B8VLB1+lajfvLml0sgvCY\n"
+           + "O/O9/RRmqFhdhfDnsPj/pWkM6nKu8KjXX6WZmW6FTuC57yG81dI2AYqoO3qlzDdt\n"
+           + "IgVXouBar3TAgWRIka5FsxudaWOUK+Mj9TiKSQBYglHWhkdlEUpjOZfZhHKkMht4\n"
+           + "Y5mbkvu5+9xcWGhKNBLBq/isdBPkyfLVeVWxxkQ=\n"
+           + "-----END CERTIFICATE-----\n");
+
+    private static final String CERT_WITH_BASIC_CA_NO_PATH_LENGTH
+        = ("-----BEGIN CERTIFICATE-----\n"
+           + "MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\n"
+           + "BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln\n"
+           + "biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF\n"
+           + "MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT\n"
+           + "d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\n"
+           + "CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8\n"
+           + "76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+\n"
+           + "bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c\n"
+           + "6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE\n"
+           + "emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd\n"
+           + "MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt\n"
+           + "MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y\n"
+           + "MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y\n"
+           + "FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi\n"
+           + "aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM\n"
+           + "gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB\n"
+           + "qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7\n"
+           + "lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn\n"
+           + "8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov\n"
+           + "L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6\n"
+           + "45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO\n"
+           + "UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5\n"
+           + "O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC\n"
+           + "bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv\n"
+           + "GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a\n"
+           + "77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC\n"
+           + "hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3\n"
+           + "92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp\n"
+           + "Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w\n"
+           + "ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt\n"
+           + "Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ\n"
+           + "-----END CERTIFICATE-----\n");
+
+    public void testGetBasicConstraints() throws Exception {
+        assertEquals(5, generateCert(CERT_CORRECT).getBasicConstraints());
+        assertEquals(-1, generateCert(CERT_WITHOUT_BASIC).getBasicConstraints());
+        assertEquals(-1, generateCert(CERT_WITH_BASIC_NON_CA).getBasicConstraints());
+        assertEquals(0, generateCert(CERT_WITH_BASIC_CA_ZERO_PATH_LENGTH).getBasicConstraints());
+        assertEquals(Integer.MAX_VALUE, generateCert(CERT_WITH_BASIC_CA_NO_PATH_LENGTH).getBasicConstraints());
+    }
+
+    public void testCertificateException() throws Exception {
+        try {
+            generateCert(CERT_TAMPERED);
+            fail();
+        } catch (CertificateException expected) {
+        }
+
+        try {
+            generateCert(CERT);
+            fail();
+        } catch (CertificateException expected) {
+        }
+    }
+
+    public X509Certificate generateCert(String string) throws Exception {
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
-        ByteArrayInputStream bais = new ByteArrayInputStream(base64certCorrect
-                .getBytes());
-        cert = (X509Certificate) cf.generateCertificate(bais);
+        ByteArrayInputStream bais = new ByteArrayInputStream(string.getBytes());
+        X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
         assertNotNull(cert);
+        return cert;
     }
 }
diff --git a/luni/src/test/java/tests/targets/security/cert/CertPathBuilderTestPKIX.java b/luni/src/test/java/tests/targets/security/cert/CertPathBuilderTestPKIX.java
index fc67261..dc49de0 100644
--- a/luni/src/test/java/tests/targets/security/cert/CertPathBuilderTestPKIX.java
+++ b/luni/src/test/java/tests/targets/security/cert/CertPathBuilderTestPKIX.java
@@ -44,8 +44,7 @@
 
         keyStore.load(null, null);
 
-        CertificateFactory certificateFactory = CertificateFactory.getInstance(
-                "X509");
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
 
         X509Certificate selfSignedcertificate =
                 (X509Certificate) certificateFactory.generateCertificate(
diff --git a/luni/src/test/java/tests/targets/security/cert/CertPathValidatorTestPKIX.java b/luni/src/test/java/tests/targets/security/cert/CertPathValidatorTestPKIX.java
index af4037f..62b5f4a 100644
--- a/luni/src/test/java/tests/targets/security/cert/CertPathValidatorTestPKIX.java
+++ b/luni/src/test/java/tests/targets/security/cert/CertPathValidatorTestPKIX.java
@@ -56,8 +56,7 @@
         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
         keyStore.load(null, null);
 
-        CertificateFactory certificateFactory = CertificateFactory.getInstance(
-                "X509");
+        CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
 
         X509Certificate selfSignedcertificate =
                 (X509Certificate) certificateFactory.generateCertificate(
diff --git a/luni/src/test/native/sub.mk b/luni/src/test/native/sub.mk
new file mode 100644
index 0000000..24dc52c
--- /dev/null
+++ b/luni/src/test/native/sub.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2013 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.
+
+LOCAL_SRC_FILES := \
+    test_openssl_engine.cpp
+
+LOCAL_C_INCLUDES += \
+    external/openssl/include
+
+LOCAL_SHARED_LIBRARIES += \
+    libcrypto
diff --git a/luni/src/test/native/test_openssl_engine.cpp b/luni/src/test/native/test_openssl_engine.cpp
new file mode 100644
index 0000000..9a0f3b3
--- /dev/null
+++ b/luni/src/test/native/test_openssl_engine.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "UniquePtr.h"
+
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/objects.h>
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#define DYNAMIC_ENGINE
+#define TEST_ENGINE_ID   "javacoretests"
+#define TEST_ENGINE_NAME "libcore test engine"
+
+struct RSA_Delete {
+    void operator()(RSA* p) const {
+        RSA_free(p);
+    }
+};
+typedef UniquePtr<RSA, RSA_Delete> Unique_RSA;
+
+static const char* HMAC_TAG = "-HMAC-";
+static const size_t HMAC_TAG_LEN = strlen(HMAC_TAG);
+
+static EVP_PKEY *test_load_key(ENGINE* e, const char *key_id,
+        EVP_PKEY* (*read_func)(BIO*, EVP_PKEY**, pem_password_cb*, void*)) {
+    void* data = static_cast<void*>(const_cast<char*>(key_id));
+
+    EVP_PKEY *key = NULL;
+
+    const size_t key_len = strlen(key_id);
+    if (key_len > HMAC_TAG_LEN && !strncmp(key_id, HMAC_TAG, HMAC_TAG_LEN)) {
+        key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, e, reinterpret_cast<const unsigned char*>(key_id),
+                key_len);
+    } else {
+        BIO* in = BIO_new_mem_buf(data, strlen(key_id));
+        if (!in) {
+            return NULL;
+        }
+        key = read_func(in, NULL, 0, NULL);
+        BIO_free(in);
+
+        if (key != NULL && EVP_PKEY_type(key->type) == EVP_PKEY_RSA) {
+            ENGINE_init(e);
+
+            Unique_RSA rsa(EVP_PKEY_get1_RSA(key));
+            rsa->engine = e;
+            rsa->flags |= RSA_FLAG_EXT_PKEY;
+        }
+    }
+
+    return key;
+}
+
+static EVP_PKEY* test_load_privkey(ENGINE* e, const char* key_id, UI_METHOD*, void*) {
+    return test_load_key(e, key_id, PEM_read_bio_PrivateKey);
+}
+
+static EVP_PKEY* test_load_pubkey(ENGINE* e, const char* key_id, UI_METHOD*, void*) {
+    return test_load_key(e, key_id, PEM_read_bio_PUBKEY);
+}
+
+static const int meths[] = {
+        EVP_PKEY_HMAC,
+};
+
+static int pkey_meths(ENGINE*, EVP_PKEY_METHOD** meth, const int** nids, int nid) {
+    if (nid == EVP_PKEY_HMAC) {
+        *meth = const_cast<EVP_PKEY_METHOD*>(EVP_PKEY_meth_find(nid));
+        return 1;
+    } else if (nid != 0) {
+        return 0;
+    }
+
+    if (nids != NULL) {
+        *nids = meths;
+        return 1;
+    }
+
+    return 0;
+}
+
+static int test_engine_setup(ENGINE* e) {
+    if (!ENGINE_set_id(e, TEST_ENGINE_ID)
+            || !ENGINE_set_name(e, TEST_ENGINE_NAME)
+            || !ENGINE_set_flags(e, 0)
+            || !ENGINE_set_RSA(e, RSA_get_default_method())
+            || !ENGINE_set_load_privkey_function(e, test_load_privkey)
+            || !ENGINE_set_load_pubkey_function(e, test_load_pubkey)
+            || !ENGINE_set_pkey_meths(e, pkey_meths)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static int test_engine_bind_fn(ENGINE *e, const char *id) {
+    if (id && (strcmp(id, TEST_ENGINE_ID) != 0)) {
+        return 0;
+    }
+
+    if (!test_engine_setup(e)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+extern "C" {
+#undef OPENSSL_EXPORT
+#define OPENSSL_EXPORT extern __attribute__ ((visibility ("default")))
+
+IMPLEMENT_DYNAMIC_CHECK_FN()
+IMPLEMENT_DYNAMIC_BIND_FN(test_engine_bind_fn)
+};
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index be880d3..8d54b3e 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -69,8 +69,8 @@
     public static final String JSSE_PROVIDER_NAME = (IS_RI) ? "SunJSSE" : "AndroidOpenSSL";
     public static final String SECURITY_PROVIDER_NAME = (IS_RI) ? "SUN" : "BC";
 
-    public static final String KEY_MANAGER_FACTORY_DEFAULT = (IS_RI) ? "SunX509" : "X509";
-    public static final String TRUST_MANAGER_FACTORY_DEFAULT = (IS_RI) ? "PKIX" : "X509";
+    public static final String KEY_MANAGER_FACTORY_DEFAULT = (IS_RI) ? "SunX509" : "PKIX";
+    public static final String TRUST_MANAGER_FACTORY_DEFAULT = "PKIX";
 
     public static final String KEY_STORE_ALGORITHM = (IS_RI) ? "JKS" : "BKS";
 
@@ -178,7 +178,7 @@
         provide("KeyGenerator", "HmacSHA512");
         provide("KeyGenerator", "RC2");
         provide("KeyInfoFactory", "DOM");
-        provide("KeyManagerFactory", "SunX509");
+        provide("KeyManagerFactory", "PKIX");
         provide("KeyPairGenerator", "DSA");
         provide("KeyPairGenerator", "DiffieHellman");
         provide("KeyPairGenerator", "RSA");
@@ -190,6 +190,7 @@
         provide("Mac", "HmacSHA256");
         provide("Mac", "HmacSHA384");
         provide("Mac", "HmacSHA512");
+        // If adding a new MessageDigest, consider adding it to JarVerifier
         provide("MessageDigest", "MD2");
         provide("MessageDigest", "MD5");
         provide("MessageDigest", "SHA-256");
@@ -262,7 +263,6 @@
             provide("KeyGenerator", "SunTlsMasterSecret");
             provide("KeyGenerator", "SunTlsPrf");
             provide("KeyGenerator", "SunTlsRsaPremasterSecret");
-            provide("KeyManagerFactory", "NewSunX509");
             provide("KeyStore", "CaseExactJKS");
             provide("Mac", "HmacPBESHA1");
             provide("Mac", "SslMacMD5");
@@ -306,6 +306,14 @@
             unprovide("SSLContext", "TLSv1.2");
         }
 
+        // Fixups for the RI
+        if (IS_RI) {
+            // different names: Standard Names says PKIX, JSSE Reference Guide says SunX509 or NewSunX509
+            unprovide("KeyManagerFactory", "PKIX");
+            provide("KeyManagerFactory", "SunX509");
+            provide("KeyManagerFactory", "NewSunX509");
+        }
+
         // Fixups for dalvik
         if (!IS_RI) {
 
@@ -340,10 +348,6 @@
             provide("Cipher", "PBEWithSHAAnd3-KEYTripleDES-CBC");
             provide("SecretKeyFactory", "PBEWithSHAAnd3-KEYTripleDES-CBC");
 
-            // different names: dropped Sun
-            unprovide("KeyManagerFactory", "SunX509");
-            provide("KeyManagerFactory", "X509");
-
             // different names: BouncyCastle actually uses the Standard name of SHA-1 vs SHA
             unprovide("MessageDigest", "SHA");
             provide("MessageDigest", "SHA-1");
@@ -353,10 +357,6 @@
             provide("Cipher", "RSA/ECB/NOPADDING");
             provide("Cipher", "RSA/ECB/PKCS1PADDING");
 
-            // different names: JSSE Reference Guide says PKIX aka X509
-            unprovide("TrustManagerFactory", "PKIX");
-            provide("TrustManagerFactory", "X509");
-
             // different names: ARCFOUR vs ARC4
             unprovide("Cipher", "ARCFOUR");
             provide("Cipher", "ARC4");
@@ -422,6 +422,26 @@
             provide("SecretKeyFactory", "PBEWITHSHAAND40BITRC4");
             provide("SecretKeyFactory", "PBEWITHSHAANDTWOFISH-CBC");
 
+            // Needed by our OpenSSL provider
+            provide("Cipher", "AES/CBC/NOPADDING");
+            provide("Cipher", "AES/CBC/PKCS5PADDING");
+            provide("Cipher", "AES/CFB/NOPADDING");
+            provide("Cipher", "AES/CFB/PKCS5PADDING");
+            provide("Cipher", "AES/CTR/NOPADDING");
+            provide("Cipher", "AES/CTR/PKCS5PADDING");
+            provide("Cipher", "AES/ECB/NOPADDING");
+            provide("Cipher", "AES/ECB/PKCS5PADDING");
+            provide("Cipher", "AES/OFB/NOPADDING");
+            provide("Cipher", "AES/OFB/PKCS5PADDING");
+            provide("Cipher", "DESEDE/CBC/NOPADDING");
+            provide("Cipher", "DESEDE/CBC/PKCS5PADDING");
+            provide("Cipher", "DESEDE/CFB/NOPADDING");
+            provide("Cipher", "DESEDE/CFB/PKCS5PADDING");
+            provide("Cipher", "DESEDE/ECB/NOPADDING");
+            provide("Cipher", "DESEDE/ECB/PKCS5PADDING");
+            provide("Cipher", "DESEDE/OFB/NOPADDING");
+            provide("Cipher", "DESEDE/OFB/PKCS5PADDING");
+
             // removed LDAP
             unprovide("CertStore", "LDAP");
 
diff --git a/support/src/test/java/libcore/java/security/TestKeyStore.java b/support/src/test/java/libcore/java/security/TestKeyStore.java
index e24ee78..74c2840 100644
--- a/support/src/test/java/libcore/java/security/TestKeyStore.java
+++ b/support/src/test/java/libcore/java/security/TestKeyStore.java
@@ -528,8 +528,12 @@
         if (!permittedNameConstraints.isEmpty() || !excludedNameConstraints.isEmpty()) {
             x509cg.addExtension(X509Extensions.NameConstraints,
                                 true,
-                                new NameConstraints(permittedNameConstraints,
-                                                    excludedNameConstraints));
+                                new NameConstraints(permittedNameConstraints.toArray(
+                                                        new GeneralSubtree[
+                                                            permittedNameConstraints.size()]),
+                                                    excludedNameConstraints.toArray(
+                                                        new GeneralSubtree[
+                                                            excludedNameConstraints.size()])));
         }
 
         if (privateKey instanceof ECPrivateKey) {
diff --git a/support/src/test/java/tests/resources/hyts_signed_sha256digest_sha256withrsa.jar b/support/src/test/java/tests/resources/hyts_signed_sha256digest_sha256withrsa.jar
new file mode 100644
index 0000000..21e493f
--- /dev/null
+++ b/support/src/test/java/tests/resources/hyts_signed_sha256digest_sha256withrsa.jar
Binary files differ
diff --git a/support/src/test/java/tests/resources/hyts_signed_sha256withrsa.jar b/support/src/test/java/tests/resources/hyts_signed_sha256withrsa.jar
new file mode 100644
index 0000000..df6fd4c
--- /dev/null
+++ b/support/src/test/java/tests/resources/hyts_signed_sha256withrsa.jar
Binary files differ
diff --git a/support/src/test/java/tests/resources/hyts_signed_sha512digest_sha512withecdsa.jar b/support/src/test/java/tests/resources/hyts_signed_sha512digest_sha512withecdsa.jar
new file mode 100644
index 0000000..02791de
--- /dev/null
+++ b/support/src/test/java/tests/resources/hyts_signed_sha512digest_sha512withecdsa.jar
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-dirname.der b/support/src/test/java/tests/resources/x509/cert-alt-dirname.der
new file mode 100644
index 0000000..e96fc33
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-dirname.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-dns.der b/support/src/test/java/tests/resources/x509/cert-alt-dns.der
new file mode 100644
index 0000000..7f245f1
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-dns.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-email.der b/support/src/test/java/tests/resources/x509/cert-alt-email.der
new file mode 100644
index 0000000..94db3e9
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-email.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-none.der b/support/src/test/java/tests/resources/x509/cert-alt-none.der
new file mode 100644
index 0000000..6b7ab1d
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-none.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-other.der b/support/src/test/java/tests/resources/x509/cert-alt-other.der
new file mode 100644
index 0000000..fb66368
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-other.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-rid.der b/support/src/test/java/tests/resources/x509/cert-alt-rid.der
new file mode 100644
index 0000000..6773b4f
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-rid.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-alt-uri.der b/support/src/test/java/tests/resources/x509/cert-alt-uri.der
new file mode 100644
index 0000000..c21ae4c
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-alt-uri.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-ca.der b/support/src/test/java/tests/resources/x509/cert-ca.der
new file mode 100644
index 0000000..0570113
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-ca.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-caWithPathLen.der b/support/src/test/java/tests/resources/x509/cert-caWithPathLen.der
new file mode 100644
index 0000000..c964a0b
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-caWithPathLen.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-crl-ca.der b/support/src/test/java/tests/resources/x509/cert-crl-ca.der
new file mode 100644
index 0000000..d58022c
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-crl-ca.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-dsa.der b/support/src/test/java/tests/resources/x509/cert-dsa.der
new file mode 100644
index 0000000..09f83eb
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-dsa.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-ec.der b/support/src/test/java/tests/resources/x509/cert-ec.der
new file mode 100644
index 0000000..ca746cc
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-ec.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-extendedKeyUsage.der b/support/src/test/java/tests/resources/x509/cert-extendedKeyUsage.der
new file mode 100644
index 0000000..33673aa
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-extendedKeyUsage.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-invalidip.der b/support/src/test/java/tests/resources/x509/cert-invalidip.der
new file mode 100644
index 0000000..63ed7e3
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-invalidip.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-ipv6.der b/support/src/test/java/tests/resources/x509/cert-ipv6.der
new file mode 100644
index 0000000..0e6c5ed
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-ipv6.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-keyUsage-extraLong.der b/support/src/test/java/tests/resources/x509/cert-keyUsage-extraLong.der
new file mode 100644
index 0000000..1d5fdea
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-keyUsage-extraLong.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-rsa-dates.txt b/support/src/test/java/tests/resources/x509/cert-rsa-dates.txt
new file mode 100644
index 0000000..cbd8632
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-rsa-dates.txt
@@ -0,0 +1,2 @@
+notBefore=Mar  6 00:42:06 2013 GMT
+notAfter=Mar  4 00:42:06 2023 GMT
diff --git a/support/src/test/java/tests/resources/x509/cert-rsa-pubkey.der b/support/src/test/java/tests/resources/x509/cert-rsa-pubkey.der
new file mode 100644
index 0000000..4fb898b
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-rsa-pubkey.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-rsa-serial.txt b/support/src/test/java/tests/resources/x509/cert-rsa-serial.txt
new file mode 100644
index 0000000..07ed8ec
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-rsa-serial.txt
@@ -0,0 +1 @@
+serial=BFC278DBB294AC42
diff --git a/support/src/test/java/tests/resources/x509/cert-rsa-sig.der b/support/src/test/java/tests/resources/x509/cert-rsa-sig.der
new file mode 100644
index 0000000..54361b5
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-rsa-sig.der
@@ -0,0 +1 @@
+7ÚáiøopŒ¡_%5óÐý:™%تþ"©ãRëÄǜ—Œo‘ǁ޴òª@àçHēòŒjÚñQÌO§ÑÜA3e“ÏC£ëB ¿±`H¿p÷›Ë>†é.5²ßï€Ä'ÉqÖk{ãäCÕ¾æºqšPl-Ÿ
\ No newline at end of file
diff --git a/support/src/test/java/tests/resources/x509/cert-rsa-tbs.der b/support/src/test/java/tests/resources/x509/cert-rsa-tbs.der
new file mode 100644
index 0000000..cbe67b7
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-rsa-tbs.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-rsa.der b/support/src/test/java/tests/resources/x509/cert-rsa.der
new file mode 100644
index 0000000..e27787b
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-rsa.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-sigopt.der b/support/src/test/java/tests/resources/x509/cert-sigopt.der
new file mode 100644
index 0000000..d808282
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-sigopt.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-unsupported.der b/support/src/test/java/tests/resources/x509/cert-unsupported.der
new file mode 100644
index 0000000..c0db35a
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-unsupported.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/cert-userWithPathLen.der b/support/src/test/java/tests/resources/x509/cert-userWithPathLen.der
new file mode 100644
index 0000000..e5ec37e
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/cert-userWithPathLen.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/certs-pk7.der b/support/src/test/java/tests/resources/x509/certs-pk7.der
new file mode 100644
index 0000000..f7f8abc
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/certs-pk7.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/certs-pk7.pem b/support/src/test/java/tests/resources/x509/certs-pk7.pem
new file mode 100644
index 0000000..feef173
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/certs-pk7.pem
@@ -0,0 +1,45 @@
+-----BEGIN PKCS7-----
+MIIICAYJKoZIhvcNAQcCoIIH+TCCB/UCAQExADALBgkqhkiG9w0BBwGgggfbMIIE
+vDCCBCWgAwIBAgIJAL/CeNuylKxCMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNVBAYT
+AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTYW4gTWF0ZW8xFzAV
+BgNVBAoTDkdlbml1cy5jb20gSW5jMQ8wDQYDVQQLEwZOZXRPcHMwHhcNMTMwMzA2
+MDA0MjA2WhcNMjMwMzA0MDA0MjA2WjBgMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
+Q2FsaWZvcm5pYTESMBAGA1UEBxMJU2FuIE1hdGVvMRcwFQYDVQQKEw5HZW5pdXMu
+Y29tIEluYzEPMA0GA1UECxMGTmV0T3BzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQCxtuZhs1g1NvAYRkbdyDwNh+KLxc/GWDC+reFz9bO8NDmINjLVoe3Pu5S1
+ONXEgpkvSj6v8a9S7FgSShautZd6G1fm6XFB2Nn+eUjN56o86xNHMiEOG+QCTRRu
+pAkZaa3W1WEh+zqq2x0X9JlYY/xpDmP3voGC6rOcfnOoyl/fPQIDAQABo4ICfDCC
+AngwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0OBBYEFL8PXLxMk0+9Upkm
+yqlonexUHoq4MIGSBgNVHSMEgYowgYeAFL8PXLxMk0+9UpkmyqlonexUHoq4oWSk
+YjBgMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJ
+U2FuIE1hdGVvMRcwFQYDVQQKEw5HZW5pdXMuY29tIEluYzEPMA0GA1UECxMGTmV0
+T3BzggkAv8J427KUrEIwHgYJYIZIAYb4QgENBBEWD1guNTA5IFVuaXQgVGVzdDCB
+wwYDVR0RBIG7MIG4oA4GAyoDBKAHDAV0ZXN0MYEQeDUwOUBleGFtcGxlLmNvbYIQ
+eDUwOS5leGFtcGxlLmNvbaRQME4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1Bd2Vz
+b21lIER1ZGVzMRcwFQYDVQQLFA7DnGJlciBGcsOuZW5kczEOMAwGA1UEAxQF4oiG
+xpKGJWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vP3E9YXdlc29tZW5lc3OHBMCoAAGI
+AyoDBDCBwwYDVR0SBIG7MIG4oA4GAyoDBKAHDAV0ZXN0MYEQeDUwOUBleGFtcGxl
+LmNvbYIQeDUwOS5leGFtcGxlLmNvbaRQME4xCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1Bd2Vzb21lIER1ZGVzMRcwFQYDVQQLFA7DnGJlciBGcsOuZW5kczEOMAwGA1UE
+AxQF4oiGxpKGJWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vP3E9YXdlc29tZW5lc3OH
+BMCoAAGIAyoDBDANBgkqhkiG9w0BAQUFAAOBgQAaN9rhafhvcIyhXyUVNfPQ/Tod
+mSV/2Kr+IgfCjanjUusQxATHnJeMb5HHgd608hmqQODnHUjEk/KMatrxUcxPp9Hc
+QTNlBBWTz0Oj60KgAb+xYEi/cBn3Cxubyxo+hgjpLhc1st/vgMQnyXHWa3vjFuRD
+1b7munGaUBNsFi0BnzCCAxcwggLVoAMCAQICCQDoI9YBeCh6fzAJBgcqhkjOOAQD
+MGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlT
+YW4gTWF0ZW8xFzAVBgNVBAoTDkdlbml1cy5jb20gSW5jMQ8wDQYDVQQLEwZOZXRP
+cHMwHhcNMTMwMzA2MDA0MjA4WhcNMTMwNDA1MDA0MjA4WjBgMQswCQYDVQQGEwJV
+UzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU2FuIE1hdGVvMRcwFQYD
+VQQKEw5HZW5pdXMuY29tIEluYzEPMA0GA1UECxMGTmV0T3BzMIIBtjCCASsGByqG
+SM44BAEwggEeAoGBAJSFHSZWbyn0lJm3v2VeFoVNPSCM4akS+YxZ8NyYjEwl58aC
+YW/StslxMQeQvow++1bzM2GMMq6i29fSy/y8w/kNgua6mC+H1rkoAPy1zr/qiYD5
+4LgVm53sjur9HZaSP5h5U1u0f7uYrnxd5r8ns/pNJ1HnVdPzbFEgBU0LRjUtAhUA
+vcuQ5p9rxRPOegf2e3DP8dgc+LcCgYAJ2OG3B4JdOOowJidNeIFL2E86nQ9YqY+y
+Qn760g3L23PX+tjJag1vJfvS2pwU1X0w3YwvdoPBFLwBfy0vEwEFqR70iJC/E2st
+3geZqbPjZWkoUS6Rq3++JsKoLxYMLns/2U9/vpl/KF2053Xa3QvTyd0Dxs1o3T0m
+g2rhP8FldAOBhAACgYAYB+w4767ypZaw1RYNLWWl+UFe4L2cfsZKGM5Z8flqcJke
+y5fhk2VPRFa/YXUEYuYkganGQFNqrv2tlLalpcqRX00BXLFGx2WFrhpY1u2YYnDV
+5xGFpLH4MV0Nvw0iDMD+q+Wo2sH5MgUbCmnlwtZ8xhQYF3M2Cyb4fnm+tQsNnKMa
+MBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCADcwCQYHKoZIzjgEAwMxADAuAhUAt3Mt
+TCmXGAdJ8gGtrg/BfAQ/lPoCFQCMlyzUwS31EtfnLSu/SGFR6xxTMqEAMQA=
+-----END PKCS7-----
diff --git a/support/src/test/java/tests/resources/x509/certs.der b/support/src/test/java/tests/resources/x509/certs.der
new file mode 100644
index 0000000..d454c5b
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/certs.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/certs.pem b/support/src/test/java/tests/resources/x509/certs.pem
new file mode 100644
index 0000000..448227e
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/certs.pem
@@ -0,0 +1,47 @@
+-----BEGIN CERTIFICATE-----
+MIIEvDCCBCWgAwIBAgIJAL/CeNuylKxCMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTYW4gTWF0ZW8x
+FzAVBgNVBAoTDkdlbml1cy5jb20gSW5jMQ8wDQYDVQQLEwZOZXRPcHMwHhcNMTMw
+MzA2MDA0MjA2WhcNMjMwMzA0MDA0MjA2WjBgMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU2FuIE1hdGVvMRcwFQYDVQQKEw5HZW5p
+dXMuY29tIEluYzEPMA0GA1UECxMGTmV0T3BzMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQCxtuZhs1g1NvAYRkbdyDwNh+KLxc/GWDC+reFz9bO8NDmINjLVoe3P
+u5S1ONXEgpkvSj6v8a9S7FgSShautZd6G1fm6XFB2Nn+eUjN56o86xNHMiEOG+QC
+TRRupAkZaa3W1WEh+zqq2x0X9JlYY/xpDmP3voGC6rOcfnOoyl/fPQIDAQABo4IC
+fDCCAngwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0OBBYEFL8PXLxMk0+9
+UpkmyqlonexUHoq4MIGSBgNVHSMEgYowgYeAFL8PXLxMk0+9UpkmyqlonexUHoq4
+oWSkYjBgMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UE
+BxMJU2FuIE1hdGVvMRcwFQYDVQQKEw5HZW5pdXMuY29tIEluYzEPMA0GA1UECxMG
+TmV0T3BzggkAv8J427KUrEIwHgYJYIZIAYb4QgENBBEWD1guNTA5IFVuaXQgVGVz
+dDCBwwYDVR0RBIG7MIG4oA4GAyoDBKAHDAV0ZXN0MYEQeDUwOUBleGFtcGxlLmNv
+bYIQeDUwOS5leGFtcGxlLmNvbaRQME4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1B
+d2Vzb21lIER1ZGVzMRcwFQYDVQQLFA7DnGJlciBGcsOuZW5kczEOMAwGA1UEAxQF
+4oiGxpKGJWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vP3E9YXdlc29tZW5lc3OHBMCo
+AAGIAyoDBDCBwwYDVR0SBIG7MIG4oA4GAyoDBKAHDAV0ZXN0MYEQeDUwOUBleGFt
+cGxlLmNvbYIQeDUwOS5leGFtcGxlLmNvbaRQME4xCzAJBgNVBAYTAlVTMRYwFAYD
+VQQKEw1Bd2Vzb21lIER1ZGVzMRcwFQYDVQQLFA7DnGJlciBGcsOuZW5kczEOMAwG
+A1UEAxQF4oiGxpKGJWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vP3E9YXdlc29tZW5l
+c3OHBMCoAAGIAyoDBDANBgkqhkiG9w0BAQUFAAOBgQAaN9rhafhvcIyhXyUVNfPQ
+/TodmSV/2Kr+IgfCjanjUusQxATHnJeMb5HHgd608hmqQODnHUjEk/KMatrxUcxP
+p9HcQTNlBBWTz0Oj60KgAb+xYEi/cBn3Cxubyxo+hgjpLhc1st/vgMQnyXHWa3vj
+FuRD1b7munGaUBNsFi0Bnw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDFzCCAtWgAwIBAgIJAOgj1gF4KHp/MAkGByqGSM44BAMwYDELMAkGA1UEBhMC
+VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVNhbiBNYXRlbzEXMBUG
+A1UEChMOR2VuaXVzLmNvbSBJbmMxDzANBgNVBAsTBk5ldE9wczAeFw0xMzAzMDYw
+MDQyMDhaFw0xMzA0MDUwMDQyMDhaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpD
+YWxpZm9ybmlhMRIwEAYDVQQHEwlTYW4gTWF0ZW8xFzAVBgNVBAoTDkdlbml1cy5j
+b20gSW5jMQ8wDQYDVQQLEwZOZXRPcHMwggG2MIIBKwYHKoZIzjgEATCCAR4CgYEA
+lIUdJlZvKfSUmbe/ZV4WhU09IIzhqRL5jFnw3JiMTCXnxoJhb9K2yXExB5C+jD77
+VvMzYYwyrqLb19LL/LzD+Q2C5rqYL4fWuSgA/LXOv+qJgPnguBWbneyO6v0dlpI/
+mHlTW7R/u5iufF3mvyez+k0nUedV0/NsUSAFTQtGNS0CFQC9y5Dmn2vFE856B/Z7
+cM/x2Bz4twKBgAnY4bcHgl046jAmJ014gUvYTzqdD1ipj7JCfvrSDcvbc9f62Mlq
+DW8l+9LanBTVfTDdjC92g8EUvAF/LS8TAQWpHvSIkL8Tay3eB5mps+NlaShRLpGr
+f74mwqgvFgwuez/ZT3++mX8oXbTnddrdC9PJ3QPGzWjdPSaDauE/wWV0A4GEAAKB
+gBgH7DjvrvKllrDVFg0tZaX5QV7gvZx+xkoYzlnx+WpwmR7Ll+GTZU9EVr9hdQRi
+5iSBqcZAU2qu/a2UtqWlypFfTQFcsUbHZYWuGljW7ZhicNXnEYWksfgxXQ2/DSIM
+wP6r5ajawfkyBRsKaeXC1nzGFBgXczYLJvh+eb61Cw2coxowGDAJBgNVHRMEAjAA
+MAsGA1UdDwQEAwIANzAJBgcqhkjOOAQDAzEAMC4CFQC3cy1MKZcYB0nyAa2uD8F8
+BD+U+gIVAIyXLNTBLfUS1+ctK79IYVHrHFMy
+-----END CERTIFICATE-----
diff --git a/support/src/test/java/tests/resources/x509/create.sh b/support/src/test/java/tests/resources/x509/create.sh
new file mode 100755
index 0000000..a1d2f35
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/create.sh
@@ -0,0 +1,123 @@
+#!/bin/bash -
+# 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.
+
+set -o nounset                              # Treat unset variables as an error
+set -e
+
+DIR=$(dirname $0)
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch > /tmp/cert-rsa-req.pem
+openssl req -in /tmp/cert-rsa-req.pem -pubkey -noout | openssl rsa -pubin -pubout -outform der > ${DIR}/cert-rsa-pubkey.der
+openssl x509 -extfile ${DIR}/default.cnf -days 3650 -extensions usr_cert -req -signkey /tmp/privkey.pem -outform d < /tmp/cert-rsa-req.pem > ${DIR}/cert-rsa.der
+rm /tmp/cert-rsa-req.pem
+
+openssl asn1parse -in ${DIR}/cert-rsa.der -inform d -out ${DIR}/cert-rsa-tbs.der -noout -strparse 4
+SIG_OFFSET=$(openssl asn1parse -in ${DIR}/cert-rsa.der -inform d | tail -1 | cut -f1 -d:)
+openssl asn1parse -in ${DIR}/cert-rsa.der -inform d -strparse ${SIG_OFFSET} -noout -out ${DIR}/cert-rsa-sig.der
+
+# extract startdate and enddate
+openssl x509 -in ${DIR}/cert-rsa.der -inform d -noout -startdate -enddate > ${DIR}/cert-rsa-dates.txt
+
+# extract serial
+openssl x509 -in ${DIR}/cert-rsa.der -inform d -noout -serial > ${DIR}/cert-rsa-serial.txt
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions keyUsage_extraLong_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-keyUsage-extraLong.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions extendedKeyUsage_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-extendedKeyUsage.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions ca_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-ca.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions userWithPathLen_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-userWithPathLen.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions caWithPathLen_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-caWithPathLen.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_other_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-other.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_email_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-email.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_dns_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-dns.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_dirname_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-dirname.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_uri_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-uri.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_rid_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-rid.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions alt_none_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-alt-none.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions ipv6_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-ipv6.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions unsupported_cert -req -signkey /tmp/privkey.pem -outform d > ${DIR}/cert-unsupported.der
+
+openssl req -config ${DIR}/default.cnf -new -nodes -batch -config ${DIR}/default.cnf -extensions usr_cert -x509 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:1 -outform d > ${DIR}/cert-sigopt.der
+
+openssl dsaparam -out /tmp/dsaparam.pem 1024
+openssl req -config ${DIR}/default.cnf -newkey dsa:/tmp/dsaparam.pem -keyout /tmp/dsapriv.pem -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions keyUsage_cert -req -signkey /tmp/dsapriv.pem -outform d > ${DIR}/cert-dsa.der
+rm /tmp/dsaparam.pem
+
+openssl ecparam -name sect283k1 -out /tmp/ecparam.pem
+openssl req -config ${DIR}/default.cnf -newkey ec:/tmp/ecparam.pem -keyout /tmp/ecpriv.pem -nodes -batch | openssl x509 -extfile ${DIR}/default.cnf -extensions keyUsage_critical_cert -req -signkey /tmp/ecpriv.pem -outform d > ${DIR}/cert-ec.der
+rm /tmp/ecparam.pem
+
+# Create temporary CA for CRL generation
+rm -rf /tmp/ca
+mkdir -p /tmp/ca
+touch /tmp/ca/index.txt
+touch /tmp/ca/index.txt.attr
+echo "01" > /tmp/ca/serial
+openssl req -new -nodes -batch -x509 -extensions v3_ca -keyout /tmp/cakey.pem -out /tmp/cacert.pem -days 3650 -config ${DIR}/default.cnf
+openssl x509 -in /tmp/cacert.pem -outform d > ${DIR}/cert-crl-ca.der
+
+openssl ca -gencrl -crlhours 70 -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -out /tmp/crl-empty.pem -config ${DIR}/default.cnf
+openssl crl -in /tmp/crl-empty.pem -outform d -out ${DIR}/crl-empty.der
+
+openssl x509 -inform d -in ${DIR}/cert-rsa.der -out /tmp/cert-rsa.pem
+openssl ca -revoke /tmp/cert-rsa.pem -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -config ${DIR}/default.cnf
+openssl ca -gencrl -crlhours 70 -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -out /tmp/crl-rsa.pem -config ${DIR}/default.cnf
+openssl crl -in /tmp/crl-rsa.pem -outform d -out ${DIR}/crl-rsa.der
+
+openssl asn1parse -in ${DIR}/crl-rsa.der -inform d -out ${DIR}/crl-rsa-tbs.der -noout -strparse 4
+SIG_OFFSET=$(openssl asn1parse -in ${DIR}/crl-rsa.der -inform d | tail -1 | cut -f1 -d:)
+openssl asn1parse -in ${DIR}/crl-rsa.der -inform d -strparse ${SIG_OFFSET} -noout -out ${DIR}/crl-rsa-sig.der
+
+openssl x509 -inform d -in ${DIR}/cert-dsa.der -out /tmp/cert-dsa.pem
+openssl ca -revoke /tmp/cert-dsa.pem -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -crl_reason cessationOfOperation -extensions unsupported_cert -config ${DIR}/default.cnf
+openssl ca -gencrl -crldays 30 -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -out /tmp/crl-rsa-dsa.pem -config ${DIR}/default.cnf
+openssl ca -gencrl -crldays 30 -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -out ${DIR}/crl-rsa-dsa-sigopt.pem -config ${DIR}/default.cnf -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:1
+openssl crl -in /tmp/crl-rsa-dsa.pem -outform d -out ${DIR}/crl-rsa-dsa.der
+openssl crl -in ${DIR}/crl-rsa-dsa-sigopt.pem -outform d -out ${DIR}/crl-rsa-dsa-sigopt.der
+
+# Unsupported extensions
+openssl ca -gencrl -crlexts unsupported_cert -keyfile /tmp/cakey.pem -cert /tmp/cacert.pem -out /tmp/crl-unsupported.pem -config ${DIR}/default.cnf
+openssl crl -in /tmp/crl-unsupported.pem -outform d -out ${DIR}/crl-unsupported.der
+
+openssl crl -inform d -in ${DIR}/crl-rsa.der -noout -lastupdate -nextupdate > ${DIR}/crl-rsa-dates.txt
+openssl crl -inform d -in ${DIR}/crl-rsa-dsa.der -noout -lastupdate -nextupdate > ${DIR}/crl-rsa-dsa-dates.txt
+
+rm /tmp/cert-rsa.pem /tmp/cert-dsa.pem /tmp/cacert.pem /tmp/cakey.pem /tmp/crl-rsa.pem /tmp/crl-rsa-dsa.pem /tmp/crl-unsupported.pem /tmp/crl-empty.pem
+rm -r /tmp/ca
+
+rm /tmp/privkey.pem
+rm /tmp/dsapriv.pem
+rm /tmp/ecpriv.pem
+
+cat ${DIR}/cert-rsa.der ${DIR}/cert-dsa.der > /tmp/certs.der
+openssl x509 -inform d -in ${DIR}/cert-rsa.der > /tmp/certs.pem
+openssl x509 -inform d -in ${DIR}/cert-dsa.der >> /tmp/certs.pem
+
+openssl crl2pkcs7 -certfile /tmp/certs.pem -nocrl > ${DIR}/certs-pk7.pem
+openssl crl2pkcs7 -certfile /tmp/certs.pem -nocrl -outform d > ${DIR}/certs-pk7.der
+
+rm /tmp/certs.pem
diff --git a/support/src/test/java/tests/resources/x509/crl-empty.der b/support/src/test/java/tests/resources/x509/crl-empty.der
new file mode 100644
index 0000000..c5d21a4
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-empty.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-dates.txt b/support/src/test/java/tests/resources/x509/crl-rsa-dates.txt
new file mode 100644
index 0000000..4669357
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-dates.txt
@@ -0,0 +1,2 @@
+lastUpdate=Mar  6 00:42:08 2013 GMT
+nextUpdate=Mar  8 22:42:08 2013 GMT
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-dsa-dates.txt b/support/src/test/java/tests/resources/x509/crl-rsa-dsa-dates.txt
new file mode 100644
index 0000000..7700c7ef
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-dsa-dates.txt
@@ -0,0 +1,2 @@
+lastUpdate=Mar  6 00:42:08 2013 GMT
+nextUpdate=Apr  5 00:42:08 2013 GMT
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-dsa-sigopt.der b/support/src/test/java/tests/resources/x509/crl-rsa-dsa-sigopt.der
new file mode 100644
index 0000000..ec83979
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-dsa-sigopt.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-dsa-sigopt.pem b/support/src/test/java/tests/resources/x509/crl-rsa-dsa-sigopt.pem
new file mode 100644
index 0000000..87531a2
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-dsa-sigopt.pem
@@ -0,0 +1,10 @@
+-----BEGIN X509 CRL-----
+MIIBejCB3wIBATASBgkqhkiG9w0BAQowBaIDAgEBMGAxCzAJBgNVBAYTAlVTMRMw
+EQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTYW4gTWF0ZW8xFzAVBgNVBAoT
+Dkdlbml1cy5jb20gSW5jMQ8wDQYDVQQLEwZOZXRPcHMXDTEzMDMwNjAwNDIwOFoX
+DTEzMDQwNTAwNDIwOFowRjAaAgkAv8J427KUrEIXDTEzMDMwNjAwNDIwOFowKAIJ
+AOgj1gF4KHp/Fw0xMzAzMDYwMDQyMDhaMAwwCgYDVR0VBAMKAQUwEgYJKoZIhvcN
+AQEKMAWiAwIBAQOBgQBDETj4knxhTVoHhpHOAwskEfJLk0i5jW6iSn7nJ62ASlwj
+lXjpgIbEkohn6AkzLMiHXTzZDWu0iEiPwNK17RKlxWAiqVoH0igL50Luc6rNcsit
++RNnINYIN67JSkLfCxW2iWSDSONRTnbwCo1/XdcpoUFcNkwy5qDWJvcHdjgtGg==
+-----END X509 CRL-----
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-dsa.der b/support/src/test/java/tests/resources/x509/crl-rsa-dsa.der
new file mode 100644
index 0000000..32366fa
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-dsa.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-sig.der b/support/src/test/java/tests/resources/x509/crl-rsa-sig.der
new file mode 100644
index 0000000..80c915b
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-sig.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa-tbs.der b/support/src/test/java/tests/resources/x509/crl-rsa-tbs.der
new file mode 100644
index 0000000..c444c3a
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa-tbs.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/crl-rsa.der b/support/src/test/java/tests/resources/x509/crl-rsa.der
new file mode 100644
index 0000000..bde1720
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-rsa.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/crl-unsupported.der b/support/src/test/java/tests/resources/x509/crl-unsupported.der
new file mode 100644
index 0000000..19ffc9c
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/crl-unsupported.der
Binary files differ
diff --git a/support/src/test/java/tests/resources/x509/default.cnf b/support/src/test/java/tests/resources/x509/default.cnf
new file mode 100644
index 0000000..192b7f0
--- /dev/null
+++ b/support/src/test/java/tests/resources/x509/default.cnf
@@ -0,0 +1,294 @@
+# This is based on the default OpenSSL configuration file which is
+# licensed with the following license:
+
+# Copyright (c) 1998-2011 The OpenSSL Project.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+#
+# 3. All advertising materials mentioning features or use of this
+#    software must display the following acknowledgment:
+#    "This product includes software developed by the OpenSSL Project
+#    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+#
+# 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+#    endorse or promote products derived from this software without
+#    prior written permission. For written permission, please contact
+#    openssl-core@openssl.org.
+#
+# 5. Products derived from this software may not be called "OpenSSL"
+#    nor may "OpenSSL" appear in their names without prior written
+#    permission of the OpenSSL Project.
+#
+# 6. Redistributions of any form whatsoever must retain the following
+#    acknowledgment:
+#    "This product includes software developed by the OpenSSL Project
+#    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+#
+# THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+# ====================================================================
+#
+# This product includes cryptographic software written by Eric Young
+# (eay@cryptsoft.com).  This product includes software written by Tim
+# Hudson (tjh@cryptsoft.com).
+#
+
+HOME            = .
+RANDFILE        = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file        = $ENV::HOME/.oid
+oid_section        = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions        =
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca' and 'req'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+####################################################################
+[ ca ]
+default_ca    = CA_default        # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir        = /tmp/ca        # Where everything is kept
+certs        = $dir/certs        # Where the issued certs are kept
+crl_dir        = $dir/crl        # Where the issued crl are kept
+database    = $dir/index.txt    # database index file.
+new_certs_dir    = $dir/newcerts        # default place for new certs.
+
+certificate    = $dir/cacert.pem     # The CA certificate
+serial        = $dir/serial         # The current serial number
+crl        = $dir/crl.pem         # The current CRL
+private_key    = $dir/private/cakey.pem# The private key
+RANDFILE    = $dir/private/.rand    # private random number file
+
+x509_extensions    = usr_cert        # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt     = ca_default        # Subject Name options
+cert_opt     = ca_default        # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crl_extensions    = crl_ext
+
+default_days    = 365            # how long to certify for
+default_crl_days= 30            # how long before next CRL
+default_md    = sha1            # which md to use.
+preserve    = no            # keep passed DN ordering
+
+policy        = policy_anything
+
+[ policy_match ]
+countryName        = match
+stateOrProvinceName    = match
+organizationName    = match
+organizationalUnitName    = optional
+commonName        = supplied
+emailAddress        = optional
+
+[ policy_anything ]
+countryName        = optional
+stateOrProvinceName    = optional
+localityName        = optional
+organizationName    = optional
+organizationalUnitName    = optional
+commonName        = supplied
+emailAddress        = optional
+
+####################################################################
+[ req ]
+default_bits        = 1024
+default_keyfile     = /tmp/privkey.pem
+distinguished_name    = req_distinguished_name
+attributes        = req_attributes
+x509_extensions    = v3_ca    # The extentions to add to the self signed cert
+string_mask = nombstr
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName            = Country Name (2 letter code)
+countryName_default        = US
+countryName_min            = 2
+countryName_max            = 2
+
+stateOrProvinceName        = State or Province Name (full name)
+stateOrProvinceName_default    = California
+
+localityName            = Locality Name (eg, city)
+localityName_default        = San Mateo
+
+0.organizationName        = Organization Name (eg, company)
+0.organizationName_default    = Genius.com Inc
+
+organizationalUnitName        = Organizational Unit Name (eg, section)
+organizationalUnitName_default    = NetOps
+
+commonName            = Common Name (eg, your name or your server\'s hostname)
+commonName_max            = 64
+
+emailAddress            = Email Address
+emailAddress_max        = 64
+
+[ req_attributes ]
+challengePassword        = A challenge password
+challengePassword_min        = 4
+challengePassword_max        = 20
+unstructuredName        = An optional company name
+
+[ unsupported_cert ]
+# Just a made-up OID
+1.2.3.4.99999.1.2.3.4 = critical,ASN1:FORMAT:BITLIST,BITSTRING:0,1,2
+
+[ keyUsage_critical_cert ]
+basicConstraints=CA:FALSE
+keyUsage = critical, decipherOnly, keyAgreement
+
+[ keyUsage_extraLong_cert ]
+keyUsage=ASN1:FORMAT:BITLIST,BITSTRING:0,1,2,3,4,5,6,7,8,9,10
+
+[ keyUsage_cert ]
+basicConstraints=CA:FALSE
+keyUsage = encipherOnly, keyEncipherment, dataEncipherment, keyCertSign, cRLSign, cRLSign, keyEncipherment, dataEncipherment, keyCertSign, cRLSign
+
+[ extendedKeyUsage_cert ]
+extendedKeyUsage=1.2.3.4
+
+[ userWithPathLen_cert ]
+basicConstraints=CA:false,pathlen:10
+
+[ ca_cert ]
+basicConstraints=CA:true
+
+[ caWithPathLen_cert ]
+basicConstraints=CA:true,pathlen:10
+
+[ invalid_ip_cert ]
+subjectAltName = ASN1:SEQUENCE:invalid_ip_SEQ
+issuerAltName = ASN1:SEQUENCE:invalid_ip_SEQ
+
+[ invalid_ip_SEQ ]
+IP.1 = IMPLICIT:7,FORMAT:HEX,OCTETSTRING:0A
+
+[ ipv6_cert ]
+subjectAltName = ASN1:SEQUENCE:ipv6_SEQ
+issuerAltName = ASN1:SEQUENCE:ipv6_SEQ
+
+[ ipv6_SEQ ]
+IP.1 = IMPLICIT:7,FORMAT:HEX,OCTETSTRING:20010DB8000000000000FF0000428329
+
+[ usr_cert ]
+basicConstraints=CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer:always
+nsComment            = "X.509 Unit Test"
+
+subjectAltName = @alt_names
+issuerAltName = @alt_names
+#subjectAltName = ASN1:SEQUENCE:raw_alt_names
+
+[ alt_none_cert ]
+
+[ alt_names ]
+otherName.0 = 1.2.3.4;UTF8:test1
+email.0 = x509@example.com
+DNS.0 = x509.example.com
+dirName.0 = dir_example
+URI.0 = http://www.example.com/?q=awesomeness
+IP.0 = 192.168.0.1
+RID.0 = 1.2.3.4
+
+[ alt_other_cert ]
+subjectAltName = otherName:1.2.3.4;UTF8:test1
+
+[ alt_email_cert ]
+subjectAltName = email:x509@example.com
+
+[ alt_dns_cert ]
+subjectAltName = DNS:x509.example.com
+
+[ alt_dirname_cert ]
+subjectAltName = dirName:dir_example
+
+[ alt_uri_cert ]
+subjectAltName = URI:http://www.example.com/?q=awesomeness
+
+[ alt_rid_cert ]
+subjectAltName = RID:1.2.3.4
+
+[ raw_alt_names ]
+ediPartyName = IMPLICIT:5,SEQUENCE:ediPartyName_SEQ
+x400 = IMPLICIT:3,SEQUENCE:x400_SEQ
+
+[ x400_SEQ ]
+BuiltInStandardAttributes = SEQUENCE:x400_BuiltInStandardAddtributes_SEQ
+
+[ x400_BuiltInStandardAddtributes_SEQ ]
+PersonalName=IMPLICIT:5,SET:x400_PersonalName_SET
+
+[ x400_PersonalName_SET ]
+Surname=IMPLICIT:0,PRINTABLESTRING:Root
+GivenName=IMPLICIT:1,PRINTABLESTRING:Kenny
+
+[ ediPartyName_SEQ ]
+partyName = IMPLICIT:1,PRINTABLESTRING:Joe
+
+[ dir_example ]
+C=US
+O=Awesome Dudes
+OU=Über Frîends
+CN=example X.509
+CN=∆ƒ
+
+[ v3_req ]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+issuerAltName = @alt_names
+basicConstraints=CA:FALSE
+nsComment            = "X.509 Unit Test"
+
+[ v3_ca ]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+basicConstraints = CA:true
+
+[ crl_ext ]
+authorityKeyIdentifier=keyid:always,issuer:always
diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java
index 2be8d36..80fd8ca 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlParser.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java
@@ -1612,7 +1612,7 @@
         boolean detectCharset = (charset == null);
 
         if (is == null) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("is == null");
         }
 
         try {
diff --git a/xml/src/main/java/org/kxml2/io/KXmlSerializer.java b/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
index d1965d6..8fa2756 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlSerializer.java
@@ -327,7 +327,7 @@
     public void setOutput(OutputStream os, String encoding)
         throws IOException {
         if (os == null)
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException("os == null");
         setOutput(
             encoding == null
                 ? new OutputStreamWriter(os)