Merge "Regex support for named capture groups."
diff --git a/NativeCode.mk b/NativeCode.mk
index fcf4c24..d8e9d64 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -179,7 +179,9 @@
 
 # Set of gtest unit tests.
 include $(CLEAR_VARS)
-LOCAL_CFLAGS += $(libart_cflags)
+# Add -fno-builtin so that the compiler doesn't attempt to inline
+# memcpy calls that are not really aligned.
+LOCAL_CFLAGS += $(libart_cflags) -fno-builtin
 LOCAL_CPPFLAGS += $(core_cppflags)
 LOCAL_SRC_FILES += \
   luni/src/test/native/libcore_io_Memory_test.cpp \
diff --git a/dalvik/src/main/java/dalvik/system/DexFile.java b/dalvik/src/main/java/dalvik/system/DexFile.java
index e89386a..42cdac0 100644
--- a/dalvik/src/main/java/dalvik/system/DexFile.java
+++ b/dalvik/src/main/java/dalvik/system/DexFile.java
@@ -146,6 +146,7 @@
         }
 
         mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
+        mInternalCookie = mCookie;
         mFileName = sourceName;
         //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
     }
@@ -451,8 +452,7 @@
 
     /**
      * dex2oat should be run to update the apk/jar because the existing code
-     * is not relocated to match the boot image and does not have the
-     * necessary patch information to use patchoat.
+     * is not relocated to match the boot image.
      *
      * See {@link #getDexOptNeeded(String, String, int)}.
      *
@@ -461,15 +461,6 @@
     public static final int DEX2OAT_FOR_RELOCATION = 4;
 
     /**
-     * patchoat should be run to update the apk/jar.
-     *
-     * See {@link #getDexOptNeeded(String, String, int)}.
-     *
-     * @hide
-     */
-    public static final int PATCHOAT_FOR_RELOCATION = 5;
-
-    /**
      * Returns the VM's opinion of what kind of dexopt is needed to make the
      * apk/jar file up to date, where {@code targetMode} is used to indicate what
      * type of compilation the caller considers up-to-date, and {@code newProfile}
@@ -480,7 +471,7 @@
      * @param newProfile flag that describes whether a profile corresponding
      *        to the dex file has been recently updated and should be considered
      *        in the state of the file.
-     * @return NO_DEXOPT_NEEDED, DEX2OAT_*, or PATCHOAT_*. See documentation
+     * @return NO_DEXOPT_NEEDED, or DEX2OAT_*. See documentation
      *         of the particular status code for more information on its
      *         meaning. Returns a positive status code if the status refers to
      *         the oat file in the oat location. Returns a negative status
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java
index b25c4de..db09af8 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.harmony.tests.java.nio;
 
+import java.io.RandomAccessFile;
 import java.nio.BufferOverflowException;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
@@ -29,6 +30,9 @@
 import java.nio.LongBuffer;
 import java.nio.ReadOnlyBufferException;
 import java.nio.ShortBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 
 /**
@@ -2033,6 +2037,28 @@
         }
     }
 
+    // http://b/34045479
+    public void testMappedByteBuffer_Put_ReadOnlyHeapByteBuffer() throws Exception {
+        // Create a temp file
+        byte[] data = new byte[] {1, 2, 3, 4};
+        Path tempFile = Files.createTempFile("mmap", "test");
+        Files.write(tempFile, data);
+
+        // Create a read-only heap buffer
+        ByteBuffer readOnlySource = ByteBuffer.allocate(4).asReadOnlyBuffer();
+        try (RandomAccessFile tempRAF = new RandomAccessFile(tempFile.toFile(), "rw")) {
+            FileChannel tempFileChannel = tempRAF.getChannel();
+            ByteBuffer mappedByteBuffer =
+                tempFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, tempFileChannel.size());
+
+            // Try to put a non-empty, read-only heap byte buffer into a mapped byte buffer.
+            mappedByteBuffer.put(readOnlySource);
+            tempFileChannel.close();
+        } finally {
+            Files.delete(tempFile);
+        }
+    }
+
     private void loadTestData1(byte array[], int offset, int length) {
         for (int i = 0; i < length; i++) {
             array[offset + i] = (byte) i;
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index cedbf82..fb348bc 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -97,12 +97,7 @@
         try {
             Libcore.os.bind(fd, address, port);
         } catch (ErrnoException errnoException) {
-            if (errnoException.errno == EADDRINUSE || errnoException.errno == EADDRNOTAVAIL ||
-                errnoException.errno == EPERM || errnoException.errno == EACCES) {
-                throw new BindException(errnoException.getMessage(), errnoException);
-            } else {
-                throw new SocketException(errnoException.getMessage(), errnoException);
-            }
+            throw new BindException(errnoException.getMessage(), errnoException);
         }
     }
 
@@ -567,11 +562,10 @@
         return result;
     }
 
-    private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException)
-            throws IOException {
+    private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException) throws SocketException {
         if (isDatagram) {
-            if (errnoException.errno == ECONNREFUSED) {
-                throw new PortUnreachableException("ICMP Port Unreachable");
+            if (errnoException.errno == ECONNRESET || errnoException.errno == ECONNREFUSED) {
+                return 0;
             }
         } else {
             if (errnoException.errno == EAGAIN) {
@@ -580,7 +574,7 @@
                 return 0;
             }
         }
-        throw errnoException.rethrowAsIOException();
+        throw errnoException.rethrowAsSocketException();
     }
 
     public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
@@ -613,12 +607,8 @@
         }
         if (packet != null) {
             packet.setReceivedLength(byteCount);
+            packet.setAddress(srcAddress.getAddress());
             packet.setPort(srcAddress.getPort());
-
-            // packet.address should only be changed when it is different from srcAddress.
-            if (!srcAddress.getAddress().equals(packet.getAddress())) {
-                packet.setAddress(srcAddress.getAddress());
-            }
         }
         return byteCount;
     }
@@ -632,7 +622,7 @@
             }
         } else {
             if (isConnected && errnoException.errno == ECONNREFUSED) {
-                throw new PortUnreachableException("ICMP Port Unreachable", errnoException);
+                throw new PortUnreachableException("", errnoException);
             } else if (errnoException.errno == EAGAIN) {
                 throw new SocketTimeoutException(errnoException);
             } else {
diff --git a/luni/src/main/native/ExecStrings.cpp b/luni/src/main/native/ExecStrings.cpp
index 4f90431..9a408fd 100644
--- a/luni/src/main/native/ExecStrings.cpp
+++ b/luni/src/main/native/ExecStrings.cpp
@@ -20,7 +20,7 @@
 
 #include <stdlib.h>
 
-#include <android/log.h>
+#include <log/log.h>
 
 #include "ScopedLocalRef.h"
 
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index ec9d878..3c244ef 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -18,7 +18,7 @@
 
 #include <stdlib.h>
 
-#include "android/log.h"
+#include "log/log.h"
 
 #include "JniConstants.h"
 #include "ScopedLocalFrame.h"
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index 7456028..46594c6 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -31,8 +31,8 @@
 #include <memory>
 #include <vector>
 
-#include <android/log.h>
 #include <android-base/unique_fd.h>
+#include <log/log.h>
 
 #include "IcuUtilities.h"
 #include "JNIHelp.h"
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index 86c9782..d150052 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -47,9 +47,9 @@
 
 #include <memory>
 
-#include <android/log.h>
 #include <android-base/file.h>
 #include <android-base/strings.h>
+#include <log/log.h>
 
 #include "AsynchronousCloseMonitor.h"
 #include "ExecStrings.h"
@@ -1718,8 +1718,13 @@
     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);
-    if (recvCount > 0) {
-        fillInetSocketAddress(env, javaInetSocketAddress, ss);
+    if (recvCount >= 0) {
+        // The socket may have performed orderly shutdown and recvCount would return 0 (see man 2
+        // recvfrom), in which case ss.ss_family == AF_UNIX and fillInetSocketAddress would fail.
+        // Don't fill in the address if recvfrom didn't succeed. http://b/33483694
+        if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
+            fillInetSocketAddress(env, javaInetSocketAddress, ss);
+        }
     }
     return recvCount;
 }
diff --git a/luni/src/test/java/libcore/io/OsTest.java b/luni/src/test/java/libcore/io/OsTest.java
index bc28121..7bbf383 100644
--- a/luni/src/test/java/libcore/io/OsTest.java
+++ b/luni/src/test/java/libcore/io/OsTest.java
@@ -28,6 +28,8 @@
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -43,6 +45,7 @@
 import java.util.Locale;
 import java.util.concurrent.atomic.AtomicReference;
 import junit.framework.TestCase;
+
 import static android.system.OsConstants.*;
 
 public class OsTest extends TestCase {
@@ -762,4 +765,20 @@
       assertTrue("Could not delete symlink: " + path, new File(path).delete());
     }
   }
+
+  // Address should be correctly set for empty packets. http://b/33481605
+  public void test_recvfrom_EmptyPacket() throws Exception {
+    try (DatagramSocket ds = new DatagramSocket();
+         DatagramSocket srcSock = new DatagramSocket()) {
+      srcSock.send(new DatagramPacket(new byte[0], 0, ds.getLocalSocketAddress()));
+
+      byte[] recvBuf = new byte[16];
+      InetSocketAddress address = new InetSocketAddress();
+      int recvCount =
+          android.system.Os.recvfrom(ds.getFileDescriptor$(), recvBuf, 0, 16, 0, address);
+      assertEquals(0, recvCount);
+      assertTrue(address.getAddress().isLoopbackAddress());
+      assertEquals(srcSock.getLocalPort(), address.getPort());
+    }
+  }
 }
diff --git a/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java b/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java
index d4cf83e..60c716c 100644
--- a/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java
+++ b/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java
@@ -177,14 +177,22 @@
         encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
         decoder.onMalformedInput(CodingErrorAction.REPORT);
         CharBuffer inputCB = CharBuffer.allocate(65536);
-        for (int code = 32; code <= 65533; ++code) {
-            // icu4c seems to accept any surrogate as a sign that "more is coming",
-            // even for charsets like US-ASCII. http://b/10310751
-            if (code >= 0xd800 && code <= 0xdfff) {
+        // Only test most of the Unicode BMP.
+        // Supplementary code points would require use of encoder.canEncode(CharSequence).
+        for (char code = 0x20; code <= 0xfffd; code++) {
+            // Skip surrogates to avoid writing broken UTF-16.
+            // Ignore charsets that do convert surrogate code units.
+            if (code == 0xd800) {
+                code = 0xdfff;
                 continue;
             }
-            if (encoder.canEncode((char) code)) {
-                inputCB.put((char) code);
+            // Ignore the private use area.
+            if (code == 0xe000) {
+                code = 0xf8ff;
+                continue;
+            }
+            if (encoder.canEncode(code)) {
+                inputCB.put(code);
             }
         }
         inputCB.rewind();
diff --git a/luni/src/test/java/libcore/java/util/TreeMapTest.java b/luni/src/test/java/libcore/java/util/TreeMapTest.java
index bd31a61..9384d56 100644
--- a/luni/src/test/java/libcore/java/util/TreeMapTest.java
+++ b/luni/src/test/java/libcore/java/util/TreeMapTest.java
@@ -612,6 +612,33 @@
         return bound == '[' || bound == '(';
     }
 
+    // http://b//26336181
+    //
+    // Note that this is only worth working around because these bogus comparators worked
+    // somewhat-fine on M and below provided that :
+    //
+    // (1) put was called with distinct elements (i.e, with no two elements equal() to each other)
+    // (2) get or get-like methods are never called
+    //
+    // These comparators are clearly bogus but are somewhat common.
+    public void testTreeMapWithBogusComparator() {
+        TreeMap<String, String> treeMap = new TreeMap<String, String>(
+                new Comparator<String>() {
+                    @Override
+                    public int compare(String o1, String o2) {
+                        if (o1.equals(o2)) {
+                            throw new IllegalArgumentException("Expected unequal elements");
+                        }
+
+                        return o1.compareTo(o2);
+                    }
+                }
+        );
+
+        treeMap.put("candy", "floss");
+        treeMap.put("cheddar", "cheese");
+    }
+
     public void test_spliterator_keySet() {
         TreeMap<String, String> treeMap = new TreeMap<>();
         treeMap.put("a", "1");
diff --git a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
index 99691d5..901519e 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
@@ -110,8 +110,8 @@
   }
 
   public void testLoadTzData_invalidOffsets() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
 
     // Sections must be in the correct order: section sizing is calculated using them.
     builder.setIndexOffsetOverride(10);
@@ -124,8 +124,9 @@
   }
 
   public void testLoadTzData_zoneTabOutsideFile() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder()
+                    .initializeToValid();
 
     // Sections must be in the correct order: section sizing is calculated using them.
     builder.setIndexOffsetOverride(10);
@@ -139,8 +140,8 @@
   }
 
   public void testLoadTzData_nonDivisibleIndex() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
 
     // Sections must be in the correct order: section sizing is calculated using them.
     int indexOffset = 10;
@@ -156,22 +157,22 @@
   }
 
   public void testLoadTzData_badId() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
     builder.clearZicData();
     byte[] validZicData =
-            new ZoneInfoTestHelper.ZoneInfoDataBuilder().initializeToValid().build();
+            new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
     builder.addZicData("", validZicData); // "" is an invalid ID
 
     checkInvalidDataDetected(builder.build());
   }
 
   public void testLoadTzData_badIdOrder() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
     builder.clearZicData();
     byte[] validZicData =
-            new ZoneInfoTestHelper.ZoneInfoDataBuilder().initializeToValid().build();
+            new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
     builder.addZicData("Europe/Zurich", validZicData);
     builder.addZicData("Europe/London", validZicData);
 
@@ -179,11 +180,11 @@
   }
 
   public void testLoadTzData_duplicateId() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
     builder.clearZicData();
     byte[] validZicData =
-            new ZoneInfoTestHelper.ZoneInfoDataBuilder().initializeToValid().build();
+            new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
     builder.addZicData("Europe/London", validZicData);
     builder.addZicData("Europe/London", validZicData);
 
@@ -191,8 +192,8 @@
   }
 
   public void testLoadTzData_badZicLength() throws Exception {
-    ZoneInfoTestHelper.TzDataBuilder builder
-            = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+    ZoneInfoTestHelper.TzDataBuilder builder =
+            new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
     builder.clearZicData();
     byte[] invalidZicData = "This is too short".getBytes();
     builder.addZicData("Europe/London", invalidZicData);
diff --git a/luni/src/test/java/libcore/util/ZoneInfoTest.java b/luni/src/test/java/libcore/util/ZoneInfoTest.java
index d2cd1fb..82418ca 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoTest.java
@@ -351,15 +351,17 @@
   }
 
   public void testReadTimeZone_valid() throws Exception {
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.initializeToValid();
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid();
     assertNotNull(createZoneInfo(getName(), System.currentTimeMillis(), builder.build()));
   }
 
   public void testReadTimeZone_badMagic() throws Exception {
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.initializeToValid();
-    builder.setMagic(0xdeadbeef); // Bad magic.
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setMagic(0xdeadbeef); // Bad magic.
     try {
       createZoneInfo(getName(), System.currentTimeMillis(), builder.build());
       fail();
@@ -370,9 +372,10 @@
    * Checks to make sure that ZoneInfo rejects more than 256 types.
    */
   public void testReadTimeZone_TooManyTypes() throws Exception {
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.initializeToValid();
-    builder.setTypeCountOverride(257);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTypeCountOverride(257);
     byte[] bytes = builder.build();
     try {
       createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -385,9 +388,10 @@
    * Checks to make sure that ZoneInfo rejects more than 2000 transitions.
    */
   public void testReadTimeZone_TooManyTransitions() throws Exception {
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.initializeToValid();
-    builder.setTransitionCountOverride(2001);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTransitionCountOverride(2001);
     byte[] bytes = builder.build();
     try {
       createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -400,9 +404,10 @@
    * Checks to make sure that ZoneInfo rejects a negative type count.
    */
   public void testReadTimeZone_NegativeTypes() throws Exception {
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.initializeToValid();
-    builder.setTypeCountOverride(-1);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTypeCountOverride(-1);
     byte[] bytes = builder.build();
     try {
       createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -415,9 +420,10 @@
    * Checks to make sure that ZoneInfo rejects a negative transition count.
    */
   public void testReadTimeZone_NegativeTransitions() throws Exception {
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.initializeToValid();
-    builder.setTransitionCountOverride(-1);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTransitionCountOverride(-1);
     byte[] bytes = builder.build();
     try {
       createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -437,8 +443,10 @@
             { 1800, 1 },
     };
 
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.setTransitionsAndTypes(transitions, types);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTransitionsAndTypes(transitions, types);
 
     byte[] bytes = builder.build();
     try {
@@ -459,8 +467,10 @@
             { 1800, 1 },
     };
 
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.setTransitionsAndTypes(transitions, types);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTransitionsAndTypes(transitions, types);
 
     byte[] bytes = builder.build();
     try {
@@ -481,8 +491,10 @@
             { 1800, 2 }, // Invalid isDst - must be 0 or 1
     };
 
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.setTransitionsAndTypes(transitions, types);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .initializeToValid()
+                    .setTransitionsAndTypes(transitions, types);
 
     byte[] bytes = builder.build();
     try {
@@ -548,8 +560,9 @@
   private ZoneInfo createZoneInfo(String name, int[][] transitions, int[][] types,
       long currentTimeMillis) throws Exception {
 
-    ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
-    builder.setTransitionsAndTypes(transitions, types);
+    ZoneInfoTestHelper.ZicDataBuilder builder =
+            new ZoneInfoTestHelper.ZicDataBuilder()
+                    .setTransitionsAndTypes(transitions, types);
     return createZoneInfo(name, currentTimeMillis, builder.build());
   }
 
diff --git a/ojluni/src/main/java/java/io/Console.java b/ojluni/src/main/java/java/io/Console.java
index 3cab03e..2b4e4e6 100644
--- a/ojluni/src/main/java/java/io/Console.java
+++ b/ojluni/src/main/java/java/io/Console.java
@@ -559,19 +559,4 @@
                      cs));
         rcb = new char[1024];
     }
-
-    /**
-     * Android-changed: Added method for internal use only, and also in use
-     * by tests.
-     *
-     * @hide
-     */
-    public static synchronized Console getConsole() {
-        if (istty()) {
-            if (cons == null)
-                cons = new Console();
-            return cons;
-        }
-        return null;
-    }
 }
diff --git a/ojluni/src/main/java/java/lang/Math.java b/ojluni/src/main/java/java/lang/Math.java
index abcb6bb..b53d35c 100644
--- a/ojluni/src/main/java/java/lang/Math.java
+++ b/ojluni/src/main/java/java/lang/Math.java
@@ -1203,8 +1203,9 @@
      * @return  the absolute value of the argument.
      */
     public static float abs(float a) {
-        // Note, as a "quality of implementation" rather than pure "spec compliance" we require that
-        // Math.abs() clears the sign bit (but changes nothing else) for all numbers, including NaN.
+        // Note, as a "quality of implementation", rather than pure "spec compliance",
+        // we require that Math.abs() clears the sign bit (but changes nothing else)
+        // for all numbers, including NaN (signaling NaN may become quiet though).
         return Float.intBitsToFloat(0x7fffffff & Float.floatToRawIntBits(a));
     }
 
@@ -1224,8 +1225,9 @@
      * @return  the absolute value of the argument.
      */
     public static double abs(double a) {
-        // Note, as a "quality of implementation" rather than pure "spec compliance" we require that
-        // Math.abs() clears the sign bit (but changes nothing else) for all numbers, including NaN.
+        // Note, as a "quality of implementation", rather than pure "spec compliance",
+        // we require that Math.abs() clears the sign bit (but changes nothing else)
+        // for all numbers, including NaN (signaling NaN may become quiet though).
         return Double.longBitsToDouble(0x7fffffffffffffffL & Double.doubleToRawLongBits(a));
     }
 
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java b/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java
index 08ce6f0..b821f63 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java
@@ -27,12 +27,13 @@
 
 import java.lang.reflect.*;
 import java.util.*;
+import java.lang.invoke.MethodHandles.Lookup;
 import static java.lang.invoke.MethodHandleStatics.*;
 
 /**
  * A symbolic reference obtained by cracking a direct method handle
  * into its consitutent symbolic parts.
- * To crack a direct method handle, call {@code Lookup#revealDirect Lookup.revealDirect}.
+ * To crack a direct method handle, call {@link Lookup#revealDirect Lookup.revealDirect}.
  * <h1><a name="directmh"></a>Direct Method Handles</h1>
  * A <em>direct method handle</em> represents a method, constructor, or field without
  * any intervening argument bindings or other transformations.
@@ -43,16 +44,16 @@
  * <li>By executing an {@code ldc} instruction on a {@code CONSTANT_MethodHandle} constant.
  *     (See the Java Virtual Machine Specification, sections 4.4.8 and 5.4.3.)
  * <li>By calling one of the <a href="MethodHandles.Lookup.html#lookups">Lookup Factory Methods</a>,
- *     such as {@code Lookup#findVirtual Lookup.findVirtual},
+ *     such as {@link Lookup#findVirtual Lookup.findVirtual},
  *     to resolve a symbolic reference into a method handle.
  *     A symbolic reference consists of a class, name string, and type.
- * <li>By calling the factory method {@code Lookup#unreflect Lookup.unreflect}
- *     or {@code Lookup#unreflectSpecial Lookup.unreflectSpecial}
+ * <li>By calling the factory method {@link Lookup#unreflect Lookup.unreflect}
+ *     or {@link Lookup#unreflectSpecial Lookup.unreflectSpecial}
  *     to convert a {@link Method} into a method handle.
- * <li>By calling the factory method {@code Lookup#unreflectConstructor Lookup.unreflectConstructor}
+ * <li>By calling the factory method {@link Lookup#unreflectConstructor Lookup.unreflectConstructor}
  *     to convert a {@link Constructor} into a method handle.
- * <li>By calling the factory method {@code Lookup#unreflectGetter Lookup.unreflectGetter}
- *     or {@code Lookup#unreflectSetter Lookup.unreflectSetter}
+ * <li>By calling the factory method {@link Lookup#unreflectGetter Lookup.unreflectGetter}
+ *     or {@link Lookup#unreflectSetter Lookup.unreflectSetter}
  *     to convert a {@link Field} into a method handle.
  * </ul>
  *
@@ -65,7 +66,7 @@
  * <p>
  * If the underlying method is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a>,
  * the direct method handle will have been "bound" to a particular caller class, the
- * {@code java.lang.invoke.MethodHandles.Lookup#lookupClass() lookup class}
+ * {@linkplain java.lang.invoke.MethodHandles.Lookup#lookupClass() lookup class}
  * of the lookup object used to create it.
  * Cracking this method handle with a different lookup class will fail
  * even if the underlying method is public (like {@code Class.forName}).
@@ -73,7 +74,7 @@
  * The requirement of lookup object matching provides a "fast fail" behavior
  * for programs which may otherwise trust erroneous revelation of a method
  * handle with symbolic information (or caller binding) from an unexpected scope.
- * Use {@code java.lang.invoke.MethodHandles#reflectAs} to override this limitation.
+ * Use {@link java.lang.invoke.MethodHandles#reflectAs} to override this limitation.
  *
  * <h1><a name="refkinds"></a>Reference kinds</h1>
  * The <a href="MethodHandles.Lookup.html#lookups">Lookup Factory Methods</a>
@@ -120,7 +121,6 @@
  * </table>
  * @since 1.8
  */
-// TODO(narayan) : Change @code back to @link once MethodHandles has been imported.
 public
 interface MethodHandleInfo {
     /**
@@ -198,14 +198,7 @@
      * @exception NullPointerException if either argument is {@code null}
      * @exception IllegalArgumentException if the underlying member is not accessible to the given lookup object
      */
-    // TODO(narayan): change the second argument back to MethodHandles.Lookup once that
-    // class has been introduced.
-    //
-    // public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup);
-    //
-    // We need to temporarily keep the existing version because of a circular dependency between
-    // the two classes.
-    public <T extends Member> T reflectAs(Class<T> expected, Object lookup);
+    public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup);
 
     /**
      * Returns the access modifiers of the underlying member.
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
index 9599f27..9c3b56d 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
@@ -868,9 +868,6 @@
          * @throws NullPointerException if any argument is null
          */
         public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
-            // TODO: Support varargs methods. The returned method handle must be a var-args
-            // collector in that case.
-
             // Special case : when we're looking up a virtual method on the MethodHandles class
             // itself, we can return one of our specialized invokers.
             if (refc == MethodHandle.class) {
@@ -953,9 +950,6 @@
          * @throws NullPointerException if any argument is null
          */
         public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
-            // TODO: Support varargs methods. The returned method handle must be a var-args
-            // collector in that case.
-
             // The queried |type| is (PT1,PT2,..)V
             Constructor constructor = refc.getDeclaredConstructor(type.ptypes());
             if (constructor == null) {
diff --git a/ojluni/src/main/java/java/lang/reflect/Proxy.java b/ojluni/src/main/java/java/lang/reflect/Proxy.java
index 209ccb1..c9e56cf 100644
--- a/ojluni/src/main/java/java/lang/reflect/Proxy.java
+++ b/ojluni/src/main/java/java/lang/reflect/Proxy.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,25 +26,30 @@
 
 package java.lang.reflect;
 
+
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
-import java.security.AccessController;
 import java.security.Permission;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiFunction;
+import libcore.util.EmptyArray;
 import sun.reflect.CallerSensitive;
 import sun.reflect.misc.ReflectUtil;
 import sun.security.util.SecurityConstants;
-import libcore.util.EmptyArray;
 
 /**
  * {@code Proxy} provides static methods for creating dynamic proxy
@@ -54,16 +59,14 @@
  * <p>To create a proxy for some interface {@code Foo}:
  * <pre>
  *     InvocationHandler handler = new MyInvocationHandler(...);
- *     Class proxyClass = Proxy.getProxyClass(
- *         Foo.class.getClassLoader(), new Class[] { Foo.class });
- *     Foo f = (Foo) proxyClass.
- *         getConstructor(new Class[] { InvocationHandler.class }).
- *         newInstance(new Object[] { handler });
+ *     Class&lt;?&gt; proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
+ *     Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
+ *                     newInstance(handler);
  * </pre>
  * or more simply:
  * <pre>
  *     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
- *                                          new Class[] { Foo.class },
+ *                                          new Class&lt;?&gt;[] { Foo.class },
  *                                          handler);
  * </pre>
  *
@@ -92,7 +95,11 @@
  * <p>A proxy class has the following properties:
  *
  * <ul>
- * <li>Proxy classes are public, final, and not abstract.
+ * <li>Proxy classes are <em>public, final, and not abstract</em> if
+ * all proxy interfaces are public.</li>
+ *
+ * <li>Proxy classes are <em>non-public, final, and not abstract</em> if
+ * any of the proxy interfaces is non-public.</li>
  *
  * <li>The unqualified name of a proxy class is unspecified.  The space
  * of class names that begin with the string {@code "$Proxy"}
@@ -236,23 +243,14 @@
     private final static String proxyClassNamePrefix = "$Proxy";
 
     /** parameter types of a proxy class constructor */
-    private final static Class[] constructorParams =
+    private static final Class<?>[] constructorParams =
         { InvocationHandler.class };
 
-    /** maps a class loader to the proxy class cache for that loader */
-    private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
-        = new WeakHashMap<>();
-
-    /** marks that a particular proxy class is currently being generated */
-    private static Object pendingGenerationMarker = new Object();
-
-    /** next number to use for generation of unique proxy class names */
-    private static long nextUniqueNumber = 0;
-    private static Object nextUniqueNumberLock = new Object();
-
-    /** set of all generated proxy classes, for isProxyClass implementation */
-    private static Map<Class<?>, Void> proxyClasses =
-        Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());
+    /**
+     * a cache of proxy classes
+     */
+    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
+        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
 
     /**
      * the invocation handler for this proxy instance.
@@ -296,9 +294,13 @@
      * (typically, a dynamic proxy class) with the specified value
      * for its invocation handler.
      *
-     * @param   h the invocation handler for this proxy instance
+     * @param  h the invocation handler for this proxy instance
+     *
+     * @throws NullPointerException if the given invocation handler, {@code h},
+     *         is {@code null}.
      */
     protected Proxy(InvocationHandler h) {
+        Objects.requireNonNull(h);
         this.h = h;
     }
 
@@ -306,9 +308,10 @@
      * Returns the {@code java.lang.Class} object for a proxy class
      * given a class loader and an array of interfaces.  The proxy class
      * will be defined by the specified class loader and will implement
-     * all of the supplied interfaces.  If a proxy class for the same
-     * permutation of interfaces has already been defined by the class
-     * loader, then the existing proxy class will be returned; otherwise,
+     * all of the supplied interfaces.  If any of the given interfaces
+     * is non-public, the proxy class will be non-public. If a proxy class
+     * for the same permutation of interfaces has already been defined by the
+     * class loader, then the existing proxy class will be returned; otherwise,
      * a proxy class for those interfaces will be generated dynamically
      * and defined by the class loader.
      *
@@ -373,6 +376,21 @@
      * @throws  IllegalArgumentException if any of the restrictions on the
      *          parameters that may be passed to {@code getProxyClass}
      *          are violated
+     * @throws  SecurityException if a security manager, <em>s</em>, is present
+     *          and any of the following conditions is met:
+     *          <ul>
+     *             <li> the given {@code loader} is {@code null} and
+     *             the caller's class loader is not {@code null} and the
+     *             invocation of {@link SecurityManager#checkPermission
+     *             s.checkPermission} with
+     *             {@code RuntimePermission("getClassLoader")} permission
+     *             denies access.</li>
+     *             <li> for each proxy interface, {@code intf},
+     *             the caller's class loader is not the same as or an
+     *             ancestor of the class loader for {@code intf} and
+     *             invocation of {@link SecurityManager#checkPackageAccess
+     *             s.checkPackageAccess()} denies access to {@code intf}.</li>
+     *          </ul>
      * @throws  NullPointerException if the {@code interfaces} array
      *          argument or any of its elements are {@code null}
      */
@@ -394,142 +412,204 @@
             throw new IllegalArgumentException("interface limit exceeded");
         }
 
-        Class<?> proxyClass = null;
+        // If the proxy class defined by the given loader implementing
+        // the given interfaces exists, this will simply return the cached copy;
+        // otherwise, it will create the proxy class via the ProxyClassFactory
+        return proxyClassCache.get(loader, interfaces);
+    }
 
-        /* collect interface names to use as key for proxy class cache */
-        String[] interfaceNames = new String[interfaces.length];
+    /*
+     * a key used for proxy class with 0 implemented interfaces
+     */
+    private static final Object key0 = new Object();
 
-        // for detecting duplicates
-        Set<Class<?>> interfaceSet = new HashSet<>();
+    /*
+     * Key1 and Key2 are optimized for the common use of dynamic proxies
+     * that implement 1 or 2 interfaces.
+     */
 
-        for (int i = 0; i < interfaces.length; i++) {
-            /*
-             * Verify that the class loader resolves the name of this
-             * interface to the same Class object.
-             */
-            String interfaceName = interfaces[i].getName();
-            Class<?> interfaceClass = null;
-            try {
-                interfaceClass = Class.forName(interfaceName, false, loader);
-            } catch (ClassNotFoundException e) {
-            }
-            if (interfaceClass != interfaces[i]) {
-                throw new IllegalArgumentException(
-                    interfaces[i] + " is not visible from class loader");
-            }
+    /*
+     * a key used for proxy class with 1 implemented interface
+     */
+    private static final class Key1 extends WeakReference<Class<?>> {
+        private final int hash;
 
-            /*
-             * Verify that the Class object actually represents an
-             * interface.
-             */
-            if (!interfaceClass.isInterface()) {
-                throw new IllegalArgumentException(
-                    interfaceClass.getName() + " is not an interface");
-            }
-
-            /*
-             * Verify that this interface is not a duplicate.
-             */
-            if (interfaceSet.contains(interfaceClass)) {
-                throw new IllegalArgumentException(
-                    "repeated interface: " + interfaceClass.getName());
-            }
-            interfaceSet.add(interfaceClass);
-
-            interfaceNames[i] = interfaceName;
+        Key1(Class<?> intf) {
+            super(intf);
+            this.hash = intf.hashCode();
         }
 
-        /*
-         * Using string representations of the proxy interfaces as
-         * keys in the proxy class cache (instead of their Class
-         * objects) is sufficient because we require the proxy
-         * interfaces to be resolvable by name through the supplied
-         * class loader, and it has the advantage that using a string
-         * representation of a class makes for an implicit weak
-         * reference to the class.
-         */
-        List<String> key = Arrays.asList(interfaceNames);
-
-        /*
-         * Find or create the proxy class cache for the class loader.
-         */
-        Map<List<String>, Object> cache;
-        synchronized (loaderToCache) {
-            cache = loaderToCache.get(loader);
-            if (cache == null) {
-                cache = new HashMap<>();
-                loaderToCache.put(loader, cache);
-            }
-            /*
-             * This mapping will remain valid for the duration of this
-             * method, without further synchronization, because the mapping
-             * will only be removed if the class loader becomes unreachable.
-             */
+        @Override
+        public int hashCode() {
+            return hash;
         }
 
-        /*
-         * Look up the list of interfaces in the proxy class cache using
-         * the key.  This lookup will result in one of three possible
-         * kinds of values:
-         *     null, if there is currently no proxy class for the list of
-         *         interfaces in the class loader,
-         *     the pendingGenerationMarker object, if a proxy class for the
-         *         list of interfaces is currently being generated,
-         *     or a weak reference to a Class object, if a proxy class for
-         *         the list of interfaces has already been generated.
-         */
-        synchronized (cache) {
-            /*
-             * Note that we need not worry about reaping the cache for
-             * entries with cleared weak references because if a proxy class
-             * has been garbage collected, its class loader will have been
-             * garbage collected as well, so the entire cache will be reaped
-             * from the loaderToCache map.
-             */
-            do {
-                Object value = cache.get(key);
-                if (value instanceof Reference) {
-                    proxyClass = (Class<?>) ((Reference) value).get();
+        @Override
+        public boolean equals(Object obj) {
+            Class<?> intf;
+            return this == obj ||
+                   obj != null &&
+                   obj.getClass() == Key1.class &&
+                   (intf = get()) != null &&
+                   intf == ((Key1) obj).get();
+        }
+    }
+
+    /*
+     * a key used for proxy class with 2 implemented interfaces
+     */
+    private static final class Key2 extends WeakReference<Class<?>> {
+        private final int hash;
+        private final WeakReference<Class<?>> ref2;
+
+        Key2(Class<?> intf1, Class<?> intf2) {
+            super(intf1);
+            hash = 31 * intf1.hashCode() + intf2.hashCode();
+            ref2 = new WeakReference<Class<?>>(intf2);
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            Class<?> intf1, intf2;
+            return this == obj ||
+                   obj != null &&
+                   obj.getClass() == Key2.class &&
+                   (intf1 = get()) != null &&
+                   intf1 == ((Key2) obj).get() &&
+                   (intf2 = ref2.get()) != null &&
+                   intf2 == ((Key2) obj).ref2.get();
+        }
+    }
+
+    /*
+     * a key used for proxy class with any number of implemented interfaces
+     * (used here for 3 or more only)
+     */
+    private static final class KeyX {
+        private final int hash;
+        private final WeakReference<Class<?>>[] refs;
+
+        @SuppressWarnings("unchecked")
+        KeyX(Class<?>[] interfaces) {
+            hash = Arrays.hashCode(interfaces);
+            refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.length];
+            for (int i = 0; i < interfaces.length; i++) {
+                refs[i] = new WeakReference<>(interfaces[i]);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return this == obj ||
+                   obj != null &&
+                   obj.getClass() == KeyX.class &&
+                   equals(refs, ((KeyX) obj).refs);
+        }
+
+        private static boolean equals(WeakReference<Class<?>>[] refs1,
+                                      WeakReference<Class<?>>[] refs2) {
+            if (refs1.length != refs2.length) {
+                return false;
+            }
+            for (int i = 0; i < refs1.length; i++) {
+                Class<?> intf = refs1[i].get();
+                if (intf == null || intf != refs2[i].get()) {
+                    return false;
                 }
-                if (proxyClass != null) {
-                    // proxy class already generated: return it
-                    return proxyClass;
-                } else if (value == pendingGenerationMarker) {
-                    // proxy class being generated: wait for it
-                    try {
-                        cache.wait();
-                    } catch (InterruptedException e) {
-                        /*
-                         * The class generation that we are waiting for should
-                         * take a small, bounded time, so we can safely ignore
-                         * thread interrupts here.
-                         */
-                    }
-                    continue;
-                } else {
-                    /*
-                     * No proxy class for this list of interfaces has been
-                     * generated or is being generated, so we will go and
-                     * generate it now.  Mark it as pending generation.
-                     */
-                    cache.put(key, pendingGenerationMarker);
-                    break;
-                }
-            } while (true);
+            }
+            return true;
         }
+    }
 
-        try {
+    /**
+     * A function that maps an array of interfaces to an optimal key where
+     * Class objects representing interfaces are weakly referenced.
+     */
+    private static final class KeyFactory
+        implements BiFunction<ClassLoader, Class<?>[], Object>
+    {
+        @Override
+        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
+            switch (interfaces.length) {
+                case 1: return new Key1(interfaces[0]); // the most frequent
+                case 2: return new Key2(interfaces[0], interfaces[1]);
+                case 0: return key0;
+                default: return new KeyX(interfaces);
+            }
+        }
+    }
+
+    /**
+     * A factory function that generates, defines and returns the proxy class given
+     * the ClassLoader and array of interfaces.
+     */
+    private static final class ProxyClassFactory
+        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
+    {
+        // prefix for all proxy class names
+        private static final String proxyClassNamePrefix = "$Proxy";
+
+        // next number to use for generation of unique proxy class names
+        private static final AtomicLong nextUniqueNumber = new AtomicLong();
+
+        @Override
+        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
+
+            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
+            for (Class<?> intf : interfaces) {
+                /*
+                 * Verify that the class loader resolves the name of this
+                 * interface to the same Class object.
+                 */
+                Class<?> interfaceClass = null;
+                try {
+                    interfaceClass = Class.forName(intf.getName(), false, loader);
+                } catch (ClassNotFoundException e) {
+                }
+                if (interfaceClass != intf) {
+                    throw new IllegalArgumentException(
+                        intf + " is not visible from class loader");
+                }
+                /*
+                 * Verify that the Class object actually represents an
+                 * interface.
+                 */
+                if (!interfaceClass.isInterface()) {
+                    throw new IllegalArgumentException(
+                        interfaceClass.getName() + " is not an interface");
+                }
+                /*
+                 * Verify that this interface is not a duplicate.
+                 */
+                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
+                    throw new IllegalArgumentException(
+                        "repeated interface: " + interfaceClass.getName());
+                }
+            }
+
             String proxyPkg = null;     // package to define proxy class in
+            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
 
             /*
              * Record the package of a non-public proxy interface so that the
              * proxy class will be defined in the same package.  Verify that
              * all non-public proxy interfaces are in the same package.
              */
-            for (int i = 0; i < interfaces.length; i++) {
-                int flags = interfaces[i].getModifiers();
+            for (Class<?> intf : interfaces) {
+                int flags = intf.getModifiers();
                 if (!Modifier.isPublic(flags)) {
-                    String name = interfaces[i].getName();
+                    accessFlags = Modifier.FINAL;
+                    String name = intf.getName();
                     int n = name.lastIndexOf('.');
                     String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                     if (proxyPkg == null) {
@@ -560,36 +640,13 @@
                 /*
                  * Choose a name for the proxy class to generate.
                  */
-                final long num;
-                synchronized (nextUniqueNumberLock) {
-                    num = nextUniqueNumber++;
-                }
+                long num = nextUniqueNumber.getAndIncrement();
                 String proxyName = proxyPkg + proxyClassNamePrefix + num;
 
-                proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
-                        exceptionsArray);
-            }
-            // add to set of all generated proxy classes, for isProxyClass
-            proxyClasses.put(proxyClass, null);
-
-        } finally {
-            /*
-             * We must clean up the "pending generation" state of the proxy
-             * class cache entry somehow.  If a proxy class was successfully
-             * generated, store it in the cache (with a weak reference);
-             * otherwise, remove the reserved entry.  In all cases, notify
-             * all waiters on reserved entries in this cache.
-             */
-            synchronized (cache) {
-                if (proxyClass != null) {
-                    cache.put(key, new WeakReference<Class<?>>(proxyClass));
-                } else {
-                    cache.remove(key);
-                }
-                cache.notifyAll();
+                return generateProxy(proxyName, interfaces, loader, methodsArray,
+                                     exceptionsArray);
             }
         }
-        return proxyClass;
     }
 
     /**
@@ -643,7 +700,6 @@
         return intersection.toArray(new Class<?>[intersection.size()]);
     }
 
-
     /**
      * Throws if any two methods in {@code methods} have the same name and
      * parameters but incompatible return types.
@@ -696,15 +752,11 @@
         }
     }
 
+
     /**
      * Returns an instance of a proxy class for the specified interfaces
      * that dispatches method invocations to the specified invocation
-     * handler.  This method is equivalent to:
-     * <pre>
-     *     Proxy.getProxyClass(loader, interfaces).
-     *         getConstructor(new Class[] { InvocationHandler.class }).
-     *         newInstance(new Object[] { handler });
-     * </pre>
+     * handler.
      *
      * <p>{@code Proxy.newProxyInstance} throws
      * {@code IllegalArgumentException} for the same reasons that
@@ -720,6 +772,27 @@
      * @throws  IllegalArgumentException if any of the restrictions on the
      *          parameters that may be passed to {@code getProxyClass}
      *          are violated
+     * @throws  SecurityException if a security manager, <em>s</em>, is present
+     *          and any of the following conditions is met:
+     *          <ul>
+     *          <li> the given {@code loader} is {@code null} and
+     *               the caller's class loader is not {@code null} and the
+     *               invocation of {@link SecurityManager#checkPermission
+     *               s.checkPermission} with
+     *               {@code RuntimePermission("getClassLoader")} permission
+     *               denies access;</li>
+     *          <li> for each proxy interface, {@code intf},
+     *               the caller's class loader is not the same as or an
+     *               ancestor of the class loader for {@code intf} and
+     *               invocation of {@link SecurityManager#checkPackageAccess
+     *               s.checkPackageAccess()} denies access to {@code intf};</li>
+     *          <li> any of the given proxy interfaces is non-public and the
+     *               caller class is not in the same {@linkplain Package runtime package}
+     *               as the non-public interface and the invocation of
+     *               {@link SecurityManager#checkPermission s.checkPermission} with
+     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
+     *               permission denies access.</li>
+     *          </ul>
      * @throws  NullPointerException if the {@code interfaces} array
      *          argument or any of its elements are {@code null}, or
      *          if the invocation handler, {@code h}, is
@@ -731,38 +804,47 @@
                                           InvocationHandler h)
         throws IllegalArgumentException
     {
-        if (h == null) {
-            throw new NullPointerException();
-        }
+        Objects.requireNonNull(h);
+
+        final Class<?>[] intfs = interfaces.clone();
+        // Android-changed: sm is always null
+        // final SecurityManager sm = System.getSecurityManager();
+        // if (sm != null) {
+        //     checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
+        // }
 
         /*
          * Look up or generate the designated proxy class.
          */
-        Class<?> cl = getProxyClass0(loader, interfaces);
+        Class<?> cl = getProxyClass0(loader, intfs);
 
         /*
          * Invoke its constructor with the designated invocation handler.
          */
         try {
-            final Constructor<?> cons = cl.getConstructor(constructorParams);
-            return newInstance(cons, h);
-        } catch (NoSuchMethodException e) {
-            throw new InternalError(e.toString());
-        }
-    }
+            // Android-changed: sm is always null
+            // if (sm != null) {
+            //     checkNewProxyPermission(Reflection.getCallerClass(), cl);
+            // }
 
-    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
-        try {
-            return cons.newInstance(new Object[] {h} );
-        } catch (IllegalAccessException | InstantiationException e) {
-            throw new InternalError(e.toString());
+            final Constructor<?> cons = cl.getConstructor(constructorParams);
+            final InvocationHandler ih = h;
+            if (!Modifier.isPublic(cl.getModifiers())) {
+                // Android-changed: Removed AccessController.doPrivileged
+                cons.setAccessible(true);
+            }
+            return cons.newInstance(new Object[]{h});
+        } catch (IllegalAccessException|InstantiationException e) {
+            throw new InternalError(e.toString(), e);
         } catch (InvocationTargetException e) {
             Throwable t = e.getCause();
             if (t instanceof RuntimeException) {
                 throw (RuntimeException) t;
             } else {
-                throw new InternalError(t.toString());
+                throw new InternalError(t.toString(), t);
             }
+        } catch (NoSuchMethodException e) {
+            throw new InternalError(e.toString(), e);
         }
     }
 
@@ -781,11 +863,7 @@
      * @throws  NullPointerException if {@code cl} is {@code null}
      */
     public static boolean isProxyClass(Class<?> cl) {
-        if (cl == null) {
-            throw new NullPointerException();
-        }
-
-        return proxyClasses.containsKey(cl);
+        return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
     }
 
     /**
@@ -795,19 +873,40 @@
      * @return  the invocation handler for the proxy instance
      * @throws  IllegalArgumentException if the argument is not a
      *          proxy instance
+     * @throws  SecurityException if a security manager, <em>s</em>, is present
+     *          and the caller's class loader is not the same as or an
+     *          ancestor of the class loader for the invocation handler
+     *          and invocation of {@link SecurityManager#checkPackageAccess
+     *          s.checkPackageAccess()} denies access to the invocation
+     *          handler's class.
      */
+    @CallerSensitive
     public static InvocationHandler getInvocationHandler(Object proxy)
         throws IllegalArgumentException
     {
         /*
          * Verify that the object is actually a proxy instance.
          */
-        if (!(proxy instanceof Proxy)) {
+        if (!isProxyClass(proxy.getClass())) {
             throw new IllegalArgumentException("not a proxy instance");
         }
-        return ((Proxy) proxy).h;
+
+        final Proxy p = (Proxy) proxy;
+        final InvocationHandler ih = p.h;
+        // Android changed, System.getSecurityManager() is always null
+        // if (System.getSecurityManager() != null) {
+        //     Class<?> ihClass = ih.getClass();
+        //     Class<?> caller = Reflection.getCallerClass();
+        //     if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),
+        //                                             ihClass.getClassLoader()))
+        //     {
+        //         ReflectUtil.checkPackageAccess(ihClass);
+        //     }
+        // }
+        return ih;
     }
 
+    // Android-changed: helper for art native code.
     private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
         InvocationHandler h = proxy.h;
         return h.invoke(proxy, method, args);
diff --git a/ojluni/src/main/java/java/lang/reflect/WeakCache.java b/ojluni/src/main/java/java/lang/reflect/WeakCache.java
new file mode 100644
index 0000000..4c28024
--- /dev/null
+++ b/ojluni/src/main/java/java/lang/reflect/WeakCache.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang.reflect;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+/**
+ * Cache mapping pairs of {@code (key, sub-key) -> value}. Keys and values are
+ * weakly but sub-keys are strongly referenced.  Keys are passed directly to
+ * {@link #get} method which also takes a {@code parameter}. Sub-keys are
+ * calculated from keys and parameters using the {@code subKeyFactory} function
+ * passed to the constructor. Values are calculated from keys and parameters
+ * using the {@code valueFactory} function passed to the constructor.
+ * Keys can be {@code null} and are compared by identity while sub-keys returned by
+ * {@code subKeyFactory} or values returned by {@code valueFactory}
+ * can not be null. Sub-keys are compared using their {@link #equals} method.
+ * Entries are expunged from cache lazily on each invocation to {@link #get},
+ * {@link #containsValue} or {@link #size} methods when the WeakReferences to
+ * keys are cleared. Cleared WeakReferences to individual values don't cause
+ * expunging, but such entries are logically treated as non-existent and
+ * trigger re-evaluation of {@code valueFactory} on request for their
+ * key/subKey.
+ *
+ * @author Peter Levart
+ * @param <K> type of keys
+ * @param <P> type of parameters
+ * @param <V> type of values
+ */
+final class WeakCache<K, P, V> {
+
+    private final ReferenceQueue<K> refQueue
+        = new ReferenceQueue<>();
+    // the key type is Object for supporting null key
+    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
+        = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
+        = new ConcurrentHashMap<>();
+    private final BiFunction<K, P, ?> subKeyFactory;
+    private final BiFunction<K, P, V> valueFactory;
+
+    /**
+     * Construct an instance of {@code WeakCache}
+     *
+     * @param subKeyFactory a function mapping a pair of
+     *                      {@code (key, parameter) -> sub-key}
+     * @param valueFactory  a function mapping a pair of
+     *                      {@code (key, parameter) -> value}
+     * @throws NullPointerException if {@code subKeyFactory} or
+     *                              {@code valueFactory} is null.
+     */
+    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
+                     BiFunction<K, P, V> valueFactory) {
+        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
+        this.valueFactory = Objects.requireNonNull(valueFactory);
+    }
+
+    /**
+     * Look-up the value through the cache. This always evaluates the
+     * {@code subKeyFactory} function and optionally evaluates
+     * {@code valueFactory} function if there is no entry in the cache for given
+     * pair of (key, subKey) or the entry has already been cleared.
+     *
+     * @param key       possibly null key
+     * @param parameter parameter used together with key to create sub-key and
+     *                  value (should not be null)
+     * @return the cached value (never null)
+     * @throws NullPointerException if {@code parameter} passed in or
+     *                              {@code sub-key} calculated by
+     *                              {@code subKeyFactory} or {@code value}
+     *                              calculated by {@code valueFactory} is null.
+     */
+    public V get(K key, P parameter) {
+        Objects.requireNonNull(parameter);
+
+        expungeStaleEntries();
+
+        Object cacheKey = CacheKey.valueOf(key, refQueue);
+
+        // lazily install the 2nd level valuesMap for the particular cacheKey
+        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
+        if (valuesMap == null) {
+            ConcurrentMap<Object, Supplier<V>> oldValuesMap
+                = map.putIfAbsent(cacheKey,
+                                  valuesMap = new ConcurrentHashMap<>());
+            if (oldValuesMap != null) {
+                valuesMap = oldValuesMap;
+            }
+        }
+
+        // create subKey and retrieve the possible Supplier<V> stored by that
+        // subKey from valuesMap
+        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
+        Supplier<V> supplier = valuesMap.get(subKey);
+        Factory factory = null;
+
+        while (true) {
+            if (supplier != null) {
+                // supplier might be a Factory or a CacheValue<V> instance
+                V value = supplier.get();
+                if (value != null) {
+                    return value;
+                }
+            }
+            // else no supplier in cache
+            // or a supplier that returned null (could be a cleared CacheValue
+            // or a Factory that wasn't successful in installing the CacheValue)
+
+            // lazily construct a Factory
+            if (factory == null) {
+                factory = new Factory(key, parameter, subKey, valuesMap);
+            }
+
+            if (supplier == null) {
+                supplier = valuesMap.putIfAbsent(subKey, factory);
+                if (supplier == null) {
+                    // successfully installed Factory
+                    supplier = factory;
+                }
+                // else retry with winning supplier
+            } else {
+                if (valuesMap.replace(subKey, supplier, factory)) {
+                    // successfully replaced
+                    // cleared CacheEntry / unsuccessful Factory
+                    // with our Factory
+                    supplier = factory;
+                } else {
+                    // retry with current supplier
+                    supplier = valuesMap.get(subKey);
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks whether the specified non-null value is already present in this
+     * {@code WeakCache}. The check is made using identity comparison regardless
+     * of whether value's class overrides {@link Object#equals} or not.
+     *
+     * @param value the non-null value to check
+     * @return true if given {@code value} is already cached
+     * @throws NullPointerException if value is null
+     */
+    public boolean containsValue(V value) {
+        Objects.requireNonNull(value);
+
+        expungeStaleEntries();
+        return reverseMap.containsKey(new LookupValue<>(value));
+    }
+
+    /**
+     * Returns the current number of cached entries that
+     * can decrease over time when keys/values are GC-ed.
+     */
+    public int size() {
+        expungeStaleEntries();
+        return reverseMap.size();
+    }
+
+    private void expungeStaleEntries() {
+        CacheKey<K> cacheKey;
+        while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
+            cacheKey.expungeFrom(map, reverseMap);
+        }
+    }
+
+    /**
+     * A factory {@link Supplier} that implements the lazy synchronized
+     * construction of the value and installment of it into the cache.
+     */
+    private final class Factory implements Supplier<V> {
+
+        private final K key;
+        private final P parameter;
+        private final Object subKey;
+        private final ConcurrentMap<Object, Supplier<V>> valuesMap;
+
+        Factory(K key, P parameter, Object subKey,
+                ConcurrentMap<Object, Supplier<V>> valuesMap) {
+            this.key = key;
+            this.parameter = parameter;
+            this.subKey = subKey;
+            this.valuesMap = valuesMap;
+        }
+
+        @Override
+        public synchronized V get() { // serialize access
+            // re-check
+            Supplier<V> supplier = valuesMap.get(subKey);
+            if (supplier != this) {
+                // something changed while we were waiting:
+                // might be that we were replaced by a CacheValue
+                // or were removed because of failure ->
+                // return null to signal WeakCache.get() to retry
+                // the loop
+                return null;
+            }
+            // else still us (supplier == this)
+
+            // create new value
+            V value = null;
+            try {
+                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
+            } finally {
+                if (value == null) { // remove us on failure
+                    valuesMap.remove(subKey, this);
+                }
+            }
+            // the only path to reach here is with non-null value
+            assert value != null;
+
+            // wrap value with CacheValue (WeakReference)
+            CacheValue<V> cacheValue = new CacheValue<>(value);
+
+            // try replacing us with CacheValue (this should always succeed)
+            if (valuesMap.replace(subKey, this, cacheValue)) {
+                // put also in reverseMap
+                reverseMap.put(cacheValue, Boolean.TRUE);
+            } else {
+                throw new AssertionError("Should not reach here");
+            }
+
+            // successfully replaced us with new CacheValue -> return the value
+            // wrapped by it
+            return value;
+        }
+    }
+
+    /**
+     * Common type of value suppliers that are holding a referent.
+     * The {@link #equals} and {@link #hashCode} of implementations is defined
+     * to compare the referent by identity.
+     */
+    private interface Value<V> extends Supplier<V> {}
+
+    /**
+     * An optimized {@link Value} used to look-up the value in
+     * {@link WeakCache#containsValue} method so that we are not
+     * constructing the whole {@link CacheValue} just to look-up the referent.
+     */
+    private static final class LookupValue<V> implements Value<V> {
+        private final V value;
+
+        LookupValue(V value) {
+            this.value = value;
+        }
+
+        @Override
+        public V get() {
+            return value;
+        }
+
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(value); // compare by identity
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj == this ||
+                   obj instanceof Value &&
+                   this.value == ((Value<?>) obj).get();  // compare by identity
+        }
+    }
+
+    /**
+     * A {@link Value} that weakly references the referent.
+     */
+    private static final class CacheValue<V>
+        extends WeakReference<V> implements Value<V>
+    {
+        private final int hash;
+
+        CacheValue(V value) {
+            super(value);
+            this.hash = System.identityHashCode(value); // compare by identity
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            V value;
+            return obj == this ||
+                   obj instanceof Value &&
+                   // cleared CacheValue is only equal to itself
+                   (value = get()) != null &&
+                   value == ((Value<?>) obj).get(); // compare by identity
+        }
+    }
+
+    /**
+     * CacheKey containing a weakly referenced {@code key}. It registers
+     * itself with the {@code refQueue} so that it can be used to expunge
+     * the entry when the {@link WeakReference} is cleared.
+     */
+    private static final class CacheKey<K> extends WeakReference<K> {
+
+        // a replacement for null keys
+        private static final Object NULL_KEY = new Object();
+
+        static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
+            return key == null
+                   // null key means we can't weakly reference it,
+                   // so we use a NULL_KEY singleton as cache key
+                   ? NULL_KEY
+                   // non-null key requires wrapping with a WeakReference
+                   : new CacheKey<>(key, refQueue);
+        }
+
+        private final int hash;
+
+        private CacheKey(K key, ReferenceQueue<K> refQueue) {
+            super(key, refQueue);
+            this.hash = System.identityHashCode(key);  // compare by identity
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            K key;
+            return obj == this ||
+                   obj != null &&
+                   obj.getClass() == this.getClass() &&
+                   // cleared CacheKey is only equal to itself
+                   (key = this.get()) != null &&
+                   // compare key by identity
+                   key == ((CacheKey<K>) obj).get();
+        }
+
+        void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
+                         ConcurrentMap<?, Boolean> reverseMap) {
+            // removing just by key is always safe here because after a CacheKey
+            // is cleared and enqueue-ed it is only equal to itself
+            // (see equals method)...
+            ConcurrentMap<?, ?> valuesMap = map.remove(this);
+            // remove also from reverseMap if needed
+            if (valuesMap != null) {
+                for (Object cacheValue : valuesMap.values()) {
+                    reverseMap.remove(cacheValue);
+                }
+            }
+        }
+    }
+}
diff --git a/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java b/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
index f6b221c..ea45500 100644
--- a/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -24,23 +24,11 @@
  */
 package java.net;
 
-import android.system.StructGroupReq;
-
 import java.io.IOException;
-import libcore.io.IoBridge;
-import libcore.util.EmptyArray;
-
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
 import jdk.net.*;
-
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.AF_UNSPEC;
-import static android.system.OsConstants.MSG_PEEK;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static libcore.io.IoBridge.JAVA_IP_MULTICAST_TTL;
-import static libcore.io.IoBridge.JAVA_MCAST_JOIN_GROUP;
-import static libcore.io.IoBridge.JAVA_MCAST_LEAVE_GROUP;
-import static libcore.io.IoBridge.setSocketOption;
 import static sun.net.ExtendedOptionsImpl.*;
 
 /*
@@ -90,139 +78,44 @@
         }
     }
 
-    protected synchronized void bind0(int lport, InetAddress laddr) throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
+    protected synchronized native void bind0(int lport, InetAddress laddr)
+        throws SocketException;
 
-        IoBridge.bind(fd, laddr, lport);
+    protected native void send(DatagramPacket p) throws IOException;
 
-        if (lport == 0) {
-            // Now that we're a connected socket, let's extract the port number that the system
-            // chose for us and store it in the Socket object.
-            localPort = IoBridge.getLocalInetSocketAddress(fd).getPort();
-        } else {
-            localPort = lport;
-        }
-    }
+    protected synchronized native int peek(InetAddress i) throws IOException;
 
-    protected void send(DatagramPacket p) throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-        if (p.getData() == null || p.getAddress() == null) {
-            throw new NullPointerException("null buffer || null address");
-        }
+    protected synchronized native int peekData(DatagramPacket p) throws IOException;
 
-        int port = connected ? 0 : p.getPort();
-        InetAddress address = connected ? null : p.getAddress();
-        IoBridge.sendto(fd, p.getData(), p.getOffset(), p.getLength(), 0, address, port);
-    }
+    protected synchronized native void receive0(DatagramPacket p)
+        throws IOException;
 
-    protected synchronized int peek(InetAddress i) throws IOException {
-        DatagramPacket p = new DatagramPacket(EmptyArray.BYTE, 0);
-        doRecv(p, MSG_PEEK);
-        i.holder().address = p.getAddress().holder().address;
-        return p.getPort();
-    }
+    protected native void setTimeToLive(int ttl) throws IOException;
 
-    protected synchronized int peekData(DatagramPacket p) throws IOException {
-        doRecv(p, MSG_PEEK);
-        return p.getPort();
-    }
+    protected native int getTimeToLive() throws IOException;
 
-    protected synchronized void receive0(DatagramPacket p) throws IOException {
-        doRecv(p, 0);
-    }
+    protected native void setTTL(byte ttl) throws IOException;
 
-    private void doRecv(DatagramPacket p, int flags) throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
+    protected native byte getTTL() throws IOException;
 
-        if (timeout != 0) {
-            IoBridge.poll(fd, POLLIN, timeout);
-        }
+    protected native void join(InetAddress inetaddr, NetworkInterface netIf)
+        throws IOException;
 
-        IoBridge.recvfrom(false, fd, p.getData(), p.getOffset(), p.getLength(), flags, p,
-                connected);
-    }
+    protected native void leave(InetAddress inetaddr, NetworkInterface netIf)
+        throws IOException;
 
-    protected void setTimeToLive(int ttl) throws IOException {
-        IoBridge.setSocketOption(fd, JAVA_IP_MULTICAST_TTL, ttl);
-    }
+    protected native void datagramSocketCreate() throws SocketException;
 
-    protected int getTimeToLive() throws IOException {
-        return (Integer) IoBridge.getSocketOption(fd, JAVA_IP_MULTICAST_TTL);
-    }
-
-    protected void setTTL(byte ttl) throws IOException {
-        setTimeToLive((int) ttl & 0xff);
-    }
-
-    protected byte getTTL() throws IOException {
-        return (byte) getTimeToLive();
-    }
-
-    private static StructGroupReq makeGroupReq(InetAddress gr_group,
-            NetworkInterface networkInterface) {
-        int gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
-        return new StructGroupReq(gr_interface, gr_group);
-    }
-
-    protected void join(InetAddress inetaddr, NetworkInterface netIf) throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-
-        IoBridge.setSocketOption(fd, JAVA_MCAST_JOIN_GROUP, makeGroupReq(inetaddr, netIf));
-    }
-
-    protected void leave(InetAddress inetaddr, NetworkInterface netIf)
-        throws IOException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
-
-        IoBridge.setSocketOption(fd, JAVA_MCAST_LEAVE_GROUP, makeGroupReq(inetaddr, netIf));
-    }
-
-    protected void datagramSocketCreate() throws SocketException {
-        fd = IoBridge.socket(AF_INET6, SOCK_DGRAM, 0);
-        setSocketOption(fd, SO_BROADCAST, true);
-    }
-
-    protected void datagramSocketClose() {
-        try {
-            IoBridge.closeAndSignalBlockedThreads(fd);
-        } catch (IOException ignored) { }
-    }
+    protected native void datagramSocketClose();
 
     protected native void socketSetOption0(int opt, Object val)
         throws SocketException;
 
     protected native Object socketGetOption(int opt) throws SocketException;
 
-    protected void connect0(InetAddress address, int port) throws SocketException {
-        if (isClosed()) {
-            throw new SocketException("Socket closed");
-        }
+    protected native void connect0(InetAddress address, int port) throws SocketException;
 
-        IoBridge.connect(fd, address, port);
-    }
-
-    protected void disconnect0(int family) {
-        if (isClosed()) {
-            return;
-        }
-
-        InetAddress inetAddressUnspec = new InetAddress();
-        inetAddressUnspec.holder().family = AF_UNSPEC;
-
-        try {
-            IoBridge.connect(fd, inetAddressUnspec, 0);
-        } catch (SocketException ignored) { }
-    }
+    protected native void disconnect0(int family);
 
     /**
      * Perform class load-time initializations.
diff --git a/ojluni/src/main/java/java/nio/ByteBuffer.java b/ojluni/src/main/java/java/nio/ByteBuffer.java
index c78447f..7f51a61 100644
--- a/ojluni/src/main/java/java/nio/ByteBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBuffer.java
@@ -555,14 +555,14 @@
             // isDirect() doesn't imply !hasArray(), ByteBuffer.allocateDirect allocated buffer will
             // have a backing, non-gc-movable byte array. JNI allocated direct byte buffers WILL NOT
             // have a backing array.
-            final Object srcObject = src.isDirect() ? src : src.array();
+            final Object srcObject = src.isDirect() ? src : src.hb;
             int srcOffset = src.position();
             if (!src.isDirect()) {
                 srcOffset += src.offset;
             }
 
             final ByteBuffer dst = this;
-            final Object dstObject = dst.isDirect() ? dst : dst.array();
+            final Object dstObject = dst.isDirect() ? dst : dst.hb;
             int dstOffset = dst.position();
             if (!dst.isDirect()) {
                 dstOffset += dst.offset;
diff --git a/ojluni/src/main/java/java/util/ArrayDeque.java b/ojluni/src/main/java/java/util/ArrayDeque.java
index 83ae076..ecaf0e8 100644
--- a/ojluni/src/main/java/java/util/ArrayDeque.java
+++ b/ojluni/src/main/java/java/util/ArrayDeque.java
@@ -27,6 +27,7 @@
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file:
+ *
  * Written by Josh Bloch of Google Inc. and released to the public domain,
  * as explained at http://creativecommons.org/publicdomain/zero/1.0/.
  */
@@ -907,7 +908,7 @@
      * @since 1.8
      */
     public Spliterator<E> spliterator() {
-        return new DeqSpliterator<E>(this, -1, -1);
+        return new DeqSpliterator<>(this, -1, -1);
     }
 
     static final class DeqSpliterator<E> implements Spliterator<E> {
diff --git a/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java b/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java
index d3b5614..87c04d1 100644
--- a/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java
+++ b/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java
@@ -1,4 +1,33 @@
 /*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file:
+ *
  * Written by Doug Lea with assistance from members of JCP JSR-166
  * Expert Group and released to the public domain, as explained at
  * http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/Deque.java b/ojluni/src/main/java/java/util/Deque.java
index 9fec73b..3520f53 100644
--- a/ojluni/src/main/java/java/util/Deque.java
+++ b/ojluni/src/main/java/java/util/Deque.java
@@ -27,6 +27,7 @@
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file:
+ *
  * Written by Doug Lea and Josh Bloch with assistance from members of
  * JCP JSR-166 Expert Group and released to the public domain, as explained
  * at http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/NavigableMap.java b/ojluni/src/main/java/java/util/NavigableMap.java
index fdbc0b5..66b57c7 100644
--- a/ojluni/src/main/java/java/util/NavigableMap.java
+++ b/ojluni/src/main/java/java/util/NavigableMap.java
@@ -27,6 +27,7 @@
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file:
+ *
  * Written by Doug Lea and Josh Bloch with assistance from members of JCP
  * JSR-166 Expert Group and released to the public domain, as explained at
  * http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/NavigableSet.java b/ojluni/src/main/java/java/util/NavigableSet.java
index 412a07d..71d8dba 100644
--- a/ojluni/src/main/java/java/util/NavigableSet.java
+++ b/ojluni/src/main/java/java/util/NavigableSet.java
@@ -27,6 +27,7 @@
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file:
+ *
  * Written by Doug Lea and Josh Bloch with assistance from members of JCP
  * JSR-166 Expert Group and released to the public domain, as explained at
  * http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/Queue.java b/ojluni/src/main/java/java/util/Queue.java
index 6520337..cbdc205 100644
--- a/ojluni/src/main/java/java/util/Queue.java
+++ b/ojluni/src/main/java/java/util/Queue.java
@@ -27,6 +27,7 @@
  * License version 2 only, as published by the Free Software Foundation.
  * However, the following notice accompanied the original version of this
  * file:
+ *
  * Written by Doug Lea with assistance from members of JCP JSR-166
  * Expert Group and released to the public domain, as explained at
  * http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/SplittableRandom.java b/ojluni/src/main/java/java/util/SplittableRandom.java
index f17c37a..579102a 100644
--- a/ojluni/src/main/java/java/util/SplittableRandom.java
+++ b/ojluni/src/main/java/java/util/SplittableRandom.java
@@ -34,7 +34,6 @@
 import java.util.stream.LongStream;
 import java.util.stream.StreamSupport;
 
-
 /**
  * A generator of uniform pseudorandom values applicable for use in
  * (among other contexts) isolated parallel computations that may
diff --git a/ojluni/src/main/java/java/util/TreeMap.java b/ojluni/src/main/java/java/util/TreeMap.java
index b1e7e21..6b18547 100644
--- a/ojluni/src/main/java/java/util/TreeMap.java
+++ b/ojluni/src/main/java/java/util/TreeMap.java
@@ -536,7 +536,31 @@
     public V put(K key, V value) {
         TreeMapEntry<K,V> t = root;
         if (t == null) {
-            compare(key, key); // type (and possibly null) check
+            // We could just call compare(key, key) for its side effect of checking the type and
+            // nullness of the input key. However, several applications seem to have written comparators
+            // that only expect to be called on elements that aren't equal to each other (after
+            // making assumptions about the domain of the map). Clearly, such comparators are bogus
+            // because get() would never work, but TreeSets are frequently used for sorting a set
+            // of distinct elements.
+            //
+            // As a temporary work around, we perform the null & instanceof checks by hand so that
+            // we can guarantee that elements are never compared against themselves.
+            //
+            // compare(key, key);
+            //
+            // **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE ****
+            if (comparator != null) {
+                if (key == null) {
+                    comparator.compare(key, key);
+                }
+            } else {
+                if (key == null) {
+                    throw new NullPointerException("key == null");
+                } else if (!(key instanceof Comparable)) {
+                    throw new ClassCastException(
+                            "Cannot cast" + key.getClass().getName() + " to Comparable.");
+                }
+            }
 
             root = new TreeMapEntry<>(key, value, null);
             size = 1;
diff --git a/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java b/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java
index 2a5f5de..d3d681f 100644
--- a/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java
+++ b/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java
@@ -100,21 +100,22 @@
     final transient Object lock = new Object();
 
     /** The array, accessed only via getArray/setArray. */
-    private transient volatile Object[] array;
+    // Android-changed: renamed array -> elements for backwards compatibility b/33916927
+    private transient volatile Object[] elements;
 
     /**
      * Gets the array.  Non-private so as to also be accessible
      * from CopyOnWriteArraySet class.
      */
     final Object[] getArray() {
-        return array;
+        return elements;
     }
 
     /**
      * Sets the array.
      */
     final void setArray(Object[] a) {
-        array = a;
+        elements = a;
     }
 
     /**
diff --git a/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java b/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
index cda4cbb..e2e25f7 100644
--- a/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
+++ b/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -305,8 +305,10 @@
      * @param key key whose mapping is to be removed from the preference node.
      * @throws IllegalStateException if this node (or an ancestor) has been
      *         removed with the {@link #removeNode()} method.
+     * @throws NullPointerException {@inheritDoc}.
      */
     public void remove(String key) {
+        Objects.requireNonNull(key, "Specified key cannot be null");
         synchronized(lock) {
             if (removed)
                 throw new IllegalStateException("Node has been removed.");
@@ -1086,6 +1088,8 @@
      * removed.  (The implementor needn't check for any of these things.)
      *
      * <p>This method is invoked with the lock on this node held.
+     * @param key the key
+     * @param value the value
      */
     protected abstract void putSpi(String key, String value);
 
@@ -1104,6 +1108,7 @@
      *
      * <p>This method is invoked with the lock on this node held.
      *
+     * @param key the key
      * @return the value associated with the specified key at this preference
      *          node, or <tt>null</tt> if there is no association for this
      *          key, or the association cannot be determined at this time.
@@ -1117,6 +1122,7 @@
      * (The implementor needn't check for either of these things.)
      *
      * <p>This method is invoked with the lock on this node held.
+     * @param key the key
      */
     protected abstract void removeSpi(String key);
 
diff --git a/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java b/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java
index 2c0c51e..0fb96c5 100644
--- a/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java
+++ b/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -34,6 +34,7 @@
  * @see NodeChangeListener
  * @since   1.4
  */
+@FunctionalInterface
 public interface PreferenceChangeListener extends java.util.EventListener {
     /**
      * This method gets called when a preference is added, removed or when
diff --git a/ojluni/src/main/java/java/util/prefs/Preferences.java b/ojluni/src/main/java/java/util/prefs/Preferences.java
index 285ba7d..aaf9950 100644
--- a/ojluni/src/main/java/java/util/prefs/Preferences.java
+++ b/ojluni/src/main/java/java/util/prefs/Preferences.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -135,52 +135,52 @@
  * subsequently restore from the backup.
  *
  * <p>The XML document has the following DOCTYPE declaration:
- * <pre>
- * &lt;!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"&gt;
- * </pre>
+ * <pre>{@code
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ * }</pre>
  * Note that the system URI (http://java.sun.com/dtd/preferences.dtd) is
  * <i>not</i> accessed when exporting or importing preferences; it merely
  * serves as a string to uniquely identify the DTD, which is:
- * <pre>
- *    &lt;?xml version="1.0" encoding="UTF-8"?&gt;
+ * <pre>{@code
+ *    <?xml version="1.0" encoding="UTF-8"?>
  *
- *    &lt;!-- DTD for a Preferences tree. --&gt;
+ *    <!-- DTD for a Preferences tree. -->
  *
- *    &lt;!-- The preferences element is at the root of an XML document
- *         representing a Preferences tree. --&gt;
- *    &lt;!ELEMENT preferences (root)&gt;
+ *    <!-- The preferences element is at the root of an XML document
+ *         representing a Preferences tree. -->
+ *    <!ELEMENT preferences (root)>
  *
- *    &lt;!-- The preferences element contains an optional version attribute,
- *          which specifies version of DTD. --&gt;
- *    &lt;!ATTLIST preferences EXTERNAL_XML_VERSION CDATA "0.0" &gt
+ *    <!-- The preferences element contains an optional version attribute,
+ *          which specifies version of DTD. -->
+ *    <!ATTLIST preferences EXTERNAL_XML_VERSION CDATA "0.0" >
  *
- *    &lt;!-- The root element has a map representing the root's preferences
- *         (if any), and one node for each child of the root (if any). --&gt;
- *    &lt;!ELEMENT root (map, node*) &gt;
+ *    <!-- The root element has a map representing the root's preferences
+ *         (if any), and one node for each child of the root (if any). -->
+ *    <!ELEMENT root (map, node*) >
  *
- *    &lt;!-- Additionally, the root contains a type attribute, which
- *         specifies whether it's the system or user root. --&gt;
- *    &lt;!ATTLIST root
- *              type (system|user) #REQUIRED &gt;
+ *    <!-- Additionally, the root contains a type attribute, which
+ *         specifies whether it's the system or user root. -->
+ *    <!ATTLIST root
+ *              type (system|user) #REQUIRED >
  *
- *    &lt;!-- Each node has a map representing its preferences (if any),
- *         and one node for each child (if any). --&gt;
- *    &lt;!ELEMENT node (map, node*) &gt;
+ *    <!-- Each node has a map representing its preferences (if any),
+ *         and one node for each child (if any). -->
+ *    <!ELEMENT node (map, node*) >
  *
- *    &lt;!-- Additionally, each node has a name attribute --&gt;
- *    &lt;!ATTLIST node
- *              name CDATA #REQUIRED &gt;
+ *    <!-- Additionally, each node has a name attribute -->
+ *    <!ATTLIST node
+ *              name CDATA #REQUIRED >
  *
- *    &lt;!-- A map represents the preferences stored at a node (if any). --&gt;
- *    &lt;!ELEMENT map (entry*) &gt;
+ *    <!-- A map represents the preferences stored at a node (if any). -->
+ *    <!ELEMENT map (entry*) >
  *
- *    &lt;!-- An entry represents a single preference, which is simply
- *          a key-value pair. --&gt;
- *    &lt;!ELEMENT entry EMPTY &gt;
- *    &lt;!ATTLIST entry
+ *    <!-- An entry represents a single preference, which is simply
+ *          a key-value pair. -->
+ *    <!ELEMENT entry EMPTY >
+ *    <!ATTLIST entry
  *              key   CDATA #REQUIRED
- *              value CDATA #REQUIRED &gt;
- * </pre>
+ *              value CDATA #REQUIRED >
+ * }</pre>
  *
  * Every <tt>Preferences</tt> implementation must have an associated {@link
  * PreferencesFactory} implementation.  Every Java(TM) SE implementation must provide
@@ -372,7 +372,7 @@
      * @throws IllegalArgumentException if the package has node preferences
      *         node associated with it.
      */
-    private static String nodeName(Class c) {
+    private static String nodeName(Class<?> c) {
         if (c.isArray())
             throw new IllegalArgumentException(
                 "Arrays have no associated preferences node.");
@@ -1124,9 +1124,9 @@
      * This XML document is, in effect, an offline backup of the node.
      *
      * <p>The XML document will have the following DOCTYPE declaration:
-     * <pre>
-     * &lt;!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"&gt;
-     * </pre>
+     * <pre>{@code
+     * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+     * }</pre>
      * The UTF-8 character encoding will be used.
      *
      * <p>This method is an exception to the general rule that the results of
@@ -1155,9 +1155,9 @@
      * effect, an offline backup of the subtree rooted at the node.
      *
      * <p>The XML document will have the following DOCTYPE declaration:
-     * <pre>
-     * &lt;!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"&gt;
-     * </pre>
+     * <pre>{@code
+     * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+     * }</pre>
      * The UTF-8 character encoding will be used.
      *
      * <p>This method is an exception to the general rule that the results of
@@ -1191,9 +1191,9 @@
      * do not exist, the nodes will be created.
      *
      * <p>The XML document must have the following DOCTYPE declaration:
-     * <pre>
-     * &lt;!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"&gt;
-     * </pre>
+     * <pre>{@code
+     * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+     * }</pre>
      * (This method is designed for use in conjunction with
      * {@link #exportNode(OutputStream)} and
      * {@link #exportSubtree(OutputStream)}.
diff --git a/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java b/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java
index 8bf37bc..d7341e6 100644
--- a/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java
+++ b/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -45,6 +45,7 @@
     /**
      * Returns the system root preference node.  (Multiple calls on this
      * method will return the same object reference.)
+     * @return the system root preference node
      */
     Preferences systemRoot();
 
@@ -52,6 +53,8 @@
      * Returns the user root preference node corresponding to the calling
      * user.  In a server, the returned value will typically depend on
      * some implicit client-context.
+     * @return the user root preference node corresponding to the calling
+     * user
      */
     Preferences userRoot();
 }
diff --git a/ojluni/src/main/java/java/util/prefs/XmlSupport.java b/ojluni/src/main/java/java/util/prefs/XmlSupport.java
index 8356e1d..b1703d5 100644
--- a/ojluni/src/main/java/java/util/prefs/XmlSupport.java
+++ b/ojluni/src/main/java/java/util/prefs/XmlSupport.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -93,7 +93,7 @@
      * @throws BackingStoreException if preference data cannot be read from
      *         backing store.
      * @throws IllegalStateException if this node (or an ancestor) has been
-     *         removed with the {@link #removeNode()} method.
+     *         removed with the {@link Preferences#removeNode()} method.
      */
     static void export(OutputStream os, final Preferences p, boolean subTree)
         throws IOException, BackingStoreException {
@@ -107,7 +107,7 @@
         xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));
 
         // Get bottom-up list of nodes from p to root, excluding root
-        List ancestors = new ArrayList();
+        List<Preferences> ancestors = new ArrayList<>();
 
         for (Preferences kid = p, dad = kid.parent(); dad != null;
                                    kid = dad, dad = kid.parent()) {
@@ -117,7 +117,7 @@
         for (int i=ancestors.size()-1; i >= 0; i--) {
             e.appendChild(doc.createElement("map"));
             e = (Element) e.appendChild(doc.createElement("node"));
-            e.setAttribute("name", ((Preferences)ancestors.get(i)).name());
+            e.setAttribute("name", ancestors.get(i).name());
         }
         putPreferencesInXml(e, doc, p, subTree);
 
@@ -365,17 +365,17 @@
      * @throws IOException if writing to the specified output stream
      *         results in an <tt>IOException</tt>.
      */
-    static void exportMap(OutputStream os, Map map) throws IOException {
+    static void exportMap(OutputStream os, Map<String, String> map) throws IOException {
         Document doc = createPrefsDoc("map");
         Element xmlMap = doc.getDocumentElement( ) ;
         xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
 
-        for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
-            Map.Entry e = (Map.Entry) i.next();
+        for (Iterator<Map.Entry<String, String>> i = map.entrySet().iterator(); i.hasNext(); ) {
+            Map.Entry<String, String> e = i.next();
             Element xe = (Element)
                 xmlMap.appendChild(doc.createElement("entry"));
-            xe.setAttribute("key",   (String) e.getKey());
-            xe.setAttribute("value", (String) e.getValue());
+            xe.setAttribute("key",   e.getKey());
+            xe.setAttribute("value", e.getValue());
         }
 
         writeDoc(doc, os);
@@ -394,7 +394,7 @@
      * @throws InvalidPreferencesFormatException Data on input stream does not
      *         constitute a valid XML document with the mandated document type.
      */
-    static void importMap(InputStream is, Map m)
+    static void importMap(InputStream is, Map<String, String> m)
         throws IOException, InvalidPreferencesFormatException
     {
         try {
diff --git a/ojluni/src/main/native/PlainDatagramSocketImpl.c b/ojluni/src/main/native/PlainDatagramSocketImpl.c
index 9acf306..b36e286 100644
--- a/ojluni/src/main/native/PlainDatagramSocketImpl.c
+++ b/ojluni/src/main/native/PlainDatagramSocketImpl.c
@@ -171,6 +171,874 @@
 }
 
 /*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    bind
+ * Signature: (ILjava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_bind0(JNIEnv *env, jobject this,
+                                           jint localport, jobject iaObj) {
+    /* fdObj is the FileDescriptor field on this */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    /* fd is an int field on fdObj */
+    int fd;
+    int len = 0;
+    SOCKADDR him;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+
+    if (IS_NULL(iaObj)) {
+        JNU_ThrowNullPointerException(env, "iaObj is null.");
+        return;
+    }
+
+    /* bind */
+    if (NET_InetAddressToSockaddr(env, iaObj, localport, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) {
+      return;
+    }
+    setDefaultScopeID(env, (struct sockaddr *)&him);
+
+    if (NET_Bind(fd, (struct sockaddr *)&him, len) < 0)  {
+        if (errno == EADDRINUSE || errno == EADDRNOTAVAIL ||
+            errno == EPERM || errno == EACCES) {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException",
+                            "Bind failed");
+        } else {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                            "Bind failed");
+        }
+        return;
+    }
+
+    /* initialize the local port */
+    if (localport == 0) {
+        /* Now that we're a connected socket, let's extract the port number
+         * that the system chose for us and store it in the Socket object.
+         */
+        if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
+            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                            "Error getting socket name");
+            return;
+        }
+
+        localport = NET_GetPortFromSockaddr((struct sockaddr *)&him);
+
+        (*env)->SetIntField(env, this, pdsi_localPortID, localport);
+    } else {
+        (*env)->SetIntField(env, this, pdsi_localPortID, localport);
+    }
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    connect0
+ * Signature: (Ljava/net/InetAddress;I)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_connect0(JNIEnv *env, jobject this,
+                                               jobject address, jint port) {
+    /* The object's field */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    /* The fdObj'fd */
+    jint fd;
+    /* The packetAddress address, family and port */
+    SOCKADDR rmtaddr;
+    int len = 0;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(address)) {
+        JNU_ThrowNullPointerException(env, "address");
+        return;
+    }
+
+    if (NET_InetAddressToSockaddr(env, address, port, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) {
+      return;
+    }
+
+    setDefaultScopeID(env, (struct sockaddr *)&rmtaddr);
+    if (JVM_Connect(fd, (struct sockaddr *)&rmtaddr, len) == -1) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
+                        "Connect failed");
+        return;
+    }
+
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    disconnect0
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_disconnect0(JNIEnv *env, jobject this, jint family) {
+    /* The object's field */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    /* The fdObj'fd */
+    jint fd;
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+    SOCKADDR addr;
+    int len;
+#endif
+
+    if (IS_NULL(fdObj)) {
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+        memset(&addr, 0, sizeof(addr));
+#ifdef AF_INET6
+        if (ipv6_available()) {
+            struct sockaddr_in6 *him6 = (struct sockaddr_in6 *)&addr;
+            him6->sin6_family = AF_UNSPEC;
+            len = sizeof(struct sockaddr_in6);
+        } else
+#endif
+        {
+            struct sockaddr_in *him4 = (struct sockaddr_in*)&addr;
+            him4->sin_family = AF_UNSPEC;
+            len = sizeof(struct sockaddr_in);
+        }
+        JVM_Connect(fd, (struct sockaddr *)&addr, len);
+
+#ifdef __linux__
+        int localPort = 0;
+        if (JVM_GetSockName(fd, (struct sockaddr *)&addr, &len) == -1)
+            return;
+        localPort = NET_GetPortFromSockaddr((struct sockaddr *)&addr);
+        if (localPort == 0) {
+            localPort = (*env)->GetIntField(env, this, pdsi_localPortID);
+#ifdef AF_INET6
+            if (((struct sockaddr*)&addr)->sa_family == AF_INET6) {
+                ((struct sockaddr_in6*)&addr)->sin6_port = htons(localPort);
+            } else
+#endif /* AF_INET6 */
+            {
+                ((struct sockaddr_in*)&addr)->sin_port = htons(localPort);
+            }
+            NET_Bind(fd, (struct sockaddr *)&addr, len);
+        }
+#endif
+#else
+    JVM_Connect(fd, 0, 0);
+#endif
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    send
+ * Signature: (Ljava/net/DatagramPacket;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_send(JNIEnv *env, jobject this,
+                                           jobject packet) {
+
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket = NULL;
+    int ret, mallocedPacket = JNI_FALSE;
+    /* The object's field */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint trafficClass = (*env)->GetIntField(env, this, pdsi_trafficClassID);
+
+    jbyteArray packetBuffer;
+    jobject packetAddress;
+    jint packetBufferOffset, packetBufferLen, packetPort;
+    jboolean connected;
+
+    /* The fdObj'fd */
+    jint fd;
+
+    SOCKADDR rmtaddr, *rmtaddrP=&rmtaddr;
+    int len;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(packet)) {
+        JNU_ThrowNullPointerException(env, "packet");
+        return;
+    }
+
+    connected = (*env)->GetBooleanField(env, this, pdsi_connected);
+
+    packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+    packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+    if (IS_NULL(packetBuffer) || IS_NULL(packetAddress)) {
+        JNU_ThrowNullPointerException(env, "null buffer || null address");
+        return;
+    }
+
+    packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, packet, dp_lengthID);
+
+    if (connected) {
+        /* arg to NET_Sendto () null in this case */
+        len = 0;
+        rmtaddrP = 0;
+    } else {
+        packetPort = (*env)->GetIntField(env, packet, dp_portID);
+        if (NET_InetAddressToSockaddr(env, packetAddress, packetPort, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) {
+          return;
+        }
+    }
+    setDefaultScopeID(env, (struct sockaddr *)&rmtaddr);
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+        /* When JNI-ifying the JDK's IO routines, we turned
+         * reads and writes of byte arrays of size greater
+         * than 2048 bytes into several operations of size 2048.
+         * This saves a malloc()/memcpy()/free() for big
+         * buffers.  This is OK for file IO and TCP, but that
+         * strategy violates the semantics of a datagram protocol.
+         * (one big send) != (several smaller sends).  So here
+         * we *must* allocate the buffer.  Note it needn't be bigger
+         * than 65,536 (0xFFFF), the max size of an IP packet.
+         * Anything bigger should be truncated anyway.
+         *
+         * We may want to use a smarter allocation scheme at some
+         * point.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "Send buffer native heap allocation failed");
+            return;
+        } else {
+            mallocedPacket = JNI_TRUE;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+    (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, packetBufferLen,
+                               (jbyte *)fullPacket);
+#ifdef AF_INET6
+    if (trafficClass != 0 && ipv6_available()) {
+        NET_SetTrafficClass((struct sockaddr *)&rmtaddr, trafficClass);
+    }
+#endif /* AF_INET6 */
+
+
+    /*
+     * Send the datagram.
+     *
+     * If we are connected it's possible that sendto will return
+     * ECONNREFUSED indicating that an ICMP port unreachable has
+     * received.
+     */
+    ret = NET_SendTo(fd, fullPacket, packetBufferLen, 0,
+                     (struct sockaddr *)rmtaddrP, len);
+
+    if (ret < 0) {
+        switch (ret) {
+            case JVM_IO_ERR :
+                if (errno == ECONNREFUSED) {
+                    JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                            "ICMP Port Unreachable");
+                } else {
+                    NET_ThrowByNameWithLastError(env, "java/io/IOException", "sendto failed");
+                }
+                break;
+
+            case JVM_IO_INTR:
+                JNU_ThrowByName(env, "java/io/InterruptedIOException",
+                                "operation interrupted");
+                break;
+        }
+    }
+
+    if (mallocedPacket) {
+        free(fullPacket);
+    }
+    return;
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    peek
+ * Signature: (Ljava/net/InetAddress;)I
+ */
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_peek(JNIEnv *env, jobject this,
+                                           jobject addressObj) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+    jint fd;
+    ssize_t n;
+    SOCKADDR remote_addr;
+    int len;
+    char buf[1];
+    jint family;
+    jobject iaObj;
+    int port;
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+        return -1;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    if (IS_NULL(addressObj)) {
+        JNU_ThrowNullPointerException(env, "Null address in peek()");
+        return -1;
+    }
+    if (timeout) {
+        int ret = NET_Timeout(fd, timeout);
+        if (ret == 0) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+                            "Peek timed out");
+            return ret;
+        } else if (ret == JVM_IO_ERR) {
+            if (errno == EBADF) {
+                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed");
+            }
+            return ret;
+        } else if (ret == JVM_IO_INTR) {
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+            return ret; /* WARNING: SHOULD WE REALLY RETURN -2??? */
+        }
+    }
+
+    len = SOCKADDR_LEN;
+    n = NET_RecvFrom(fd, buf, 1, MSG_PEEK,
+                     (struct sockaddr *)&remote_addr, &len);
+
+    if (n == JVM_IO_ERR) {
+
+#ifdef __solaris__
+        if (errno == ECONNREFUSED) {
+            int orig_errno = errno;
+            (void) recv(fd, buf, 1, 0);
+            errno = orig_errno;
+        }
+#endif
+        if (errno == ECONNREFUSED) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                            "ICMP Port Unreachable");
+        } else {
+            if (errno == EBADF) {
+                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed");
+            }
+        }
+        return 0;
+    } else if (n == JVM_IO_INTR) {
+        JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
+        return 0;
+    }
+
+    iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+#ifdef AF_INET6
+    family = getInetAddress_family(env, iaObj) == IPv4? AF_INET : AF_INET6;
+#else
+    family = AF_INET;
+#endif
+    if (family == AF_INET) { /* this API can't handle IPV6 addresses */
+        int address = getInetAddress_addr(env, iaObj);
+        setInetAddress_addr(env, addressObj, address);
+    }
+    return port;
+}
+
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_peekData(JNIEnv *env, jobject this,
+                                           jobject packet) {
+
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket = NULL;
+    int mallocedPacket = JNI_FALSE;
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+
+    jbyteArray packetBuffer;
+    jint packetBufferOffset, packetBufferLen;
+
+    int fd;
+
+    int n;
+    SOCKADDR remote_addr;
+    int len;
+    int port;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return -1;
+    }
+
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(packet)) {
+        JNU_ThrowNullPointerException(env, "packet");
+        return -1;
+    }
+
+    packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+    if (IS_NULL(packetBuffer)) {
+        JNU_ThrowNullPointerException(env, "packet buffer");
+        return -1;
+    }
+    packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID);
+    if (timeout) {
+        int ret = NET_Timeout(fd, timeout);
+        if (ret == 0) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+                            "Receive timed out");
+            return -1;
+        } else if (ret == JVM_IO_ERR) {
+#ifdef __linux__
+            if (errno == EBADF) {
+                JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+            }
+#else
+            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+#endif
+            return -1;
+        } else if (ret == JVM_IO_INTR) {
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+            return -1;
+        }
+    }
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+
+        /* When JNI-ifying the JDK's IO routines, we turned
+         * reads and writes of byte arrays of size greater
+         * than 2048 bytes into several operations of size 2048.
+         * This saves a malloc()/memcpy()/free() for big
+         * buffers.  This is OK for file IO and TCP, but that
+         * strategy violates the semantics of a datagram protocol.
+         * (one big send) != (several smaller sends).  So here
+         * we *must* allocate the buffer.  Note it needn't be bigger
+         * than 65,536 (0xFFFF), the max size of an IP packet.
+         * anything bigger is truncated anyway.
+         *
+         * We may want to use a smarter allocation scheme at some
+         * point.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "Peek buffer native heap allocation failed");
+            return -1;
+        } else {
+            mallocedPacket = JNI_TRUE;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+    len = SOCKADDR_LEN;
+    n = NET_RecvFrom(fd, fullPacket, packetBufferLen, MSG_PEEK,
+                     (struct sockaddr *)&remote_addr, &len);
+    /* truncate the data if the packet's length is too small */
+    if (n > packetBufferLen) {
+        n = packetBufferLen;
+    }
+    if (n == JVM_IO_ERR) {
+
+#ifdef __solaris__
+        if (errno == ECONNREFUSED) {
+            int orig_errno = errno;
+            (void) recv(fd, fullPacket, 1, 0);
+            errno = orig_errno;
+        }
+#endif
+        (*env)->SetIntField(env, packet, dp_offsetID, 0);
+        (*env)->SetIntField(env, packet, dp_lengthID, 0);
+        if (errno == ECONNREFUSED) {
+            JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                            "ICMP Port Unreachable");
+        } else {
+            if (errno == EBADF) {
+                 JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+            } else {
+                 NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+            }
+        }
+    } else if (n == JVM_IO_INTR) {
+        (*env)->SetIntField(env, packet, dp_offsetID, 0);
+        (*env)->SetIntField(env, packet, dp_lengthID, 0);
+        JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                        "operation interrupted");
+    } else {
+        /*
+         * success - fill in received address...
+         *
+         * REMIND: Fill in an int on the packet, and create inetadd
+         * object in Java, as a performance improvement. Also
+         * construct the inetadd object lazily.
+         */
+
+        jobject packetAddress;
+
+        /*
+         * Check if there is an InetAddress already associated with this
+         * packet. If so we check if it is the same source address. We
+         * can't update any existing InetAddress because it is immutable
+         */
+        packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+        if (packetAddress != NULL) {
+            if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, packetAddress)) {
+                /* force a new InetAddress to be created */
+                packetAddress = NULL;
+            }
+        }
+        if (packetAddress == NULL) {
+            packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+            /* stuff the new Inetaddress in the packet */
+            (*env)->SetObjectField(env, packet, dp_addressID, packetAddress);
+        } else {
+            /* only get the new port number */
+            port = NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr);
+        }
+        /* and fill in the data, remote address/port and such */
+        (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
+                                   (jbyte *)fullPacket);
+        (*env)->SetIntField(env, packet, dp_portID, port);
+        (*env)->SetIntField(env, packet, dp_lengthID, n);
+    }
+
+    if (mallocedPacket) {
+        free(fullPacket);
+    }
+    return port;
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    receive
+ * Signature: (Ljava/net/DatagramPacket;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_receive0(JNIEnv *env, jobject this,
+                                              jobject packet) {
+
+    char BUF[MAX_BUFFER_LEN];
+    char *fullPacket = NULL;
+    int mallocedPacket = JNI_FALSE;
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+
+    jbyteArray packetBuffer;
+    jint packetBufferOffset, packetBufferLen;
+
+    int fd;
+
+    int n;
+    SOCKADDR remote_addr;
+    int len;
+    jboolean retry;
+#ifdef __linux__
+    jboolean connected = JNI_FALSE;
+    jobject connectedAddress = NULL;
+    jint connectedPort = 0;
+    jlong prevTime = 0;
+#endif
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+    if (IS_NULL(packet)) {
+        JNU_ThrowNullPointerException(env, "packet");
+        return;
+    }
+
+    packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+    if (IS_NULL(packetBuffer)) {
+        JNU_ThrowNullPointerException(env, "packet buffer");
+        return;
+    }
+    packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+    packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID);
+
+    if (packetBufferLen > MAX_BUFFER_LEN) {
+
+        /* When JNI-ifying the JDK's IO routines, we turned
+         * reads and writes of byte arrays of size greater
+         * than 2048 bytes into several operations of size 2048.
+         * This saves a malloc()/memcpy()/free() for big
+         * buffers.  This is OK for file IO and TCP, but that
+         * strategy violates the semantics of a datagram protocol.
+         * (one big send) != (several smaller sends).  So here
+         * we *must* allocate the buffer.  Note it needn't be bigger
+         * than 65,536 (0xFFFF), the max size of an IP packet.
+         * anything bigger is truncated anyway.
+         *
+         * We may want to use a smarter allocation scheme at some
+         * point.
+         */
+        if (packetBufferLen > MAX_PACKET_LEN) {
+            packetBufferLen = MAX_PACKET_LEN;
+        }
+        fullPacket = (char *)malloc(packetBufferLen);
+
+        if (!fullPacket) {
+            JNU_ThrowOutOfMemoryError(env, "Receive buffer native heap allocation failed");
+            return;
+        } else {
+            mallocedPacket = JNI_TRUE;
+        }
+    } else {
+        fullPacket = &(BUF[0]);
+    }
+
+
+    do {
+        retry = JNI_FALSE;
+
+        if (timeout) {
+            int ret = NET_Timeout(fd, timeout);
+            if (ret <= 0) {
+                if (ret == 0) {
+                    JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+                                    "Receive timed out");
+                } else if (ret == JVM_IO_ERR) {
+#ifdef __linux__
+                    if (errno == EBADF) {
+                         JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+                     } else {
+                         NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+                     }
+#else
+                     JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+#endif
+                } else if (ret == JVM_IO_INTR) {
+                    JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                                    "operation interrupted");
+                }
+
+                if (mallocedPacket) {
+                    free(fullPacket);
+                }
+
+                return;
+            }
+        }
+
+        len = SOCKADDR_LEN;
+        n = NET_RecvFrom(fd, fullPacket, packetBufferLen, 0,
+                         (struct sockaddr *)&remote_addr, &len);
+        /* truncate the data if the packet's length is too small */
+        if (n > packetBufferLen) {
+            n = packetBufferLen;
+        }
+        if (n == JVM_IO_ERR) {
+            (*env)->SetIntField(env, packet, dp_offsetID, 0);
+            (*env)->SetIntField(env, packet, dp_lengthID, 0);
+            if (errno == ECONNREFUSED) {
+                JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+                                "ICMP Port Unreachable");
+            } else {
+                if (errno == EBADF) {
+                     JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+                 } else {
+                     NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+                 }
+            }
+        } else if (n == JVM_IO_INTR) {
+            (*env)->SetIntField(env, packet, dp_offsetID, 0);
+            (*env)->SetIntField(env, packet, dp_lengthID, 0);
+            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+                            "operation interrupted");
+        } else {
+            int port;
+            jobject packetAddress;
+
+            /*
+             * success - fill in received address...
+             *
+             * REMIND: Fill in an int on the packet, and create inetadd
+             * object in Java, as a performance improvement. Also
+             * construct the inetadd object lazily.
+             */
+
+            /*
+             * Check if there is an InetAddress already associated with this
+             * packet. If so we check if it is the same source address. We
+             * can't update any existing InetAddress because it is immutable
+             */
+            packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+            if (packetAddress != NULL) {
+                if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, packetAddress)) {
+                    /* force a new InetAddress to be created */
+                    packetAddress = NULL;
+                }
+            }
+            if (packetAddress == NULL) {
+                packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+                /* stuff the new Inetaddress in the packet */
+                (*env)->SetObjectField(env, packet, dp_addressID, packetAddress);
+            } else {
+                /* only get the new port number */
+                port = NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr);
+            }
+            /* and fill in the data, remote address/port and such */
+            (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
+                                       (jbyte *)fullPacket);
+            (*env)->SetIntField(env, packet, dp_portID, port);
+            (*env)->SetIntField(env, packet, dp_lengthID, n);
+        }
+
+    } while (retry);
+
+    if (mallocedPacket) {
+        free(fullPacket);
+    }
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    datagramSocketCreate
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env,
+                                                           jobject this) {
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    int arg, fd, t = 1;
+#ifdef AF_INET6
+    int domain = ipv6_available() ? AF_INET6 : AF_INET;
+#else
+    int domain = AF_INET;
+#endif
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    }
+
+    if ((fd = JVM_Socket(domain, SOCK_DGRAM, 0)) == JVM_IO_ERR) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error creating socket");
+        return;
+    }
+    tagSocket(env, fd);
+
+#ifdef AF_INET6
+    /* Disable IPV6_V6ONLY to ensure dual-socket support */
+    if (domain == AF_INET6) {
+        arg = 0;
+        if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg,
+                       sizeof(int)) < 0) {
+            NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6");
+            untagSocket(env, fd);
+            close(fd);
+            return;
+        }
+    }
+#endif /* AF_INET6 */
+
+#ifdef __APPLE__
+    arg = 65507;
+    if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_SNDBUF,
+                       (char *)&arg, sizeof(arg)) < 0) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        strerror(errno));
+        return;
+    }
+    if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_RCVBUF,
+                       (char *)&arg, sizeof(arg)) < 0) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        strerror(errno));
+        return;
+    }
+#endif /* __APPLE__ */
+
+     setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*) &t, sizeof(int));
+
+#if defined(__linux__)
+     arg = 0;
+     int level = (domain == AF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP;
+     if ((setsockopt(fd, level, IP_MULTICAST_ALL, (char*)&arg, sizeof(arg)) < 0) &&
+         (errno != ENOPROTOOPT)) {
+         JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                         strerror(errno));
+         close(fd);
+         return;
+     }
+#endif
+
+#if defined (__linux__) && defined (AF_INET6)
+    /*
+     * On Linux for IPv6 sockets we must set the hop limit
+     * to 1 to be compatible with default TTL of 1 for IPv4 sockets.
+     */
+    if (domain == AF_INET6) {
+        int ttl = 1;
+        setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl,
+                   sizeof(ttl));
+    }
+#endif /* __linux__ */
+
+    (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    datagramSocketClose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_datagramSocketClose(JNIEnv *env,
+                                                          jobject this) {
+    /*
+     * REMIND: PUT A LOCK AROUND THIS CODE
+     */
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    int fd;
+
+    if (IS_NULL(fdObj)) {
+        return;
+    }
+    fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    if (fd == -1) {
+        return;
+    }
+    (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
+    untagSocket(env, fd);
+    NET_SocketClose(fd);
+}
+
+
+/*
  * Set outgoing multicast interface designated by a NetworkInterface index.
  * Throw exception if failed.
  *
@@ -677,9 +1545,418 @@
     return NULL;
 }
 
+/*
+ * Multicast-related calls
+ */
+
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_setTTL(JNIEnv *env, jobject this,
+                                             jbyte ttl) {
+    jint ittl = ttl;
+    if (ittl < 0) {
+        ittl += 0x100;
+    }
+    PlainDatagramSocketImpl_setTimeToLive(env, this, ittl);
+}
+
+/*
+ * Set TTL for a socket. Throw exception if failed.
+ */
+static void setTTL(JNIEnv *env, int fd, jint ttl) {
+    char ittl = (char)ttl;
+    if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ittl,
+                       sizeof(ittl)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error setting socket option");
+    }
+}
+
+/*
+ * Set hops limit for a socket. Throw exception if failed.
+ */
+#ifdef AF_INET6
+static void setHopLimit(JNIEnv *env, int fd, jint ttl) {
+    int ittl = (int)ttl;
+    if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+                       (char*)&ittl, sizeof(ittl)) < 0) {
+        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                       "Error setting socket option");
+    }
+}
+#endif
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    setTTL
+ * Signature: (B)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_setTimeToLive(JNIEnv *env, jobject this,
+                                                    jint ttl) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    int fd;
+    /* it is important to cast this to a char, otherwise setsockopt gets confused */
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    /* setsockopt to be correct TTL */
+#ifdef AF_INET6
+#ifdef __linux__
+    setTTL(env, fd, ttl);
+    if ((*env)->ExceptionCheck(env)) {
+        return;
+    }
+    if (ipv6_available()) {
+        setHopLimit(env, fd, ttl);
+    }
+#else  /*  __linux__ not defined */
+    if (ipv6_available()) {
+        setHopLimit(env, fd, ttl);
+    } else {
+        setTTL(env, fd, ttl);
+    }
+#endif  /* __linux__ */
+#else
+    setTTL(env, fd, ttl);
+#endif  /* AF_INET6 */
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    getTTL
+ * Signature: ()B
+ */
+JNIEXPORT jbyte JNICALL
+PlainDatagramSocketImpl_getTTL(JNIEnv *env, jobject this) {
+    return (jbyte)PlainDatagramSocketImpl_getTimeToLive(env, this);
+}
+
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    getTTL
+ * Signature: ()B
+ */
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_getTimeToLive(JNIEnv *env, jobject this) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint fd = -1;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return -1;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    /* getsockopt of TTL */
+#ifdef AF_INET6
+    if (ipv6_available()) {
+        int ttl = 0;
+        int len = sizeof(ttl);
+
+        if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+                               (char*)&ttl, &len) < 0) {
+                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                               "Error getting socket option");
+                return -1;
+            }
+        return (jint)ttl;
+    } else
+#endif /* AF_INET6 */
+        {
+            u_char ttl = 0;
+            int len = sizeof(ttl);
+            if (JVM_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+                               (char*)&ttl, &len) < 0) {
+                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+                               "Error getting socket option");
+                return -1;
+            }
+            return (jint)ttl;
+        }
+}
+
+
+/*
+ * mcast_join_leave: Join or leave a multicast group.
+ *
+ * For IPv4 sockets use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option
+ * to join/leave multicast group.
+ *
+ * For IPv6 sockets use IPV6_ADD_MEMBERSHIP/IPV6_DROP_MEMBERSHIP socket option
+ * to join/leave multicast group. If multicast group is an IPv4 address then
+ * an IPv4-mapped address is used.
+ *
+ * On Linux with IPv6 if we wish to join/leave an IPv4 multicast group then
+ * we must use the IPv4 socket options. This is because the IPv6 socket options
+ * don't support IPv4-mapped addresses. This is true as per 2.2.19 and 2.4.7
+ * kernel releases. In the future it's possible that IP_ADD_MEMBERSHIP
+ * will be updated to return ENOPROTOOPT if uses with an IPv6 socket (Solaris
+ * already does this). Thus to cater for this we first try with the IPv4
+ * socket options and if they fail we use the IPv6 socket options. This
+ * seems a reasonable failsafe solution.
+ */
+static void mcast_join_leave(JNIEnv *env, jobject this,
+                             jobject iaObj, jobject niObj,
+                             jboolean join) {
+
+    jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+    jint fd;
+    jint ipv6_join_leave;
+
+    if (IS_NULL(fdObj)) {
+        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Socket closed");
+        return;
+    } else {
+        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+    }
+    if (IS_NULL(iaObj)) {
+        JNU_ThrowNullPointerException(env, "iaObj");
+        return;
+    }
+
+    /*
+     * Get java/net/NetworkInterface#index field.
+     */
+    static jfieldID ni_indexID;
+
+    if (ni_indexID == NULL) {
+      jclass c = (*env)->FindClass(env, "java/net/NetworkInterface");
+      CHECK_NULL(c);
+      ni_indexID = (*env)->GetFieldID(env, c, "index", "I");
+      CHECK_NULL(ni_indexID);
+    }
+
+    /*
+     * Determine if this is an IPv4 or IPv6 join/leave.
+     */
+    ipv6_join_leave = ipv6_available();
+
+    if (getInetAddress_family(env, iaObj) == IPv4) {
+        ipv6_join_leave = JNI_FALSE;
+    }
+
+    /*
+     * For IPv4 join use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option
+     *
+     * On Linux if IPv4 or IPv6 use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP
+     */
+    if (!ipv6_join_leave) {
+        struct ip_mreqn mname;
+        int mname_len;
+
+        /*
+         * joinGroup(InetAddress, NetworkInterface) implementation :-
+         *
+         * Linux/IPv6:  use ip_mreqn structure populated with multicast
+         *              address and interface index.
+         *
+         */
+        if (niObj != NULL) {
+            mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj));
+            mname.imr_address.s_addr = 0;
+            mname.imr_ifindex =  (*env)->GetIntField(env, niObj, ni_indexID);
+            mname_len = sizeof(struct ip_mreqn);
+        }
+
+
+        /*
+         * joinGroup(InetAddress) implementation :-
+         *
+         * Linux/IPv6:  use ip_mreqn structure populated with multicast
+         *              address and interface index. index obtained
+         *              from cached value or IPV6_MULTICAST_IF.
+         *
+         * IPv4:        use ip_mreq structure populated with multicast
+         *              address and local address obtained from
+         *              IP_MULTICAST_IF. On Linux IP_MULTICAST_IF
+         *              returns different structure depending on
+         *              kernel.
+         */
+
+        if (niObj == NULL) {
+            int index;
+            int len = sizeof(index);
+
+            if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                               (char*)&index, &len) < 0) {
+                NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed");
+                return;
+            }
+
+            mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj));
+            mname.imr_address.s_addr = 0 ;
+            mname.imr_ifindex = index;
+            mname_len = sizeof(struct ip_mreqn);
+        }
+
+
+        /*
+         * Join the multicast group.
+         */
+        if (JVM_SetSockOpt(fd, IPPROTO_IP, (join ? IP_ADD_MEMBERSHIP:IP_DROP_MEMBERSHIP),
+                           (char *) &mname, mname_len) < 0) {
+
+            /*
+             * If IP_ADD_MEMBERSHIP returns ENOPROTOOPT on Linux and we've got
+             * IPv6 enabled then it's possible that the kernel has been fixed
+             * so we switch to IPV6_ADD_MEMBERSHIP socket option.
+             * As of 2.4.7 kernel IPV6_ADD_MEMBERSHIP can't handle IPv4-mapped
+             * addresses so we have to use IP_ADD_MEMBERSHIP for IPv4 multicast
+             * groups. However if the socket is an IPv6 socket then then setsockopt
+             * should return ENOPROTOOPT. We assume this will be fixed in Linux
+             * at some stage.
+             */
+            if (errno == ENOPROTOOPT) {
+                if (ipv6_available()) {
+                    ipv6_join_leave = JNI_TRUE;
+                    errno = 0;
+                } else  {
+                    errno = ENOPROTOOPT;    /* errno can be changed by ipv6_available */
+                }
+            }
+            if (errno) {
+                if (join) {
+                    NET_ThrowCurrent(env, "setsockopt IP_ADD_MEMBERSHIP failed");
+                } else {
+                    if (errno == ENOENT)
+                        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                            "Not a member of the multicast group");
+                    else
+                        NET_ThrowCurrent(env, "setsockopt IP_DROP_MEMBERSHIP failed");
+                }
+                return;
+            }
+        }
+
+        /*
+         * If we haven't switched to IPv6 socket option then we're done.
+         */
+        if (!ipv6_join_leave) {
+            return;
+        }
+    }
+
+
+    /*
+     * IPv6 join. If it's an IPv4 multicast group then we use an IPv4-mapped
+     * address.
+     */
+    {
+        struct ipv6_mreq mname6;
+
+        jbyteArray ipaddress;
+        jbyte caddr[16];
+        jint family;
+        jint address;
+        family = getInetAddress_family(env, iaObj) == IPv4? AF_INET : AF_INET6;
+        if (family == AF_INET) { /* will convert to IPv4-mapped address */
+            memset((char *) caddr, 0, 16);
+            address = getInetAddress_addr(env, iaObj);
+
+            caddr[10] = 0xff;
+            caddr[11] = 0xff;
+
+            caddr[12] = ((address >> 24) & 0xff);
+            caddr[13] = ((address >> 16) & 0xff);
+            caddr[14] = ((address >> 8) & 0xff);
+            caddr[15] = (address & 0xff);
+        } else {
+            // Android-changed: explicit cast to suppress compiler warning.
+            getInet6Address_ipaddress(env, iaObj, (char*)caddr);
+        }
+
+        memcpy((void *)&(mname6.ipv6mr_multiaddr), caddr, sizeof(struct in6_addr));
+        if (IS_NULL(niObj)) {
+            int index;
+            int len = sizeof(index);
+
+            if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+                             (char*)&index, &len) < 0) {
+                NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed");
+                return;
+            }
+
+            mname6.ipv6mr_interface = index;
+        } else {
+            jint idx = (*env)->GetIntField(env, niObj, ni_indexID);
+            mname6.ipv6mr_interface = idx;
+        }
+
+#define ADD_MEMBERSHIP          IPV6_ADD_MEMBERSHIP
+#define DRP_MEMBERSHIP          IPV6_DROP_MEMBERSHIP
+#define S_ADD_MEMBERSHIP        "IPV6_ADD_MEMBERSHIP"
+#define S_DRP_MEMBERSHIP        "IPV6_DROP_MEMBERSHIP"
+
+        /* Join the multicast group */
+        if (JVM_SetSockOpt(fd, IPPROTO_IPV6, (join ? ADD_MEMBERSHIP : DRP_MEMBERSHIP),
+                           (char *) &mname6, sizeof (mname6)) < 0) {
+
+            if (join) {
+                NET_ThrowCurrent(env, "setsockopt " S_ADD_MEMBERSHIP " failed");
+            } else {
+                if (errno == ENOENT) {
+                   JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+                        "Not a member of the multicast group");
+                } else {
+                    NET_ThrowCurrent(env, "setsockopt " S_DRP_MEMBERSHIP " failed");
+                }
+            }
+        }
+    }
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    join
+ * Signature: (Ljava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_join(JNIEnv *env, jobject this,
+                                           jobject iaObj, jobject niObj)
+{
+    mcast_join_leave(env, this, iaObj, niObj, JNI_TRUE);
+}
+
+/*
+ * Class:     java_net_PlainDatagramSocketImpl
+ * Method:    leave
+ * Signature: (Ljava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_leave(JNIEnv *env, jobject this,
+                                            jobject iaObj, jobject niObj)
+{
+    mcast_join_leave(env, this, iaObj, niObj, JNI_FALSE);
+}
+
 static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(PlainDatagramSocketImpl, leave, "(Ljava/net/InetAddress;Ljava/net/NetworkInterface;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, join, "(Ljava/net/InetAddress;Ljava/net/NetworkInterface;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, getTimeToLive, "()I"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, getTTL, "()B"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, setTimeToLive, "(I)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, setTTL, "(B)V"),
   NATIVE_METHOD(PlainDatagramSocketImpl, socketGetOption, "(I)Ljava/lang/Object;"),
   NATIVE_METHOD(PlainDatagramSocketImpl, socketSetOption0, "(ILjava/lang/Object;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, datagramSocketClose, "()V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, datagramSocketCreate, "()V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, receive0, "(Ljava/net/DatagramPacket;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, peekData, "(Ljava/net/DatagramPacket;)I"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, peek, "(Ljava/net/InetAddress;)I"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, send, "(Ljava/net/DatagramPacket;)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, disconnect0, "(I)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, connect0, "(Ljava/net/InetAddress;I)V"),
+  NATIVE_METHOD(PlainDatagramSocketImpl, bind0, "(ILjava/net/InetAddress;)V"),
   NATIVE_METHOD(PlainDatagramSocketImpl, init, "()V"),
 };
 
diff --git a/ojluni/src/main/native/Register.cpp b/ojluni/src/main/native/Register.cpp
index 99db9fa..8e767f7 100644
--- a/ojluni/src/main/native/Register.cpp
+++ b/ojluni/src/main/native/Register.cpp
@@ -27,7 +27,7 @@
 
 #include <stdlib.h>
 
-#include <android/log.h>
+#include <log/log.h>
 
 #include "JniConstants.h"
 #include "ScopedLocalFrame.h"
diff --git a/ojluni/src/main/native/System.c b/ojluni/src/main/native/System.c
index 863a8c5..e75e01c 100644
--- a/ojluni/src/main/native/System.c
+++ b/ojluni/src/main/native/System.c
@@ -28,7 +28,7 @@
 #include <string.h>
 #include <time.h>
 
-#include <android/log.h>
+#include <log/log.h>
 
 #include "io_util.h"
 #include "jni.h"
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index 8c8700e..d27ef31 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -198,6 +198,7 @@
     ojluni/src/main/java/java/lang/reflect/Type.java \
     ojluni/src/main/java/java/lang/reflect/TypeVariable.java \
     ojluni/src/main/java/java/lang/reflect/UndeclaredThrowableException.java \
+    ojluni/src/main/java/java/lang/reflect/WeakCache.java \
     ojluni/src/main/java/java/lang/reflect/WildcardType.java \
     ojluni/src/main/java/java/lang/reflect/package-info.java \
     ojluni/src/main/java/java/lang/ref/PhantomReference.java \
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index 4c16601..b4bffc1 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -1021,8 +1021,7 @@
     public static final List<String> ELLIPTIC_CURVES_DEFAULT = Arrays.asList(
             "x25519 (29)",
             "secp256r1 (23)",
-            "secp384r1 (24)",
-            "secp521r1 (25)"
+            "secp384r1 (24)"
             );
 
     private static final Set<String> PERMITTED_DEFAULT_KEY_EXCHANGE_ALGS =
diff --git a/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java b/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java
index a3b2a14..3cc3756 100644
--- a/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java
+++ b/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java
@@ -33,7 +33,7 @@
     /**
      * Constructs valid and invalid zic data for tests.
      */
-    public static class ZoneInfoDataBuilder {
+    public static class ZicDataBuilder {
 
         private int magic = 0x545a6966; // Default, valid magic.
         private Integer transitionCountOverride; // Used to override the correct transition count.
@@ -43,19 +43,19 @@
         private int[] isDsts; // Whether a type uses DST, one per type.
         private int[] offsetsSeconds; // The UTC offset, one per type.
 
-        public ZoneInfoDataBuilder() {}
+        public ZicDataBuilder() {}
 
-        public ZoneInfoDataBuilder setMagic(int magic) {
+        public ZicDataBuilder setMagic(int magic) {
             this.magic = magic;
             return this;
         }
 
-        public ZoneInfoDataBuilder setTypeCountOverride(int typesCountOverride) {
+        public ZicDataBuilder setTypeCountOverride(int typesCountOverride) {
             this.typesCountOverride = typesCountOverride;
             return this;
         }
 
-        public ZoneInfoDataBuilder setTransitionCountOverride(int transitionCountOverride) {
+        public ZicDataBuilder setTransitionCountOverride(int transitionCountOverride) {
             this.transitionCountOverride = transitionCountOverride;
             return this;
         }
@@ -63,7 +63,7 @@
         /**
          * See {@link #setTransitions(int[][])} and {@link #setTypes(int[][])}.
          */
-        public ZoneInfoDataBuilder setTransitionsAndTypes(
+        public ZicDataBuilder setTransitionsAndTypes(
                 int[][] transitionPairs, int[][] typePairs) {
             setTransitions(transitionPairs);
             setTypes(typePairs);
@@ -77,7 +77,7 @@
          *   { transitionTimeSeconds2, typeIndex1 },
          * }
          */
-        public ZoneInfoDataBuilder setTransitions(int[][] transitionPairs) {
+        public ZicDataBuilder setTransitions(int[][] transitionPairs) {
             int[] transitions = new int[transitionPairs.length];
             byte[] types = new byte[transitionPairs.length];
             for (int i = 0; i < transitionPairs.length; i++) {
@@ -97,7 +97,7 @@
          *   { typeIsDst2, offsetSeconds2 },
          * }
          */
-        public ZoneInfoDataBuilder setTypes(int[][] typePairs) {
+        public ZicDataBuilder setTypes(int[][] typePairs) {
             int[] isDsts = new int[typePairs.length];
             int[] offsetSeconds = new int[typePairs.length];
             for (int i = 0; i < typePairs.length; i++) {
@@ -110,7 +110,7 @@
         }
 
         /** Initializes to a minimum viable ZoneInfo data. */
-        public ZoneInfoDataBuilder initializeToValid() {
+        public ZicDataBuilder initializeToValid() {
             setTransitions(new int[0][0]);
             setTypes(new int[][] {
                     { 3600, 0}
@@ -159,7 +159,7 @@
     }
 
     /**
-     * Constructs valid and invalid TzData files for tests. See also ZoneCompactor class in
+     * Constructs valid and invalid tzdata files for tests. See also ZoneCompactor class in
      * external/icu which is the real thing.
      */
     public static class TzDataBuilder {
@@ -212,7 +212,7 @@
 
         public TzDataBuilder initializeToValid() {
             setHeaderMagic("tzdata9999a");
-            addZicData("Europe/Elbonia", new ZoneInfoDataBuilder().initializeToValid().build());
+            addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
             setZoneTab("ZoneTab data");
             return this;
         }
diff --git a/tzdata/tools2/createTimeZoneBundle.sh b/tzdata/tools2/createTimeZoneBundle.sh
new file mode 100755
index 0000000..664fd81
--- /dev/null
+++ b/tzdata/tools2/createTimeZoneBundle.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# A script to generate TZ data updates.
+#
+# Usage: ./createTimeZoneBundle.sh <tzupdate.properties file> <output file>
+# See libcore.tzdata.update2.tools.CreateTimeZoneBundle for more information.
+
+TOOLS_DIR=src/main/libcore/tzdata/update2/tools
+UPDATE_DIR=../update2/src/main/libcore/tzdata/update2
+GEN_DIR=./gen
+
+# Fail if anything below fails
+set -e
+
+rm -rf ${GEN_DIR}
+mkdir -p ${GEN_DIR}
+
+javac \
+    ${TOOLS_DIR}/CreateTimeZoneBundle.java \
+    ${TOOLS_DIR}/TimeZoneBundleBuilder.java \
+    ${UPDATE_DIR}/BundleException.java \
+    ${UPDATE_DIR}/BundleVersion.java \
+    ${UPDATE_DIR}/FileUtils.java \
+    ${UPDATE_DIR}/TimeZoneBundle.java \
+    -d ${GEN_DIR}
+
+java -cp ${GEN_DIR} libcore.tzdata.update2.tools.CreateTimeZoneBundle $@
diff --git a/tzdata/tools2/createTzDataBundle.sh b/tzdata/tools2/createTzDataBundle.sh
deleted file mode 100755
index 2b97a61..0000000
--- a/tzdata/tools2/createTzDataBundle.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-# A script to generate TZ data updates.
-#
-# Usage: ./createTzDataBundle.sh <tzupdate.properties file> <output file>
-# See libcore.tzdata.update2.tools.CreateTzDataBundle for more information.
-
-TOOLS_DIR=src/main/libcore/tzdata/update2/tools
-UPDATE_DIR=../update2/src/main/libcore/tzdata/update2
-GEN_DIR=./gen
-
-# Fail if anything below fails
-set -e
-
-rm -rf ${GEN_DIR}
-mkdir -p ${GEN_DIR}
-
-javac \
-    ${TOOLS_DIR}/CreateTzDataBundle.java \
-    ${TOOLS_DIR}/TzDataBundleBuilder.java \
-    ${UPDATE_DIR}/ConfigBundle.java \
-    ${UPDATE_DIR}/FileUtils.java \
-    -d ${GEN_DIR}
-
-java -cp ${GEN_DIR} libcore.tzdata.update2.tools.CreateTzDataBundle $@
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTzDataBundle.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneBundle.java
similarity index 79%
rename from tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTzDataBundle.java
rename to tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneBundle.java
index fa3a39c..6297663 100644
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTzDataBundle.java
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneBundle.java
@@ -23,18 +23,18 @@
 import java.io.OutputStream;
 import java.io.Reader;
 import java.util.Properties;
-import libcore.tzdata.update2.ConfigBundle;
+import libcore.tzdata.update2.TimeZoneBundle;
 
 /**
- * A command-line tool for creating a TZ data update bundle.
+ * A command-line tool for creating a timezone update bundle.
  *
  * Args:
  * tzdata.properties file - the file describing the bundle (see template file in tzdata/tools)
  * output file - the name of the file to be generated
  */
-public class CreateTzDataBundle {
+public class CreateTimeZoneBundle {
 
-    private CreateTzDataBundle() {}
+    private CreateTimeZoneBundle() {}
 
     public static void main(String[] args) throws Exception {
         if (args.length != 2) {
@@ -48,15 +48,16 @@
             System.exit(2);
         }
         Properties p = loadProperties(f);
-        TzDataBundleBuilder builder = new TzDataBundleBuilder()
-                .setTzDataVersion(getMandatoryProperty(p, "tzdata.version"))
-                .addBionicTzData(getMandatoryPropertyFile(p, "bionic.file"))
-                .addIcuTzData(getMandatoryPropertyFile(p, "icu.file"));
+        TimeZoneBundleBuilder builder = new TimeZoneBundleBuilder()
+                .setRulesVersion(getMandatoryProperty(p, "rules.version"))
+                .setAndroidRevision(getMandatoryProperty(p, "android.revision"))
+                .setTzData(getMandatoryPropertyFile(p, "bionic.file"))
+                .setIcuData(getMandatoryPropertyFile(p, "icu.file"));
 
-        ConfigBundle bundle = builder.build();
+        TimeZoneBundle bundle = builder.build();
         File outputFile = new File(args[1]);
         try (OutputStream os = new FileOutputStream(outputFile)) {
-            os.write(bundle.getBundleBytes());
+            os.write(bundle.getBytes());
         }
         System.out.println("Wrote: " + outputFile);
     }
@@ -93,7 +94,7 @@
 
     private static void printUsage() {
         System.out.println("Usage:");
-        System.out.println("\t" + CreateTzDataBundle.class.getName() +
+        System.out.println("\t" + CreateTimeZoneBundle.class.getName() +
                 " <tzupdate.properties file> <output file>");
     }
 }
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneBundleBuilder.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneBundleBuilder.java
new file mode 100644
index 0000000..39aa496
--- /dev/null
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneBundleBuilder.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 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.tzdata.update2.tools;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import libcore.tzdata.update2.BundleException;
+import libcore.tzdata.update2.BundleVersion;
+import libcore.tzdata.update2.TimeZoneBundle;
+
+/**
+ * A class for creating a {@link TimeZoneBundle} containing timezone update data. Used in real
+ * bundle creation code and tests.
+ */
+public final class TimeZoneBundleBuilder {
+
+    private String bundleFormatVersion = BundleVersion.FULL_BUNDLE_FORMAT_VERSION;
+    private String rulesVersion;
+    private String androidRevision;
+    private byte[] tzData;
+    private byte[] icuData;
+
+    // For use in tests.
+    public TimeZoneBundleBuilder setBundleVersionForTests(String bundleVersion) {
+        this.bundleFormatVersion = bundleVersion;
+        return this;
+    }
+
+    public TimeZoneBundleBuilder setRulesVersion(String rulesVersion) {
+        this.rulesVersion = rulesVersion;
+        return this;
+    }
+
+    public TimeZoneBundleBuilder setAndroidRevision(String androidRevision) {
+        this.androidRevision = androidRevision;
+        return this;
+    }
+
+    public TimeZoneBundleBuilder clearVersionForTests() {
+        // This has the effect of omitting the version file in buildUnvalidated().
+        this.bundleFormatVersion = null;
+        return this;
+    }
+
+    public TimeZoneBundleBuilder setTzData(File tzDataFile) throws IOException {
+        return setTzData(readFileAsByteArray(tzDataFile));
+    }
+
+    public TimeZoneBundleBuilder setTzData(byte[] tzData) {
+        this.tzData = tzData;
+        return this;
+    }
+
+    // For use in tests.
+    public TimeZoneBundleBuilder clearTzDataForTests() {
+        this.tzData = null;
+        return this;
+    }
+
+    public TimeZoneBundleBuilder setIcuData(File icuDataFile) throws IOException {
+        return setIcuData(readFileAsByteArray(icuDataFile));
+    }
+
+    public TimeZoneBundleBuilder setIcuData(byte[] icuData) {
+        this.icuData = icuData;
+        return this;
+    }
+
+    // For use in tests.
+    public TimeZoneBundleBuilder clearIcuDataForTests() {
+        this.icuData = null;
+        return this;
+    }
+
+    /**
+     * For use in tests. Use {@link #build()}.
+     */
+    public TimeZoneBundle buildUnvalidated() throws BundleException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (ZipOutputStream zos = new ZipOutputStream(baos)) {
+            if (bundleFormatVersion != null && rulesVersion != null && androidRevision != null) {
+                BundleVersion bundleVersion =
+                        new BundleVersion(bundleFormatVersion, rulesVersion, androidRevision);
+                addZipEntry(zos, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME, bundleVersion.getBytes());
+            }
+
+            if (tzData != null) {
+                addZipEntry(zos, TimeZoneBundle.TZDATA_FILE_NAME, tzData);
+            }
+            if (icuData != null) {
+                addZipEntry(zos, TimeZoneBundle.ICU_DATA_FILE_NAME, icuData);
+            }
+        } catch (IOException e) {
+            throw new BundleException("Unable to create zip file", e);
+        }
+        return new TimeZoneBundle(baos.toByteArray());
+    }
+
+    /**
+     * Builds a {@link TimeZoneBundle}.
+     */
+    public TimeZoneBundle build() throws BundleException {
+        if (bundleFormatVersion == null) {
+            throw new IllegalStateException("Missing bundleVersion");
+        }
+        if (!BundleVersion.BUNDLE_FORMAT_VERSION_PATTERN.matcher(bundleFormatVersion).matches()) {
+            throw new IllegalStateException("bundleVersion invalid: " + bundleFormatVersion);
+        }
+
+        if (rulesVersion == null) {
+            throw new IllegalStateException("Missing rulesVersion");
+        }
+        if (!BundleVersion.RULES_VERSION_PATTERN.matcher(rulesVersion).matches()) {
+            throw new IllegalStateException("rulesVersion invalid: " + rulesVersion);
+        }
+
+        if (androidRevision == null) {
+            throw new IllegalStateException("Missing androidRevision");
+        }
+        if (!BundleVersion.ANDROID_REVISION_PATTERN.matcher(androidRevision).matches()) {
+            throw new IllegalStateException("androidRevision invalid: " + androidRevision);
+        }
+        if (icuData == null) {
+            throw new IllegalStateException("Missing icuData");
+        }
+        if (tzData == null) {
+            throw new IllegalStateException("Missing tzData");
+        }
+        return buildUnvalidated();
+    }
+
+    private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
+            throws BundleException {
+        try {
+            ZipEntry zipEntry = new ZipEntry(name);
+            zipEntry.setSize(content.length);
+            zos.putNextEntry(zipEntry);
+            zos.write(content);
+            zos.closeEntry();
+        } catch (IOException e) {
+            throw new BundleException("Unable to add zip entry", e);
+        }
+    }
+
+    /**
+     * Returns the contents of 'path' as a byte array.
+     */
+    public static byte[] readFileAsByteArray(File file) throws IOException {
+        byte[] buffer = new byte[8192];
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (FileInputStream  fis = new FileInputStream(file)) {
+            int count;
+            while ((count = fis.read(buffer)) != -1) {
+                baos.write(buffer, 0, count);
+            }
+        }
+        return baos.toByteArray();
+    }
+}
+
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TzDataBundleBuilder.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TzDataBundleBuilder.java
deleted file mode 100644
index d13c197..0000000
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TzDataBundleBuilder.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 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.tzdata.update2.tools;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import libcore.tzdata.update2.ConfigBundle;
-
-/**
- * A class for creating a {@link ConfigBundle} containing timezone update data.
- */
-public final class TzDataBundleBuilder {
-
-    private String tzDataVersion;
-    private File zoneInfoFile;
-    private File icuTzDataFile;
-
-    public TzDataBundleBuilder setTzDataVersion(String tzDataVersion) {
-        this.tzDataVersion = tzDataVersion;
-        return this;
-    }
-
-    public TzDataBundleBuilder addBionicTzData(File zoneInfoFile) {
-        this.zoneInfoFile = zoneInfoFile;
-        return this;
-    }
-
-    public TzDataBundleBuilder addIcuTzData(File icuTzDataFile) {
-        this.icuTzDataFile = icuTzDataFile;
-        return this;
-    }
-
-    /**
-     * Builds a {@link libcore.tzdata.update2.ConfigBundle}.
-     */
-    public ConfigBundle build() throws IOException {
-        if (tzDataVersion == null) {
-            throw new IllegalStateException("Missing tzDataVersion");
-        }
-        if (zoneInfoFile == null) {
-            throw new IllegalStateException("Missing zoneInfo file");
-        }
-
-        return buildUnvalidated();
-    }
-
-    // For use in tests.
-    public TzDataBundleBuilder clearBionicTzData() {
-        this.zoneInfoFile = null;
-        return this;
-    }
-
-    /**
-     * For use in tests. Use {@link #build()}.
-     */
-    public ConfigBundle buildUnvalidated() throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try (ZipOutputStream zos = new ZipOutputStream(baos)) {
-            addZipEntry(zos, ConfigBundle.BUNDLE_VERSION_FILE_NAME,
-                    ConfigBundle.BUNDLE_VERSION_BYTES);
-            if (tzDataVersion != null) {
-                addZipEntry(zos, ConfigBundle.TZ_DATA_VERSION_FILE_NAME,
-                        tzDataVersion.getBytes(StandardCharsets.UTF_8));
-            }
-            if (zoneInfoFile != null) {
-                addZipEntry(zos, ConfigBundle.ZONEINFO_FILE_NAME,
-                        readFileAsByteArray(zoneInfoFile));
-            }
-            if (icuTzDataFile != null) {
-                addZipEntry(zos, ConfigBundle.ICU_DATA_FILE_NAME,
-                        readFileAsByteArray(icuTzDataFile));
-            }
-        }
-        return new ConfigBundle(baos.toByteArray());
-    }
-
-    private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
-            throws IOException {
-        ZipEntry zipEntry = new ZipEntry(name);
-        zipEntry.setSize(content.length);
-        zos.putNextEntry(zipEntry);
-        zos.write(content);
-        zos.closeEntry();
-    }
-
-    /**
-     * Returns the contents of 'path' as a byte array.
-     */
-    public static byte[] readFileAsByteArray(File file) throws IOException {
-        byte[] buffer = new byte[8192];
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try (FileInputStream  fis = new FileInputStream(file)) {
-            int count;
-            while ((count = fis.read(buffer)) != -1) {
-                baos.write(buffer, 0, count);
-            }
-        }
-        return baos.toByteArray();
-    }
-}
-
diff --git a/tzdata/tools2/testing/prepareTzDataUpdates.sh b/tzdata/tools2/testing/prepareTzDataUpdates.sh
new file mode 100755
index 0000000..99cd60f
--- /dev/null
+++ b/tzdata/tools2/testing/prepareTzDataUpdates.sh
@@ -0,0 +1,170 @@
+#!/bin/bash
+
+# Used as part of manual testing of tzdata update mechanism.
+
+if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
+  echo "Configure your environment with build/envsetup.sh and lunch"
+  exit 1
+fi
+
+# Fail if anything below fails
+set -e
+
+cd $ANDROID_BUILD_TOP
+
+# Get the ICU version by looking at the stubdata file
+ICU_STUB_FILE=$(ls external/icu/icu4c/source/stubdata/icudt*l.dat)
+if [ $(echo "$ICU_STUB_FILE" | wc -l) -ne 1 ]; then
+  echo "Could not find unique ICU version from external/icu/icu4c/source/stubdata/icudt*l.dat"
+  exit 1
+fi
+
+ICU_VERSION=${ICU_STUB_FILE##*icudt}
+ICU_VERSION=${ICU_VERSION%l.dat}
+
+echo "Current ICU version is ${ICU_VERSION}"
+
+# Get the current tzdata version and find both the previous and new versions.
+TZDATA=libc/zoneinfo/tzdata
+
+TZHEADER=$(head -n1 bionic/$TZDATA | cut -c1-11)
+
+TZ_CURRENT=${TZHEADER:6}
+
+let currentYear=${TZ_CURRENT:0:4}
+update=${TZ_CURRENT:4:1}
+
+let currentUpdate=$(LC_CTYPE=C printf '%d' "'$update")
+let previousUpdate=currentUpdate-1
+if [ $previousUpdate -lt 97 ]; then
+  let previousYear=currentYear-1
+  PREVIOUSCOMMIT=$(cd bionic; git log --format=oneline ${TZDATA} | grep -E "${previousYear}.$" | head -n1)
+  TZ_PREVIOUS=$(echo $PREVIOUSCOMMIT | sed "s|.*\(${previousYear}.\)\$|\1|")
+else
+  TZ_PREVIOUS=${currentYear}$(printf "\\$(printf '%03o' "${previousUpdate}")")
+  PREVIOUSCOMMIT=$(cd bionic; git log --format=oneline ${TZDATA} | grep -E "${TZ_PREVIOUS}$" | head -n1)
+fi
+
+let nextUpdate=currentUpdate+1
+TZ_NEXT=${currentYear}$(printf "\\$(printf '%03o' "${nextUpdate}")")
+
+echo "Current version of bionic/${TZDATA} is ${TZ_CURRENT}"
+echo "Previous version of bionic/${TZDATA} is ${TZ_PREVIOUS} from commit:"
+echo "    ${PREVIOUSCOMMIT}"
+echo "Next version of bionic/${TZDATA} is ${TZ_NEXT}"
+
+TMP=$(mktemp -d)
+
+TZ_PREVIOUS_SHA=${PREVIOUSCOMMIT%% *}
+
+TMP_PREVIOUS=${TMP}/${TZ_PREVIOUS}_test
+mkdir -p ${TMP_PREVIOUS}
+
+echo "Copied ${TZ_PREVIOUS} tzdata to ${TMP_PREVIOUS}"
+(cd bionic; git show ${TZ_PREVIOUS_SHA}:${TZDATA} > ${TMP_PREVIOUS}/tzdata)
+
+cd libcore/tzdata/tools2
+
+################################################################
+# Preparing previous update version.
+################################################################
+
+# Create an icu_tzdata.dat for the previous tzdata version.
+# Download the archive for the previous tzdata version.
+PREVIOUS_TAR_GZ="tzdata${TZ_PREVIOUS}.tar.gz"
+TMP_PREVIOUS_TAR_GZ=${TMP}/${PREVIOUS_TAR_GZ}
+IANA_PREVIOUS_URL="ftp://ftp.iana.org/tz/releases/${PREVIOUS_TAR_GZ}"
+echo "Downloading archive for ${TZ_PREVIOUS} version from ${IANA_PREVIOUS_URL}"
+wget -O ${TMP_PREVIOUS_TAR_GZ} ftp://ftp.iana.org/tz/releases/tzdata2016d.tar.gz -o ${TMP}/${PREVIOUS_TAR_GZ}.log
+
+ICU_UPDATE_RESOURCES_LOG=${TMP}/icuUpdateResources.log
+echo "Creating icu_tzdata.dat file for ${TZ_PREVIOUS}/ICU ${ICU_VERSION}, may take a while, check ${ICU_UPDATE_RESOURCES_LOG} for details"
+./createIcuUpdateResources.sh ${TMP_PREVIOUS_TAR_GZ} ${ICU_VERSION} &> ${ICU_UPDATE_RESOURCES_LOG}
+mv icu_tzdata.dat ${TMP_PREVIOUS}
+
+TZ_PREVIOUS_UPDATE_PROPERTIES=${TMP}/tzupdate.properties.${TZ_PREVIOUS}
+cat > ${TZ_PREVIOUS_UPDATE_PROPERTIES} <<EOF
+android.revision=001
+rules.version=${TZ_PREVIOUS}
+bionic.file=${TMP_PREVIOUS}/tzdata
+icu.file=${TMP_PREVIOUS}/icu_tzdata.dat
+EOF
+
+TZ_PREVIOUS_UPDATE_ZIP=update_${TZ_PREVIOUS}_test.zip
+./createTimeZoneBundle.sh ${TZ_PREVIOUS_UPDATE_PROPERTIES} ${TMP}/update_${TZ_PREVIOUS}_test.zip
+adb push ${TMP}/${TZ_PREVIOUS_UPDATE_ZIP} /data/local/tmp
+echo "Pushed ${TZ_PREVIOUS_UPDATE_ZIP} to /data/local/tmp"
+
+################################################################
+# Preparing current update version.
+################################################################
+
+# Replace the version number in the current tzdata to create the current version
+TMP_CURRENT=${TMP}/${TZ_CURRENT}_test
+mkdir -p ${TMP_CURRENT}
+sed "1s/^tzdata${TZ_PREVIOUS}/tzdata${TZ_NEXT}/" ${TMP_PREVIOUS}/tzdata > ${TMP_CURRENT}/tzdata
+echo "Transformed version ${TZ_PREVIOUS} of tzdata into version ${TZ_CURRENT}"
+
+# Replace the version number in the previous icu_tzdata to create the next version
+SEARCH=$(echo ${TZ_PREVIOUS} | sed "s/\(.\)/\1\\\x00/g")
+REPLACE=$(echo ${TZ_CURRENT} | sed "s/\(.\)/\1\\\x00/g")
+sed "s/$SEARCH/$REPLACE/" ${TMP_PREVIOUS}/icu_tzdata.dat > ${TMP_CURRENT}/icu_tzdata.dat
+echo "Transformed version ${TZ_PREVIOUS} of icu_tzdata into version ${TZ_CURRENT}"
+
+TZ_CURRENT_UPDATE_PROPERTIES=${TMP}/tzupdate.properties.${TZ_CURRENT}
+cat > ${TZ_CURRENT_UPDATE_PROPERTIES} <<EOF
+android.revision=001
+rules.version=${TZ_CURRENT}
+bionic.file=${TMP_CURRENT}/tzdata
+icu.file=${TMP_CURRENT}/icu_tzdata.dat
+EOF
+
+TZ_CURRENT_UPDATE_ZIP=update_${TZ_CURRENT}_test.zip
+./createTimeZoneBundle.sh ${TZ_CURRENT_UPDATE_PROPERTIES} ${TMP}/update_${TZ_CURRENT}_test.zip
+adb push ${TMP}/${TZ_CURRENT_UPDATE_ZIP} /data/local/tmp
+echo "Pushed ${TZ_CURRENT_UPDATE_ZIP} to /data/local/tmp"
+
+################################################################
+# Preparing next update version.
+################################################################
+
+# Replace the version number in the previous tzdata to create the next version
+TMP_NEXT=${TMP}/${TZ_NEXT}_test
+mkdir -p ${TMP_NEXT}
+sed "1s/^tzdata${TZ_PREVIOUS}/tzdata${TZ_NEXT}/" ${TMP_PREVIOUS}/tzdata > ${TMP_NEXT}/tzdata
+echo "Transformed version ${TZ_PREVIOUS} of tzdata into version ${TZ_NEXT}"
+
+# Replace the version number in the previous icu_tzdata to create the next version
+SEARCH=$(echo ${TZ_PREVIOUS} | sed "s/\(.\)/\1\\\x00/g")
+REPLACE=$(echo ${TZ_NEXT} | sed "s/\(.\)/\1\\\x00/g")
+sed "s/$SEARCH/$REPLACE/" ${TMP_PREVIOUS}/icu_tzdata.dat > ${TMP_NEXT}/icu_tzdata.dat
+echo "Transformed version ${TZ_PREVIOUS} of icu_tzdata into version ${TZ_NEXT}"
+
+TZ_NEXT_UPDATE_PROPERTIES=${TMP}/tzupdate.properties.${TZ_NEXT}
+cat > ${TZ_NEXT_UPDATE_PROPERTIES} <<EOF
+android.revision=001
+rules.version=${TZ_NEXT}
+bionic.file=${TMP_NEXT}/tzdata
+icu.file=${TMP_NEXT}/icu_tzdata.dat
+EOF
+
+TZ_NEXT_UPDATE_ZIP=update_${TZ_NEXT}_test.zip
+./createTimeZoneBundle.sh ${TZ_NEXT_UPDATE_PROPERTIES} ${TMP}/update_${TZ_NEXT}_test.zip
+adb push ${TMP}/${TZ_NEXT_UPDATE_ZIP} /data/local/tmp
+echo "Pushed ${TZ_NEXT_UPDATE_ZIP} to /data/local/tmp"
+
+################################################################
+# Preparing UpdateTestApp
+################################################################
+
+UPDATE_TEST_APP_LOG=${TMP}/updateTestApp.log
+echo "Building and installing UpdateTestApp, check ${UPDATE_TEST_APP_LOG}"
+cd $ANDROID_BUILD_TOP
+make -j -l8 UpdateTestApp &> ${UPDATE_TEST_APP_LOG}
+adb install -r ${ANDROID_PRODUCT_OUT}/data/app/UpdateTestApp/UpdateTestApp.apk &>> ${UPDATE_TEST_APP_LOG}
+
+echo
+echo "Paste the following into your shell"
+echo "OLD_TZUPDATE=${TMP}/${TZ_PREVIOUS_UPDATE_ZIP}"
+echo "CURRENT_TZUPDATE=${TMP}/${TZ_CURRENT_UPDATE_ZIP}"
+echo "NEW_TZUPDATE=${TMP}/${TZ_NEXT_UPDATE_ZIP}"
diff --git a/tzdata/tools2/testing/rebootAndGrabLogs.sh b/tzdata/tools2/testing/rebootAndGrabLogs.sh
new file mode 100755
index 0000000..a85481f
--- /dev/null
+++ b/tzdata/tools2/testing/rebootAndGrabLogs.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Used as part of manual testing of tzdata update mechanism.
+
+PREFIX=${1-bootlogs}
+
+echo "Rebooting...."
+adb reboot && adb wait-for-device
+
+TIME=${2-5}
+LOGCAT=${PREFIX}.logcat
+echo "Dumping logcat output in ${LOGCAT}, waiting for ${TIME} seconds"
+adb logcat > ${LOGCAT} 2>/dev/null &
+LOGCAT_PID=$!
+
+sleep ${TIME}
+
+# Kill the logcat process and wait, suppresses the Terminated message
+# output by the shell.
+kill ${LOGCAT_PID}
+wait ${LOGCAT_PID} 2>/dev/null
+
+DMESG=${PREFIX}.dmesg
+echo "Dumping dmesg output in ${DMESG}"
+adb shell dmesg > ${DMESG}
diff --git a/tzdata/tools2/tzupdate.properties b/tzdata/tools2/tzupdate.properties
index 9dd9467..f46d942 100644
--- a/tzdata/tools2/tzupdate.properties
+++ b/tzdata/tools2/tzupdate.properties
@@ -1,9 +1,11 @@
 # Edit these to reflect the update files.
 
-# This should be the tzdata version. e.g. "2015a". Lexicographical sort order
-# may become important in future so if inventing interim releases only add
-# characters to the end.
-tzdata.version=
+# This should be the tzdata version. e.g. "2015a".
+rules.version=
+
+# This is used to indicate when Android has issued a revision. e.g. "001", "002".
+android.revision=001
+
 bionic.file=
 icu.file=
 
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/BundleException.java b/tzdata/update2/src/main/libcore/tzdata/update2/BundleException.java
new file mode 100644
index 0000000..72fb773
--- /dev/null
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/BundleException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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.tzdata.update2;
+
+/**
+ * A checked exception used in connection with time zone bundle creation / installation.
+ */
+public class BundleException extends Exception {
+
+    public BundleException(String message) {
+        super(message);
+    }
+
+    public BundleException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/BundleVersion.java b/tzdata/update2/src/main/libcore/tzdata/update2/BundleVersion.java
new file mode 100644
index 0000000..6a366da
--- /dev/null
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/BundleVersion.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 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.tzdata.update2;
+
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+/**
+ * Constants and logic associated with the timezone bundle version file.
+ */
+public class BundleVersion {
+
+    /**
+     * The current bundle format version in the form XXX.YYY. Increment the first number (XXX)
+     * when making incompatible changes to the bundle structure, or the files contained within.
+     * The second number (YYY) is currently ignored.
+     */
+    public static final String BUNDLE_FORMAT_MAJOR_VERSION = "001";
+
+    public static final String FULL_BUNDLE_FORMAT_VERSION = BUNDLE_FORMAT_MAJOR_VERSION + ".001";
+
+    private static final int BUNDLE_FORMAT_LENGTH = 7;
+
+    public static final Pattern BUNDLE_FORMAT_VERSION_PATTERN = Pattern.compile("\\d{3}\\.\\d{3}");
+
+    /** A pattern that matches the IANA rules value of a rules update. e.g. "2016g" */
+    public static final Pattern RULES_VERSION_PATTERN = Pattern.compile("\\d{4}\\w");
+
+    private static final int RULES_VERSION_LENGTH = 5;
+
+    /** A pattern that matches the Android revision of a rules update. e.g. "001" */
+    public static final Pattern ANDROID_REVISION_PATTERN = Pattern.compile("\\d{3}");
+
+    private static final int ANDROID_REVISION_LENGTH = 3;
+
+    /**
+     * The length of a well-formed bundle version file:
+     * {Bundle version}|{Rule version}|{Android revision}
+     */
+    public static final int BUNDLE_VERSION_FILE_LENGTH = BUNDLE_FORMAT_LENGTH + 1
+            + RULES_VERSION_LENGTH
+            + 1 + ANDROID_REVISION_LENGTH;
+
+    private static final Pattern BUNDLE_VERSION_PATTERN = Pattern.compile(
+            BUNDLE_FORMAT_VERSION_PATTERN.pattern() + "\\|"
+                    + RULES_VERSION_PATTERN.pattern() + "\\|"
+                    + ANDROID_REVISION_PATTERN.pattern()
+                    + ".*" /* ignore trailing */);
+
+    private final String bundleFormatVersion;
+
+    public final String rulesVersion;
+
+    public final String androidRevision;
+
+    public BundleVersion(String bundleFormatVersion, String rulesVersion,
+            String androidRevision) throws BundleException {
+        if (!BUNDLE_FORMAT_VERSION_PATTERN.matcher(bundleFormatVersion).matches()) {
+            throw new BundleException("Invalid bundleFormatVersion: " + bundleFormatVersion);
+        }
+        if (!RULES_VERSION_PATTERN.matcher(rulesVersion).matches()) {
+            throw new BundleException("Invalid rulesVersion: " + rulesVersion);
+        }
+        if (!ANDROID_REVISION_PATTERN.matcher(androidRevision).matches()) {
+            throw new BundleException("Invalid androidRevision: " + androidRevision);
+        }
+        this.bundleFormatVersion = bundleFormatVersion;
+        this.rulesVersion = rulesVersion;
+        this.androidRevision = androidRevision;
+    }
+
+    public static BundleVersion extractFromBytes(byte[] bytes) throws BundleException {
+        String bundleVersion = new String(bytes, StandardCharsets.US_ASCII);
+        try {
+            if (!BUNDLE_VERSION_PATTERN.matcher(bundleVersion).matches()) {
+                throw new BundleException("Invalid bundle version string: " + bundleVersion);
+            }
+            String bundleFormatVersion = bundleVersion.substring(0, 7);
+            String rulesVersion = bundleVersion.substring(8, 13);
+            String androidRevision = bundleVersion.substring(14);
+            return new BundleVersion(bundleFormatVersion, rulesVersion, androidRevision);
+        } catch (IndexOutOfBoundsException e) {
+            // The use of the regexp above should make this impossible.
+            throw new BundleException("Bundle version string too short:" + bundleVersion);
+        }
+    }
+
+    public String getBundleFormatMajorVersion() {
+        return bundleFormatVersion.substring(0, 3);
+    }
+
+    public byte[] getBytes() {
+        return getBytes(bundleFormatVersion, rulesVersion, androidRevision);
+    }
+
+    // @VisibleForTesting - can be used to construct invalid bundle version bytes.
+    public static byte[] getBytes(
+            String bundleFormatVersion, String rulesVersion, String androidRevision) {
+        return (bundleFormatVersion + "|" + rulesVersion + "|" + androidRevision)
+                .getBytes(StandardCharsets.US_ASCII);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        BundleVersion that = (BundleVersion) o;
+
+        if (!bundleFormatVersion.equals(that.bundleFormatVersion)) {
+            return false;
+        }
+        if (!rulesVersion.equals(that.rulesVersion)) {
+            return false;
+        }
+        return androidRevision.equals(that.androidRevision);
+    }
+
+    @Override
+    public String toString() {
+        return "BundleVersion{" +
+                "bundleFormatVersion='" + bundleFormatVersion + '\'' +
+                ", rulesVersion='" + rulesVersion + '\'' +
+                ", androidRevision='" + androidRevision + '\'' +
+                '}';
+    }
+}
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java b/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java
index 3a8515a..3d9a73a 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java
@@ -146,7 +146,7 @@
         FileUtils.doDelete(toDelete);
     }
 
-    public static boolean filesExist(File rootDir, String... fileNames) throws IOException {
+    public static boolean filesExist(File rootDir, String... fileNames) {
         for (String fileName : fileNames) {
             File file = new File(rootDir, fileName);
             if (!file.exists()) {
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/ConfigBundle.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundle.java
similarity index 63%
rename from tzdata/update2/src/main/libcore/tzdata/update2/ConfigBundle.java
rename to tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundle.java
index e4363d4..6d11469 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/ConfigBundle.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundle.java
@@ -16,61 +16,87 @@
 package libcore.tzdata.update2;
 
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
 /**
- * A configuration bundle. This is a thin wrapper around some in-memory bytes representing a zip
+ * A timezone bundle. This is a thin wrapper around some in-memory bytes representing a zip
  * archive and logic for its safe extraction.
  */
-public final class ConfigBundle {
-
-    /**
-     * The name of the file inside the bundle containing the bundle format version.
-     * The content is ASCII bytes representing a version number. The number is
-     * incremented when any of the other contents of the bundle changes in a way that is not
-     * backwards compatible.
-     */
-    public static final String BUNDLE_VERSION_FILE_NAME = "bundle_version";
-
-    /**
-     * The current bundle version. Increment this number when making incompatible changes to the
-     * bundle structure, or the files contained within.
-     */
-    public static final byte[] BUNDLE_VERSION_BYTES = "1".getBytes(StandardCharsets.US_ASCII);
-
-    /**
-     * The name of the file inside the bundle containing the TZ data version.
-     * The content is ASCII bytes representing a version derived from the IANA rules version the
-     * bundle is based on with an Android-specific suffix to denote minor versioning. e.g.
-     * "2016g 001"
-     */
-    public static final String TZ_DATA_VERSION_FILE_NAME = "tzdata_version";
+public final class TimeZoneBundle {
 
     /** The name of the file inside the bundle containing bionic/libcore TZ data. */
-    public static final String ZONEINFO_FILE_NAME = "tzdata";
+    public static final String TZDATA_FILE_NAME = "tzdata";
 
     /** The name of the file inside the bundle containing ICU TZ data. */
     public static final String ICU_DATA_FILE_NAME = "icu/icu_tzdata.dat";
 
+    /**
+     * The name of the file inside the bundle containing the bundle version information.
+     * The content is ASCII bytes representing a set of version numbers. See {@link BundleVersion}.
+     */
+    public static final String BUNDLE_VERSION_FILE_NAME = "bundle_version";
+
     private static final int BUFFER_SIZE = 8192;
 
+    /**
+     * Maximum size of entry getEntryContents() will pull into a byte array. To avoid exhausting
+     * heap memory when encountering unexpectedly large entries. 128k should be enough for anyone.
+     */
+    private static final long MAX_GET_ENTRY_CONTENTS_SIZE = 128 * 1024;
+
     private final byte[] bytes;
 
-    public ConfigBundle(byte[] bytes) {
+    public TimeZoneBundle(byte[] bytes) {
         this.bytes = bytes;
     }
 
-    public byte[] getBundleBytes() {
+    public byte[] getBytes() {
         return bytes;
     }
 
+    public BundleVersion getBundleVersion() throws BundleException, IOException {
+        byte[] contents = getEntryContents(
+                new ByteArrayInputStream(bytes), BUNDLE_VERSION_FILE_NAME);
+        if (contents == null) {
+            throw new BundleException("Bundle version file entry not found");
+        }
+        return BundleVersion.extractFromBytes(contents);
+    }
+
+    private static byte[] getEntryContents(InputStream is, String entryName) throws IOException {
+        try (ZipInputStream zipInputStream = new ZipInputStream(is)) {
+            ZipEntry entry;
+            while ((entry = zipInputStream.getNextEntry()) != null) {
+                String name = entry.getName();
+
+                if (!entryName.equals(name)) {
+                    continue;
+                }
+                // Guard against massive entries consuming too much heap memory.
+                if (entry.getSize() > MAX_GET_ENTRY_CONTENTS_SIZE) {
+                    throw new IOException("Entry " + entryName + " too large: " + entry.getSize());
+                }
+                byte[] buffer = new byte[BUFFER_SIZE];
+                try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+                    int count;
+                    while ((count = zipInputStream.read(buffer)) != -1) {
+                        baos.write(buffer, 0, count);
+                    }
+                    return baos.toByteArray();
+                }
+            }
+            // Entry not found.
+            return null;
+        }
+    }
+
     public void extractTo(File targetDir) throws IOException {
         extractZipSafely(new ByteArrayInputStream(bytes), targetDir, true /* makeWorldReadable */);
     }
@@ -128,7 +154,7 @@
             return false;
         }
 
-        ConfigBundle that = (ConfigBundle) o;
+        TimeZoneBundle that = (TimeZoneBundle) o;
 
         if (!Arrays.equals(bytes, that.bytes)) {
             return false;
@@ -136,5 +162,4 @@
 
         return true;
     }
-
 }
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/TzDataBundleInstaller.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundleInstaller.java
similarity index 63%
rename from tzdata/update2/src/main/libcore/tzdata/update2/TzDataBundleInstaller.java
rename to tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundleInstaller.java
index 1ffe860..601b8e2 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/TzDataBundleInstaller.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundleInstaller.java
@@ -18,16 +18,15 @@
 import android.util.Slog;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
 import libcore.util.ZoneInfoDB;
 
 /**
  * A bundle-validation / extraction class. Separate from the services code that uses it for easier
- * testing.
+ * testing. This class is not thread-safe: callers are expected to handle mutual exclusion.
  */
-public final class TzDataBundleInstaller {
+public final class TimeZoneBundleInstaller {
 
     private static final String CURRENT_TZ_DATA_DIR_NAME = "current";
     private static final String WORKING_DIR_NAME = "working";
@@ -39,7 +38,7 @@
     private final File currentTzDataDir;
     private final File workingDir;
 
-    public TzDataBundleInstaller(String logTag, File systemTzDataFile, File installDir) {
+    public TimeZoneBundleInstaller(String logTag, File systemTzDataFile, File installDir) {
         this.logTag = logTag;
         this.systemTzDataFile = systemTzDataFile;
         oldTzDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
@@ -66,7 +65,7 @@
      * Install the supplied content.
      *
      * <p>Errors during unpacking or installation will throw an {@link IOException}.
-     * If the content is invalid this method returns {@code false}.
+     * If the bundle content is invalid this method returns {@code false}.
      * If the installation completed successfully this method returns {@code true}.
      */
     public boolean install(byte[] content) throws IOException {
@@ -80,23 +79,34 @@
         Slog.i(logTag, "Unpacking / verifying time zone update");
         unpackBundle(content, workingDir);
         try {
-            if (!checkBundleVersion(workingDir)) {
-                Slog.i(logTag, "Update not applied: Bundle format version is incorrect.");
+            BundleVersion bundleVersion;
+            try {
+                bundleVersion = readBundleVersion(workingDir);
+            } catch (BundleException e) {
+                Slog.i(logTag, "Invalid bundle version: " + e.getMessage());
                 return false;
             }
-            // This check should not fail if the bundle version check passes, but we're being
-            // intentionally paranoid.
-            if (!checkBundleFilesExist(workingDir)) {
-                Slog.i(logTag, "Update not applied: Bundle is missing files");
+            if (bundleVersion == null) {
+                Slog.i(logTag, "Update not applied: Bundle version could not be loaded");
+                return false;
+            }
+            if (!checkBundleFormatVersion(bundleVersion)) {
+                Slog.i(logTag, "Update not applied: Bundle format version check failed: "
+                        + bundleVersion);
                 return false;
             }
 
-            if (!checkBundleRulesNewerThanSystem(systemTzDataFile, workingDir)) {
+            if (!checkBundleDataFilesExist(workingDir)) {
+                Slog.i(logTag, "Update not applied: Bundle is missing required data file(s)");
+                return false;
+            }
+
+            if (!checkBundleRulesNewerThanSystem(systemTzDataFile, bundleVersion)) {
                 Slog.i(logTag, "Update not applied: Bundle rules version check failed");
                 return false;
             }
 
-            File zoneInfoFile = new File(workingDir, ConfigBundle.ZONEINFO_FILE_NAME);
+            File zoneInfoFile = new File(workingDir, TimeZoneBundle.TZDATA_FILE_NAME);
             ZoneInfoDB.TzData tzData = ZoneInfoDB.TzData.loadTzData(zoneInfoFile.getPath());
             if (tzData == null) {
                 Slog.i(logTag, "Update not applied: " + zoneInfoFile + " could not be loaded");
@@ -163,6 +173,30 @@
         }
     }
 
+    /**
+     * Reads the currently installed bundle version. Returns {@code null} if there is no bundle
+     * installed.
+     *
+     * @throws IOException if there was a problem reading data from /data
+     * @throws BundleException if there was a problem with the installed bundle format/structure
+     */
+    public BundleVersion getInstalledBundleVersion() throws BundleException, IOException {
+        if (!currentTzDataDir.exists()) {
+            return null;
+        }
+        return readBundleVersion(currentTzDataDir);
+    }
+
+    /**
+     * Reads the timezone rules version present in /system. i.e. the version that would be present
+     * after a factory reset.
+     *
+     * @throws IOException if there was a problem reading data
+     */
+    public String getSystemRulesVersion() throws IOException {
+        return readSystemRulesVersion(systemTzDataFile);
+    }
+
     private void deleteBestEffort(File dir) {
         if (dir.exists()) {
             try {
@@ -176,64 +210,63 @@
 
     private void unpackBundle(byte[] content, File targetDir) throws IOException {
         Slog.i(logTag, "Unpacking update content to: " + targetDir);
-        ConfigBundle bundle = new ConfigBundle(content);
+        TimeZoneBundle bundle = new TimeZoneBundle(content);
         bundle.extractTo(targetDir);
     }
 
-    private boolean checkBundleFilesExist(File unpackedContentDir) throws IOException {
+    private boolean checkBundleDataFilesExist(File unpackedContentDir) throws IOException {
         Slog.i(logTag, "Verifying bundle contents");
         return FileUtils.filesExist(unpackedContentDir,
-                ConfigBundle.TZ_DATA_VERSION_FILE_NAME,
-                ConfigBundle.ZONEINFO_FILE_NAME,
-                ConfigBundle.ICU_DATA_FILE_NAME);
+                TimeZoneBundle.TZDATA_FILE_NAME,
+                TimeZoneBundle.ICU_DATA_FILE_NAME);
     }
 
-    private boolean checkBundleVersion(File unpackedContentDir) throws IOException {
-        Slog.i(logTag, "Verifying bundle format version");
-        if (!FileUtils.filesExist(unpackedContentDir, ConfigBundle.BUNDLE_VERSION_FILE_NAME)) {
-            Slog.i(logTag, "Bundle format version file does not exist.");
-            return false;
-        }
-
+    private BundleVersion readBundleVersion(File bundleDir) throws BundleException, IOException {
+        Slog.i(logTag, "Reading bundle format version");
         File bundleVersionFile =
-                new File(unpackedContentDir, ConfigBundle.BUNDLE_VERSION_FILE_NAME);
-        byte[] versionBytes = FileUtils.readBytes(bundleVersionFile, 10);
-        if (!Arrays.equals(versionBytes, ConfigBundle.BUNDLE_VERSION_BYTES)) {
-            Slog.i(logTag, "Incompatible bundle format version: " + Arrays.toString(versionBytes));
-            return false;
+                new File(bundleDir, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME);
+        if (!bundleVersionFile.exists()) {
+            throw new BundleException("No bundle version file found: " + bundleVersionFile);
         }
-        return true;
+        byte[] versionBytes =
+                FileUtils.readBytes(bundleVersionFile, BundleVersion.BUNDLE_VERSION_FILE_LENGTH);
+        return BundleVersion.extractFromBytes(versionBytes);
+    }
+
+    private boolean checkBundleFormatVersion(BundleVersion bundleVersion) {
+        return bundleVersion.getBundleFormatMajorVersion()
+                .equals(BundleVersion.BUNDLE_FORMAT_MAJOR_VERSION);
     }
 
     /**
      * Returns true if the the bundle IANA rules version is >= system IANA rules version.
      */
     private boolean checkBundleRulesNewerThanSystem(
-            File systemTzDataFile, File unpackedContentDir) throws IOException {
-        Slog.i(logTag, "Reading bundle rules version");
-        File bundleRulesFile =
-                new File(unpackedContentDir, ConfigBundle.TZ_DATA_VERSION_FILE_NAME);
-        // Read the first 5 bytes of the bundle rules version file. It is expected to be longer but
-        // should always starts with the IANA release version, e.g. 2016g.
-        byte[] rulesBytes = FileUtils.readBytes(bundleRulesFile, 5 /* maxBytes */);
-        if (rulesBytes.length != 5) {
-            Slog.i(logTag, "Bundle rules version file too short.");
-            return false;
-        }
-        // We don't inspect the rulesVersion and just assume it's valid ASCII containing the IANA
-        // rules version: it should be in the form "20\d\d[a-zA-Z]", e.g. 2016g. If we got this far
-        // (i.e. through a signature check) then it should be ok.
-        String rulesVersion = new String(rulesBytes, StandardCharsets.US_ASCII);
+            File systemTzDataFile, BundleVersion bundleVersion) throws IOException {
 
         // We only check the /system tzdata file and assume that other data like ICU is in sync.
         // There is a CTS test that checks ICU and bionic/libcore are in sync.
         Slog.i(logTag, "Reading /system rules version");
+        String systemRulesVersion = readSystemRulesVersion(systemTzDataFile);
+
+        String bundleRulesVersion = bundleVersion.rulesVersion;
+        // canApply = bundleRulesVersion >= systemRulesVersion
+        boolean canApply = bundleRulesVersion.compareTo(systemRulesVersion) >= 0;
+        if (!canApply) {
+            Slog.i(logTag, "Failed rules version check: bundleRulesVersion="
+                    + bundleRulesVersion + ", systemRulesVersion=" + systemRulesVersion);
+        } else {
+            Slog.i(logTag, "Passed rules version check: bundleRulesVersion="
+                    + bundleRulesVersion + ", systemRulesVersion=" + systemRulesVersion);
+        }
+        return canApply;
+    }
+
+    private String readSystemRulesVersion(File systemTzDataFile) throws IOException {
         if (!systemTzDataFile.exists()) {
             Slog.i(logTag, "tzdata file cannot be found in /system");
-            return false;
+            throw new FileNotFoundException("system tzdata does not exist: " + systemTzDataFile);
         }
-        String systemRulesVersion = ZoneInfoDB.TzData.getRulesVersion(systemTzDataFile);
-        // rulesVersion >= systemRulesVersion
-        return rulesVersion.compareTo(systemRulesVersion) >= 0;
+        return ZoneInfoDB.TzData.getRulesVersion(systemTzDataFile);
     }
 }
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleInstallerTest.java
new file mode 100644
index 0000000..0c7e52b
--- /dev/null
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleInstallerTest.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2015 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.tzdata.update2;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+import libcore.tzdata.testing.ZoneInfoTestHelper;
+import libcore.tzdata.update2.tools.TimeZoneBundleBuilder;
+
+/**
+ * Tests for {@link TimeZoneBundleInstaller}.
+ */
+public class TimeZoneBundleInstallerTest extends TestCase {
+
+    // OLDER_RULES_VERSION < SYSTEM_RULES_VERSION < NEW_RULES_VERSION < NEWER_RULES_VERSION
+    private static final String OLDER_RULES_VERSION = "2030a";
+    private static final String SYSTEM_RULES_VERSION = "2030b";
+    private static final String NEW_RULES_VERSION = "2030c";
+    private static final String NEWER_RULES_VERSION = "2030d";
+
+    private TimeZoneBundleInstaller installer;
+    private File tempDir;
+    private File testInstallDir;
+    private File testSystemTzDataDir;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        tempDir = createDirectory("tempDir");
+        testInstallDir =  createDirectory("testInstall");
+        testSystemTzDataDir =  createDirectory("testSystemTzData");
+
+        // Create a file to represent the tzdata file in the /system partition of the device.
+        File testSystemTzDataFile = new File(testSystemTzDataDir, "tzdata");
+        byte[] systemTzDataBytes = createTzData(SYSTEM_RULES_VERSION);
+        createFile(testSystemTzDataFile, systemTzDataBytes);
+
+        installer = new TimeZoneBundleInstaller(
+                "TimeZoneBundleInstallerTest", testSystemTzDataFile, testInstallDir);
+    }
+
+    private static File createDirectory(String prefix) throws Exception {
+        File dir = File.createTempFile(prefix, "");
+        assertTrue(dir.delete());
+        assertTrue(dir.mkdir());
+        return dir;
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (testSystemTzDataDir.exists()) {
+            FileUtils.deleteRecursive(testInstallDir);
+        }
+        if (testInstallDir.exists()) {
+            FileUtils.deleteRecursive(testInstallDir);
+        }
+        if (tempDir.exists()) {
+            FileUtils.deleteRecursive(tempDir);
+        }
+        super.tearDown();
+    }
+
+    /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
+    public void testInstall_badSystemFile() throws Exception {
+        File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
+        TimeZoneBundleInstaller brokenSystemInstaller = new TimeZoneBundleInstaller(
+                "TimeZoneBundleInstallerTest", doesNotExist, testInstallDir);
+        TimeZoneBundle tzData = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+
+        try {
+            brokenSystemInstaller.install(tzData.getBytes());
+            fail();
+        } catch (IOException expected) {}
+
+        assertNoContentInstalled();
+    }
+
+    /** Tests the first successful update on a device */
+    public void testInstall_successfulFirstUpdate() throws Exception {
+        TimeZoneBundle bundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+
+        assertTrue(installer.install(bundle.getBytes()));
+        assertBundleInstalled(bundle);
+    }
+
+    /**
+     * Tests we can install an update the same version as is in /system.
+     */
+    public void testInstall_successfulFirstUpdate_sameVersionAsSystem() throws Exception {
+        TimeZoneBundle bundle = createValidTimeZoneBundle(SYSTEM_RULES_VERSION, "001");
+        assertTrue(installer.install(bundle.getBytes()));
+        assertBundleInstalled(bundle);
+    }
+
+    /**
+     * Tests we cannot install an update older than the version in /system.
+     */
+    public void testInstall_unsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
+        TimeZoneBundle bundle = createValidTimeZoneBundle(OLDER_RULES_VERSION, "001");
+        assertFalse(installer.install(bundle.getBytes()));
+        assertNoContentInstalled();
+    }
+
+    /**
+     * Tests an update on a device when there is a prior update already applied.
+     */
+    public void testInstall_successfulFollowOnUpdate_newerVersion() throws Exception {
+        TimeZoneBundle bundle1 = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+        assertTrue(installer.install(bundle1.getBytes()));
+        assertBundleInstalled(bundle1);
+
+        TimeZoneBundle bundle2 = createValidTimeZoneBundle(NEW_RULES_VERSION, "002");
+        assertTrue(installer.install(bundle2.getBytes()));
+        assertBundleInstalled(bundle2);
+
+        TimeZoneBundle bundle3 = createValidTimeZoneBundle(NEWER_RULES_VERSION, "001");
+        assertTrue(installer.install(bundle3.getBytes()));
+        assertBundleInstalled(bundle3);
+    }
+
+    /**
+     * Tests an update on a device when there is a prior update already applied, but the follow
+     * on update is older than in /system.
+     */
+    public void testInstall_unsuccessfulFollowOnUpdate_olderVersion() throws Exception {
+        TimeZoneBundle bundle1 = createValidTimeZoneBundle(NEW_RULES_VERSION, "002");
+        assertTrue(installer.install(bundle1.getBytes()));
+        assertBundleInstalled(bundle1);
+
+        TimeZoneBundle bundle2 = createValidTimeZoneBundle(OLDER_RULES_VERSION, "001");
+        assertFalse(installer.install(bundle2.getBytes()));
+        assertBundleInstalled(bundle1);
+    }
+
+    /** Tests that a bundle with a missing file will not update the content. */
+    public void testInstall_missingTzDataFile() throws Exception {
+        TimeZoneBundle installedBundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+        assertTrue(installer.install(installedBundle.getBytes()));
+        assertBundleInstalled(installedBundle);
+
+        TimeZoneBundle incompleteBundle =
+                createValidTimeZoneBundleBuilder(NEWER_RULES_VERSION, "001")
+                        .clearTzDataForTests()
+                        .buildUnvalidated();
+        assertFalse(installer.install(incompleteBundle.getBytes()));
+        assertBundleInstalled(installedBundle);
+    }
+
+    /** Tests that a bundle with a missing file will not update the content. */
+    public void testInstall_missingIcuFile() throws Exception {
+        TimeZoneBundle installedBundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+        assertTrue(installer.install(installedBundle.getBytes()));
+        assertBundleInstalled(installedBundle);
+
+        TimeZoneBundle incompleteBundle =
+                createValidTimeZoneBundleBuilder(NEWER_RULES_VERSION, "001")
+                        .clearIcuDataForTests()
+                        .buildUnvalidated();
+        assertFalse(installer.install(incompleteBundle.getBytes()));
+        assertBundleInstalled(installedBundle);
+    }
+
+    /**
+     * Tests that an update will be unpacked even if there is a partial update from a previous run.
+     */
+    public void testInstall_withWorkingDir() throws Exception {
+        File workingDir = installer.getWorkingDir();
+        assertTrue(workingDir.mkdir());
+        createFile(new File(workingDir, "myFile"), new byte[] { 'a' });
+
+        TimeZoneBundle bundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+        assertTrue(installer.install(bundle.getBytes()));
+        assertBundleInstalled(bundle);
+    }
+
+    /**
+     * Tests that a bundle without a bundle version file will be rejected.
+     */
+    public void testInstall_withMissingBundleVersionFile() throws Exception {
+        // Create a bundle without a version file.
+        TimeZoneBundle bundle = createValidTimeZoneBundleBuilder(NEW_RULES_VERSION, "001")
+                .clearVersionForTests()
+                .buildUnvalidated();
+        assertFalse(installer.install(bundle.getBytes()));
+        assertNoContentInstalled();
+    }
+
+    /**
+     * Tests that a bundle with an newer bundle version will be rejected.
+     */
+    public void testInstall_withNewerBundleVersion() throws Exception {
+        // Create a bundle that will appear to be newer than the one currently supported.
+        TimeZoneBundle bundle = createValidTimeZoneBundleBuilder(NEW_RULES_VERSION, "001")
+                .setBundleVersionForTests("002.001")
+                .buildUnvalidated();
+        assertFalse(installer.install(bundle.getBytes()));
+        assertNoContentInstalled();
+    }
+
+    /**
+     * Tests that a bundle with a badly formed bundle version will be rejected.
+     */
+    public void testInstall_withBadlyFormedBundleVersion() throws Exception {
+        // Create a bundle that has an invalid major bundle version. It should be 3 numeric
+        // characters, "." and 3 more numeric characters.
+        String invalidBundleVersion = "A01.001";
+        byte[] versionBytes =
+                BundleVersion.getBytes(invalidBundleVersion, NEW_RULES_VERSION, "001");
+        TimeZoneBundle bundle = createTimeZoneBundleWithVersionBytes(versionBytes);
+        assertFalse(installer.install(bundle.getBytes()));
+        assertNoContentInstalled();
+    }
+
+    /**
+     * Tests that a bundle with a badly formed android revision will be rejected.
+     */
+    public void testInstall_withBadlyFormedAndroidRevision() throws Exception {
+        // Create a bundle that has an invalid Android revision. It should be 3 numeric characters.
+        String invalidAndroidRevision = "A01";
+        byte[] versionBytes = BundleVersion.getBytes(BundleVersion.FULL_BUNDLE_FORMAT_VERSION,
+                NEW_RULES_VERSION, invalidAndroidRevision);
+        TimeZoneBundle bundle = createTimeZoneBundleWithVersionBytes(versionBytes);
+        assertFalse(installer.install(bundle.getBytes()));
+        assertNoContentInstalled();
+    }
+
+    /**
+     * Tests that a bundle with a badly formed android revision will be rejected.
+     */
+    public void testInstall_withBadlyFormedRulesVersion() throws Exception {
+        // Create a bundle that has an invalid rules version. It should be in the form "2016c".
+        final String invalidRulesVersion = "203Bc";
+        byte[] versionBytes = BundleVersion.getBytes(BundleVersion.FULL_BUNDLE_FORMAT_VERSION,
+                invalidRulesVersion, "001");
+        TimeZoneBundle bundle = createTimeZoneBundleWithVersionBytes(versionBytes);
+        assertFalse(installer.install(bundle.getBytes()));
+        assertNoContentInstalled();
+    }
+
+    public void testUninstall_noExistingDataBundle() throws Exception {
+        assertFalse(installer.uninstall());
+        assertNoContentInstalled();
+    }
+
+    public void testUninstall_existingDataBundle() throws Exception {
+        File currentDataDir = installer.getCurrentTzDataDir();
+        assertTrue(currentDataDir.mkdir());
+
+        assertTrue(installer.uninstall());
+        assertNoContentInstalled();
+    }
+
+    public void testUninstall_oldDirsAlreadyExists() throws Exception {
+        File oldTzDataDir = installer.getOldTzDataDir();
+        assertTrue(oldTzDataDir.mkdir());
+
+        File currentDataDir = installer.getCurrentTzDataDir();
+        assertTrue(currentDataDir.mkdir());
+
+        assertTrue(installer.uninstall());
+        assertNoContentInstalled();
+    }
+
+    public void testGetSystemRulesVersion() throws Exception {
+        assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion());
+    }
+
+    private static TimeZoneBundle createValidTimeZoneBundle(
+            String rulesVersion, String androidRevision) throws Exception {
+        return createValidTimeZoneBundleBuilder(rulesVersion, androidRevision).build();
+    }
+
+    private static TimeZoneBundleBuilder createValidTimeZoneBundleBuilder(
+            String rulesVersion, String androidRevision) throws Exception {
+
+        byte[] bionicTzData = createTzData(rulesVersion);
+        byte[] icuData = new byte[] { 'a' };
+
+        return new TimeZoneBundleBuilder()
+                .setRulesVersion(rulesVersion)
+                .setAndroidRevision(androidRevision)
+                .setTzData(bionicTzData)
+                .setIcuData(icuData);
+    }
+
+    private void assertBundleInstalled(TimeZoneBundle expectedBundle) throws Exception {
+        assertTrue(testInstallDir.exists());
+
+        File currentTzDataDir = installer.getCurrentTzDataDir();
+        assertTrue(currentTzDataDir.exists());
+
+        File bundleVersionFile =
+                new File(currentTzDataDir, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME);
+        assertTrue(bundleVersionFile.exists());
+
+        File bionicFile = new File(currentTzDataDir, TimeZoneBundle.TZDATA_FILE_NAME);
+        assertTrue(bionicFile.exists());
+
+        File icuFile = new File(currentTzDataDir, TimeZoneBundle.ICU_DATA_FILE_NAME);
+        assertTrue(icuFile.exists());
+
+        // Assert getInstalledBundleVersion() is reporting correctly.
+        assertEquals(expectedBundle.getBundleVersion(), installer.getInstalledBundleVersion());
+
+        try (ZipInputStream zis = new ZipInputStream(
+                new ByteArrayInputStream(expectedBundle.getBytes()))) {
+            ZipEntry entry;
+            while ((entry = zis.getNextEntry()) != null) {
+                String entryName = entry.getName();
+                File actualFile;
+                if (entryName.endsWith(TimeZoneBundle.BUNDLE_VERSION_FILE_NAME)) {
+                   actualFile = bundleVersionFile;
+                } else if (entryName.endsWith(TimeZoneBundle.ICU_DATA_FILE_NAME)) {
+                    actualFile = icuFile;
+                } else if (entryName.endsWith(TimeZoneBundle.TZDATA_FILE_NAME)) {
+                    actualFile = bionicFile;
+                } else {
+                    throw new AssertionFailedError("Unknown file found");
+                }
+                assertContentsMatches(zis, actualFile);
+            }
+        }
+
+        // Also check no working directory is left lying around.
+        File workingDir = installer.getWorkingDir();
+        assertFalse(workingDir.exists());
+    }
+
+    private void assertContentsMatches(InputStream expected, File actual)
+            throws Exception {
+        byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath());
+        byte[] expectedBytes = Streams.readFullyNoClose(expected);
+        assertTrue(Arrays.equals(expectedBytes, actualBytes));
+    }
+
+    private void assertNoContentInstalled() throws Exception {
+        assertNull(installer.getInstalledBundleVersion());
+
+        File currentTzDataDir = installer.getCurrentTzDataDir();
+        assertFalse(currentTzDataDir.exists());
+
+        // Also check no working directories are left lying around.
+        File workingDir = installer.getWorkingDir();
+        assertFalse(workingDir.exists());
+
+        File oldDataDir = installer.getOldTzDataDir();
+        assertFalse(oldDataDir.exists());
+    }
+
+    private static byte[] createTzData(String rulesVersion) {
+        return new ZoneInfoTestHelper.TzDataBuilder()
+                .initializeToValid()
+                .setHeaderMagic("tzdata" + rulesVersion)
+                .build();
+    }
+
+    private static void createFile(File file, byte[] bytes) {
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(bytes);
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Creates a TimeZoneBundle containing arbitrary bytes in the version file. Used for testing
+     * bundles with badly formed version info.
+     */
+    private static TimeZoneBundle createTimeZoneBundleWithVersionBytes(byte[] versionBytes)
+            throws Exception {
+
+        // Create a valid bundle, then manipulate the version file.
+        TimeZoneBundle bundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+        byte[] bundleBytes = bundle.getBytes();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(bundleBytes.length);
+        try (ZipInputStream zipInputStream =
+                     new ZipInputStream(new ByteArrayInputStream(bundleBytes));
+             ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
+
+            ZipEntry entry;
+            while ((entry = zipInputStream.getNextEntry()) != null) {
+                zipOutputStream.putNextEntry(entry);
+                if (entry.getName().equals(TimeZoneBundle.BUNDLE_VERSION_FILE_NAME)) {
+                    // Replace the content.
+                    zipOutputStream.write(versionBytes);
+                }  else {
+                    // Just copy the content.
+                    Streams.copy(zipInputStream, zipOutputStream);
+                }
+                zipOutputStream.closeEntry();
+                zipInputStream.closeEntry();
+            }
+        }
+        return new TimeZoneBundle(baos.toByteArray());
+    }
+}
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/ConfigBundleTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleTest.java
similarity index 80%
rename from tzdata/update2/src/test/libcore/tzdata/update2/ConfigBundleTest.java
rename to tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleTest.java
index 509865f..6aaf199 100644
--- a/tzdata/update2/src/test/libcore/tzdata/update2/ConfigBundleTest.java
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleTest.java
@@ -23,6 +23,7 @@
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -31,9 +32,9 @@
 import libcore.io.IoUtils;
 
 /**
- * Tests for {@link ConfigBundle}.
+ * Tests for {@link TimeZoneBundle}.
  */
-public class ConfigBundleTest extends TestCase {
+public class TimeZoneBundleTest extends TestCase {
 
     private final List<File> testFiles = new ArrayList<>();
 
@@ -47,6 +48,19 @@
         super.tearDown();
     }
 
+    public void testGetBundleVersion() throws Exception {
+        BundleVersion bundleVersion =
+                new BundleVersion(BundleVersion.FULL_BUNDLE_FORMAT_VERSION, "2016c", "001");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
+            addZipEntry(zipOutputStream, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME,
+                    bundleVersion.getBytes());
+        }
+
+        TimeZoneBundle bundle = new TimeZoneBundle(baos.toByteArray());
+        assertEquals(bundleVersion, bundle.getBundleVersion());
+    }
+
     public void testExtractZipSafely_goodZip() throws Exception {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
@@ -61,7 +75,7 @@
         File targetDir = new File(dir, "target");
         TestInputStream inputStream =
                 new TestInputStream(new ByteArrayInputStream(baos.toByteArray()));
-        ConfigBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
+        TimeZoneBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
         inputStream.assertClosed();
         assertFilesExist(
                 new File(targetDir, "leadingSlash"),
@@ -95,7 +109,7 @@
         TestInputStream inputStream = new TestInputStream(
                 new ByteArrayInputStream(baos.toByteArray()));
         try {
-            ConfigBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
+            TimeZoneBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
             fail();
         } catch (IOException expected) {
         }
@@ -104,10 +118,15 @@
 
     private static void addZipEntry(ZipOutputStream zipOutputStream, String name)
             throws IOException {
+        addZipEntry(zipOutputStream, name, "a".getBytes(StandardCharsets.US_ASCII));
+    }
+
+    private static void addZipEntry(ZipOutputStream zipOutputStream, String name, byte[] content)
+            throws IOException {
         ZipEntry zipEntry = new ZipEntry(name);
         zipOutputStream.putNextEntry(zipEntry);
         if (!zipEntry.isDirectory()) {
-            zipOutputStream.write('a');
+            zipOutputStream.write(content);
         }
     }
 
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TzDataBundleInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TzDataBundleInstallerTest.java
deleted file mode 100644
index 34afcf1..0000000
--- a/tzdata/update2/src/test/libcore/tzdata/update2/TzDataBundleInstallerTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2015 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.tzdata.update2;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-import libcore.io.Streams;
-import libcore.tzdata.testing.ZoneInfoTestHelper;
-import libcore.tzdata.update2.tools.TzDataBundleBuilder;
-
-/**
- * Tests for {@link TzDataBundleInstaller}.
- */
-public class TzDataBundleInstallerTest extends TestCase {
-
-    // OLDER_RULES_VERSION < SYSTEM_RULES_VERSION < NEW_RULES_VERSION < NEWER_RULES_VERSION
-    private static final String OLDER_RULES_VERSION = "2030a";
-    private static final String SYSTEM_RULES_VERSION = "2030b";
-    private static final String NEW_RULES_VERSION = "2030c";
-    private static final String NEWER_RULES_VERSION = "2030d";
-
-    private TzDataBundleInstaller installer;
-    private File tempDir;
-    private File testInstallDir;
-    private File testSystemTzDataDir;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        tempDir = createDirectory("tempDir");
-        testInstallDir =  createDirectory("testInstall");
-        testSystemTzDataDir =  createDirectory("testSystemTzData");
-
-        // Create a file to represent the tzdata file in the /system partition of the device.
-        File testSystemTzDataFile = new File(testSystemTzDataDir, "tzdata");
-        createTzDataFile(testSystemTzDataFile, SYSTEM_RULES_VERSION);
-
-        installer = new TzDataBundleInstaller(
-                "TzDataBundleInstallerTest", testSystemTzDataFile, testInstallDir);
-    }
-
-    private static File createDirectory(String prefix) throws IOException {
-        File dir = File.createTempFile(prefix, "");
-        assertTrue(dir.delete());
-        assertTrue(dir.mkdir());
-        return dir;
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        if (testSystemTzDataDir.exists()) {
-            FileUtils.deleteRecursive(testInstallDir);
-        }
-        if (testInstallDir.exists()) {
-            FileUtils.deleteRecursive(testInstallDir);
-        }
-        if (tempDir.exists()) {
-            FileUtils.deleteRecursive(tempDir);
-        }
-        super.tearDown();
-    }
-
-    /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
-    public void testBadSystemFile() throws Exception {
-        File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
-        TzDataBundleInstaller brokenSystemInstaller = new TzDataBundleInstaller(
-                "TzDataBundleInstallerTest", doesNotExist, testInstallDir);
-        ConfigBundle tzData = createValidTzDataBundle(NEW_RULES_VERSION);
-
-        assertFalse(brokenSystemInstaller.install(tzData.getBundleBytes()));
-        assertNoContentInstalled();
-    }
-
-    /** Tests the first successful update on a device */
-    public void testSuccessfulFirstUpdate() throws Exception {
-        ConfigBundle tzData = createValidTzDataBundle(NEW_RULES_VERSION);
-
-        assertTrue(installer.install(tzData.getBundleBytes()));
-        assertTzDataInstalled(tzData);
-    }
-
-    /**
-     * Tests we can install an update the same version as is in /system.
-     */
-    public void testSuccessfulFirstUpdate_sameVersionAsSystem() throws Exception {
-        ConfigBundle tzData1 = createValidTzDataBundle(SYSTEM_RULES_VERSION);
-        assertTrue(installer.install(tzData1.getBundleBytes()));
-        assertTzDataInstalled(tzData1);
-    }
-
-    /**
-     * Tests we cannot install an update older than the version in /system.
-     */
-    public void testUnsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
-        ConfigBundle tzData1 = createValidTzDataBundle(OLDER_RULES_VERSION);
-        assertFalse(installer.install(tzData1.getBundleBytes()));
-        assertNoContentInstalled();
-    }
-
-    /**
-     * Tests an update on a device when there is a prior update already applied.
-     */
-    public void testSuccessfulFollowOnUpdate_newerVersion() throws Exception {
-        ConfigBundle tzData1 = createValidTzDataBundle(NEW_RULES_VERSION);
-        assertTrue(installer.install(tzData1.getBundleBytes()));
-        assertTzDataInstalled(tzData1);
-
-        ConfigBundle tzData2 = createValidTzDataBundle(NEWER_RULES_VERSION);
-        assertTrue(installer.install(tzData2.getBundleBytes()));
-        assertTzDataInstalled(tzData2);
-    }
-
-    /**
-     * Tests an update on a device when there is a prior update already applied, but the follow
-     * on update is older than in /system.
-     */
-    public void testUnsuccessfulFollowOnUpdate_olderVersion() throws Exception {
-        ConfigBundle tzData1 = createValidTzDataBundle(NEW_RULES_VERSION);
-        assertTrue(installer.install(tzData1.getBundleBytes()));
-        assertTzDataInstalled(tzData1);
-
-        ConfigBundle tzData2 = createValidTzDataBundle(OLDER_RULES_VERSION);
-        assertFalse(installer.install(tzData2.getBundleBytes()));
-        assertTzDataInstalled(tzData1);
-    }
-
-    /** Tests that a bundle with a missing file will not update the content. */
-    public void testMissingRequiredBundleFile() throws Exception {
-        ConfigBundle installedConfigBundle = createValidTzDataBundle(NEW_RULES_VERSION);
-        assertTrue(installer.install(installedConfigBundle.getBundleBytes()));
-        assertTzDataInstalled(installedConfigBundle);
-
-        ConfigBundle incompleteUpdate =
-                createValidTzDataBundleBuilder(NEWER_RULES_VERSION)
-                        .clearBionicTzData()
-                        .buildUnvalidated();
-        assertFalse(installer.install(incompleteUpdate.getBundleBytes()));
-        assertTzDataInstalled(installedConfigBundle);
-    }
-
-    /**
-     * Tests that an update will be unpacked even if there is a partial update from a previous run.
-     */
-    public void testInstallWithWorkingDir() throws Exception {
-        File workingDir = installer.getWorkingDir();
-        assertTrue(workingDir.mkdir());
-        createFile(new File(workingDir, "myFile"));
-
-        ConfigBundle tzData = createValidTzDataBundle(NEW_RULES_VERSION);
-        assertTrue(installer.install(tzData.getBundleBytes()));
-        assertTzDataInstalled(tzData);
-    }
-
-    /**
-     * Tests that a bundle without a bundle version file will be rejected.
-     */
-    public void testInstallWithMissingBundleVersionFile() throws Exception {
-        File workingDir = installer.getWorkingDir();
-        assertTrue(workingDir.mkdir());
-
-        ConfigBundle tzData = createTzDataBundleWithoutFormatVersionFile(NEW_RULES_VERSION);
-        assertFalse(installer.install(tzData.getBundleBytes()));
-        assertNoContentInstalled();
-    }
-
-    private ConfigBundle createTzDataBundleWithoutFormatVersionFile(String tzDataVersion)
-            throws IOException {
-
-        // Create a valid bundle.
-        ConfigBundle bundle = createValidTzDataBundle(tzDataVersion);
-        byte[] bundleBytes = bundle.getBundleBytes();
-
-        // Remove the version file to make the bundle invalid.
-        ByteArrayOutputStream baos = new ByteArrayOutputStream(bundleBytes.length);
-        try (ZipInputStream zipInputStream =
-                     new ZipInputStream(new ByteArrayInputStream(bundleBytes));
-             ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
-
-            ZipEntry entry;
-            while ((entry = zipInputStream.getNextEntry()) != null) {
-                if (!entry.getName().equals(ConfigBundle.BUNDLE_VERSION_FILE_NAME)) {
-                    zipOutputStream.putNextEntry(entry);
-                    Streams.copy(zipInputStream, zipOutputStream);
-                    zipOutputStream.closeEntry();
-                }
-                zipInputStream.closeEntry();
-            }
-        }
-        return new ConfigBundle(baos.toByteArray());
-    }
-
-    /**
-     * Tests that a bundle with an incorrect bundle version will be rejected.
-     */
-    public void testInstallWithInvalidBundleVersionFile() throws Exception {
-        ConfigBundle tzData = createTzDataBundleWithInvalidBundleVersion(NEW_RULES_VERSION);
-        assertFalse(installer.install(tzData.getBundleBytes()));
-        assertNoContentInstalled();
-    }
-
-    public void testUninstall_noExistingDataBundle() throws Exception {
-        assertFalse(installer.uninstall());
-        assertNoContentInstalled();
-    }
-
-    public void testUninstall_existingDataBundle() throws Exception {
-        File currentDataDir = installer.getCurrentTzDataDir();
-        assertTrue(currentDataDir.mkdir());
-
-        assertTrue(installer.uninstall());
-        assertNoContentInstalled();
-    }
-
-    public void testUninstall_oldDirsAlreadyExists() throws Exception {
-        File oldTzDataDir = installer.getOldTzDataDir();
-        assertTrue(oldTzDataDir.mkdir());
-
-        File currentDataDir = installer.getCurrentTzDataDir();
-        assertTrue(currentDataDir.mkdir());
-
-        assertTrue(installer.uninstall());
-        assertNoContentInstalled();
-    }
-
-    private ConfigBundle createTzDataBundleWithInvalidBundleVersion(String tzDataVersion)
-            throws IOException {
-
-        // Create a valid bundle.
-        ConfigBundle bundle = createValidTzDataBundle(tzDataVersion);
-        byte[] bundleBytes = bundle.getBundleBytes();
-
-        // Modify the bundle version file to be invalid.
-        byte[] badVersionBytes = new byte[ConfigBundle.BUNDLE_VERSION_BYTES.length];
-        System.arraycopy(ConfigBundle.BUNDLE_VERSION_BYTES, 0, badVersionBytes, 0,
-                badVersionBytes.length);
-        badVersionBytes[0]++;
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream(bundleBytes.length);
-        try (ZipInputStream zipInputStream =
-                     new ZipInputStream(new ByteArrayInputStream(bundleBytes));
-             ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
-
-            ZipEntry entry;
-            while ((entry = zipInputStream.getNextEntry()) != null) {
-                zipOutputStream.putNextEntry(entry);
-                if (entry.getName().equals(ConfigBundle.BUNDLE_VERSION_FILE_NAME)) {
-                    zipOutputStream.write(badVersionBytes);
-                } else {
-                    Streams.copy(zipInputStream, zipOutputStream);
-                }
-                zipOutputStream.closeEntry();
-                zipInputStream.closeEntry();
-            }
-        }
-        return new ConfigBundle(baos.toByteArray());
-    }
-
-    private ConfigBundle createValidTzDataBundle(String tzDataVersion)
-            throws IOException {
-        return createValidTzDataBundleBuilder(tzDataVersion).build();
-    }
-
-    private TzDataBundleBuilder createValidTzDataBundleBuilder(String tzDataVersion)
-            throws IOException {
-
-        File bionicTzData = new File(tempDir, "zoneinfo");
-        createTzDataFile(bionicTzData, tzDataVersion);
-
-        File icuData = new File(tempDir, "icudata");
-        createFile(icuData);
-
-        return new TzDataBundleBuilder()
-                .setTzDataVersion(tzDataVersion)
-                .addBionicTzData(bionicTzData)
-                .addIcuTzData(icuData);
-    }
-
-    private void assertTzDataInstalled(ConfigBundle expectedTzData) throws Exception {
-        assertTrue(testInstallDir.exists());
-
-        File currentTzDataDir = installer.getCurrentTzDataDir();
-        assertTrue(currentTzDataDir.exists());
-
-        File bundleVersionFile = new File(currentTzDataDir,
-                ConfigBundle.BUNDLE_VERSION_FILE_NAME);
-        assertTrue(bundleVersionFile.exists());
-
-        File versionFile = new File(currentTzDataDir,
-                ConfigBundle.TZ_DATA_VERSION_FILE_NAME);
-        assertTrue(versionFile.exists());
-
-        File bionicFile = new File(currentTzDataDir, ConfigBundle.ZONEINFO_FILE_NAME);
-        assertTrue(bionicFile.exists());
-
-        File icuFile = new File(currentTzDataDir, ConfigBundle.ICU_DATA_FILE_NAME);
-        assertTrue(icuFile.exists());
-
-        // Also check no working directory is left lying around.
-        File workingDir = installer.getWorkingDir();
-        assertFalse(workingDir.exists());
-    }
-
-    private void assertNoContentInstalled() {
-        File currentTzDataDir = installer.getCurrentTzDataDir();
-        assertFalse(currentTzDataDir.exists());
-
-        // Also check no working directories are left lying around.
-        File workingDir = installer.getWorkingDir();
-        assertFalse(workingDir.exists());
-
-        File oldDataDir = installer.getOldTzDataDir();
-        assertFalse(oldDataDir.exists());
-    }
-
-    private void createTzDataFile(File file, String rulesVersion) {
-        byte[] bytes = new ZoneInfoTestHelper.TzDataBuilder()
-                .initializeToValid()
-                .setHeaderMagic("tzdata" + rulesVersion)
-                .build();
-        try (FileOutputStream fos = new FileOutputStream(file)) {
-            fos.write(bytes);
-        } catch (IOException e) {
-            fail(e.getMessage());
-        }
-
-    }
-
-    private static void createFile(File file) {
-        try (FileOutputStream fos = new FileOutputStream(file)) {
-            fos.write('a');
-        } catch (IOException e) {
-            fail(e.getMessage());
-        }
-    }
-}