Merge "Missing 1.8 javax.net.ssl.SSLSocketFactory.createSocket method"
diff --git a/AndroidTest.xml b/AndroidTest.xml
new file mode 100644
index 0000000..78ed943
--- /dev/null
+++ b/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for libjavacore-benchmarks">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="libjavacore-benchmarks->/data/benchmarktest/libjavacore-benchmarks" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+        <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+        <option name="benchmark-module-name" value="libjavacore-benchmarks" />
+    </test>
+</configuration>
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 758b6ce..97e94f8 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -91,7 +91,7 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 LOCAL_MODULE := core-all
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
 LOCAL_CORE_LIBRARY := true
 LOCAL_UNINSTALLABLE_MODULE := true
 include $(BUILD_JAVA_LIBRARY)
@@ -110,7 +110,7 @@
 LOCAL_MODULE := core-oj
 LOCAL_JAVA_LIBRARIES := core-all
 LOCAL_NOTICE_FILE := $(LOCAL_PATH)/ojluni/NOTICE
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
 LOCAL_CORE_LIBRARY := true
 include $(BUILD_JAVA_LIBRARY)
 
@@ -132,7 +132,7 @@
 endif # EMMA_INSTRUMENT_STATIC
 endif # EMMA_INSTRUMENT
 LOCAL_CORE_LIBRARY := true
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
 include $(BUILD_JAVA_LIBRARY)
 
 # A library that exists to satisfy javac when
@@ -167,7 +167,7 @@
 LOCAL_MODULE := core-oj-testdex
 LOCAL_JAVA_LIBRARIES := core-all
 LOCAL_NOTICE_FILE := $(LOCAL_PATH)/ojluni/NOTICE
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
 LOCAL_CORE_LIBRARY := true
 include $(BUILD_JAVA_LIBRARY)
 
@@ -201,7 +201,7 @@
 LOCAL_MODULE := core-libart-testdex
 LOCAL_JAVA_LIBRARIES := core-all
 LOCAL_CORE_LIBRARY := true
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
 include $(BUILD_JAVA_LIBRARY)
 endif
 
@@ -330,7 +330,7 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 LOCAL_MODULE := core-all-hostdex
-LOCAL_REQUIRED_MODULES := tzdata-host
+LOCAL_REQUIRED_MODULES := tzdata-host tzlookup.xml-host
 LOCAL_CORE_LIBRARY := true
 LOCAL_UNINSTALLABLE_MODULE := true
 include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
@@ -346,7 +346,7 @@
 LOCAL_MODULE := core-oj-hostdex
 LOCAL_NOTICE_FILE := $(LOCAL_PATH)/ojluni/NOTICE
 LOCAL_JAVA_LIBRARIES := core-all-hostdex
-LOCAL_REQUIRED_MODULES := tzdata-host
+LOCAL_REQUIRED_MODULES := tzdata-host tzlookup.xml-host
 LOCAL_CORE_LIBRARY := true
 include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
 
@@ -361,7 +361,7 @@
 LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 LOCAL_MODULE := core-libart-hostdex
 LOCAL_JAVA_LIBRARIES := core-oj-hostdex
-LOCAL_REQUIRED_MODULES := tzdata-host
+LOCAL_REQUIRED_MODULES := tzdata-host tzlookup.xml-host
 include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
 
 # A library that exists to satisfy javac when
diff --git a/NativeCode.mk b/NativeCode.mk
index d8e9d64..9bfb88d 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -204,6 +204,7 @@
 LOCAL_C_INCLUDES += libcore/include
 LOCAL_MODULE_TAGS := debug
 LOCAL_MODULE := libjavacore-benchmarks
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
 LOCAL_SHARED_LIBRARIES := libnativehelper
 LOCAL_CXX_STL := libc++
diff --git a/benchmarks/src/benchmarks/regression/StringReplaceAllBenchmark.java b/benchmarks/src/benchmarks/regression/StringReplaceAllBenchmark.java
new file mode 100644
index 0000000..73e7979
--- /dev/null
+++ b/benchmarks/src/benchmarks/regression/StringReplaceAllBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.Param;
+
+public class StringReplaceAllBenchmark {
+    // NOTE: These estimates of MOVEABLE / NON_MOVEABLE are based on a knowledge of
+    // ART implementation details. They make a difference here because JNI calls related
+    // to strings took different paths depending on whether the String in question was
+    // moveable or not.
+    enum StringLengths {
+        EMPTY(""),
+        MOVEABLE_16(makeString(16)),
+        MOVEABLE_256(makeString(256)),
+        MOVEABLE_1024(makeString(1024)),
+        NON_MOVEABLE(makeString(64 * 1024)),
+        BOOT_IMAGE(java.util.jar.JarFile.MANIFEST_NAME);
+
+        private final String value;
+
+        StringLengths(String s) {
+            this.value = s;
+        }
+    }
+
+    private static final String makeString(int length) {
+        final String sequence8 = "abcdefghijklmnop";
+        final int numAppends = (length / 16) - 1;
+        StringBuilder stringBuilder = new StringBuilder(length);
+
+        // (n-1) occurences of "abcdefghijklmnop"
+        for (int i = 0; i < numAppends; ++i) {
+            stringBuilder.append(sequence8);
+        }
+
+        // and one final occurence of qrstuvwx.
+        stringBuilder.append("qrstuvwx");
+
+        return stringBuilder.toString();
+    }
+
+    @Param private StringLengths s;
+
+    public void timeReplaceAllTrivialPatternNonExistent(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            s.value.replaceAll("fish", "0");
+        }
+    }
+
+    public void timeReplaceTrivialPatternAllRepeated(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            s.value.replaceAll("jklm", "0");
+        }
+    }
+
+    public void timeReplaceAllTrivialPatternSingleOccurence(int reps) {
+        for (int i = 0; i < reps; ++i) {
+            s.value.replaceAll("qrst", "0");
+        }
+    }
+}
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index b9e607a..3693bb2 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -441,8 +441,6 @@
             } else if (file.isDirectory()) {
                 // We support directories for looking up native libraries.
                 elements[elementsPos++] = new NativeLibraryElement(file);
-            } else {
-                System.logW("ClassLoader referenced unknown path: " + file);
             }
         }
         if (elementsPos != elements.length) {
diff --git a/dalvik/src/main/java/dalvik/system/ZygoteHooks.java b/dalvik/src/main/java/dalvik/system/ZygoteHooks.java
index b29b895..c96016f 100644
--- a/dalvik/src/main/java/dalvik/system/ZygoteHooks.java
+++ b/dalvik/src/main/java/dalvik/system/ZygoteHooks.java
@@ -69,7 +69,7 @@
      * {@code postForkChild}.
      */
     public void postForkCommon() {
-        Daemons.start();
+        Daemons.startPostZygoteFork();
     }
 
     private static native long nativePreFork();
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index 1f0ef32..58a4337 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1505,11 +1505,37 @@
   ]
 },
 {
-  description: "Waiting for ICU 58 to be merged",
-  bug: 31516121,
+  description: "Bouncy Castle doesn't prevent reusing initialization vectors",
+  bug: 31801320,
   result: EXEC_FAILED,
   names: [
-    "libcore.java.text.OldBidiTest#testUnicode9EmojisAreLtrNeutral"
+    "com.google.security.wycheproof.AesGcmTest#testIvReuse"
+  ]
+},
+{
+  description: "Conscrypt produces different exceptions under different circumstances",
+  bug: 36772622,
+  result: EXEC_FAILED,
+  names: [
+    "com.google.security.wycheproof.RsaEncryptionTest#testExceptionsPKCS1",
+    "com.google.security.wycheproof.RsaEncryptionTest#testGetExceptionsOAEP"
+  ]
+},
+{
+  description: "Conscrypt fails these tests in 32-bit mode",
+  bug: 36636626,
+  result: EXEC_FAILED,
+  names: [
+    "com.google.security.wycheproof.EcdhTest#testModifiedPublic",
+    "com.google.security.wycheproof.EcdhTest#testModifiedPublicSpec"
+  ]
+},
+{
+  description: "Bullhead kernel does not block send when buffer is supposed to have saturated",
+  bug: 36691333,
+  result: EXEC_FAILED,
+  names: [
+    "libcore.java.net.SocketTimeoutTest#testSocketWriteNeverTimeouts"
   ]
 }
 ]
diff --git a/libart/src/main/java/dalvik/system/ClassExt.java b/libart/src/main/java/dalvik/system/ClassExt.java
index 772bd2e..9c9fa7a 100644
--- a/libart/src/main/java/dalvik/system/ClassExt.java
+++ b/libart/src/main/java/dalvik/system/ClassExt.java
@@ -48,7 +48,8 @@
     private Object obsoleteMethods;
 
     /**
-     * If set, the bytes of the original dex-file associated with the related class.
+     * If set, the bytes, native pointer (as a java.lang.Long), or DexCache of the original dex-file
+     * associated with the related class.
      *
      * In this instance 'original' means either (1) the dex-file loaded for this class when it was
      * first loaded after all non-retransformation capable transformations had been performed but
@@ -59,7 +60,7 @@
      *
      * This field is a logical part of the 'Class' type.
      */
-    private byte[] originalDexFile;
+    private Object originalDexFile;
 
     /**
      * If class verify fails, we must return same error on subsequent tries. We may store either
diff --git a/libart/src/main/java/dalvik/system/VMRuntime.java b/libart/src/main/java/dalvik/system/VMRuntime.java
index 6a673f2..d553846 100644
--- a/libart/src/main/java/dalvik/system/VMRuntime.java
+++ b/libart/src/main/java/dalvik/system/VMRuntime.java
@@ -421,4 +421,9 @@
      * Should be called just once. Subsequent calls are ignored.
      */
     public static native void registerSensitiveThread();
+
+    /**
+     * Sets up the priority of the system daemon thread (caller).
+     */
+    public static native void setSystemDaemonThreadPriority();
 }
diff --git a/libart/src/main/java/java/lang/Daemons.java b/libart/src/main/java/java/lang/Daemons.java
index 15a8ab6..0e2cb45 100644
--- a/libart/src/main/java/java/lang/Daemons.java
+++ b/libart/src/main/java/java/lang/Daemons.java
@@ -46,6 +46,13 @@
         HeapTaskDaemon.INSTANCE.start();
     }
 
+    public static void startPostZygoteFork() {
+        ReferenceQueueDaemon.INSTANCE.startPostZygoteFork();
+        FinalizerDaemon.INSTANCE.startPostZygoteFork();
+        FinalizerWatchdogDaemon.INSTANCE.startPostZygoteFork();
+        HeapTaskDaemon.INSTANCE.startPostZygoteFork();
+    }
+
     public static void stop() {
         HeapTaskDaemon.INSTANCE.stop();
         ReferenceQueueDaemon.INSTANCE.stop();
@@ -61,12 +68,22 @@
     private static abstract class Daemon implements Runnable {
         private Thread thread;
         private String name;
+        private boolean postZygoteFork;
 
         protected Daemon(String name) {
             this.name = name;
         }
 
         public synchronized void start() {
+            startInternal();
+        }
+
+        public synchronized void startPostZygoteFork() {
+            postZygoteFork = true;
+            startInternal();
+        }
+
+        public void startInternal() {
             if (thread != null) {
                 throw new IllegalStateException("already running");
             }
@@ -75,7 +92,18 @@
             thread.start();
         }
 
-        public abstract void run();
+        public void run() {
+            if (postZygoteFork) {
+                // We don't set the priority before the Thread.start() call above because
+                // Thread.start() will call SetNativePriority and overwrite the desired native
+                // priority. We (may) use a native priority that doesn't have a corresponding
+                // java.lang.Thread-level priority (native priorities are more coarse-grained.)
+                VMRuntime.getRuntime().setSystemDaemonThreadPriority();
+            }
+            runInternal();
+        }
+
+        public abstract void runInternal();
 
         /**
          * Returns true while the current thread should continue to run; false
@@ -141,7 +169,7 @@
             super("ReferenceQueueDaemon");
         }
 
-        @Override public void run() {
+        @Override public void runInternal() {
             while (isRunning()) {
                 Reference<?> list;
                 try {
@@ -173,7 +201,7 @@
             super("FinalizerDaemon");
         }
 
-        @Override public void run() {
+        @Override public void runInternal() {
             // This loop may be performance critical, since we need to keep up with mutator
             // generation of finalizable objects.
             // We minimize the amount of work we do per finalizable object. For example, we avoid
@@ -244,7 +272,7 @@
             super("FinalizerWatchdogDaemon");
         }
 
-        @Override public void run() {
+        @Override public void runInternal() {
             while (isRunning()) {
                 if (!sleepUntilNeeded()) {
                     // We have been interrupted, need to see if this daemon has been stopped.
@@ -419,7 +447,7 @@
             VMRuntime.getRuntime().stopHeapTaskProcessor();
         }
 
-        @Override public void run() {
+        @Override public void runInternal() {
             synchronized (this) {
                 if (isRunning()) {
                   // Needs to be synchronized or else we there is a race condition where we start
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index 0c34d5e..7adae8b 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -34,6 +34,7 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
+import java.net.NoRouteToHostException;
 import java.net.PortUnreachableException;
 import java.net.SocketAddress;
 import java.net.SocketException;
@@ -128,6 +129,12 @@
         try {
             connectErrno(fd, inetAddress, port, timeoutMs);
         } catch (ErrnoException errnoException) {
+            if (errnoException.errno == EHOSTUNREACH) {
+                throw new NoRouteToHostException("Host unreachable");
+            }
+            if (errnoException.errno == EADDRNOTAVAIL) {
+                throw new NoRouteToHostException("Address not available");
+            }
             throw new ConnectException(connectDetail(fd, inetAddress, port, timeoutMs,
                     errnoException), errnoException);
         } catch (SocketException ex) {
@@ -141,7 +148,7 @@
 
     private static void connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException {
         // With no timeout, just call connect(2) directly.
-        if (timeoutMs == 0) {
+        if (timeoutMs <= 0) {
             Libcore.os.connect(fd, inetAddress, port);
             return;
         }
diff --git a/luni/src/main/java/libcore/util/TimeZoneDataFiles.java b/luni/src/main/java/libcore/util/TimeZoneDataFiles.java
new file mode 100644
index 0000000..c792665
--- /dev/null
+++ b/luni/src/main/java/libcore/util/TimeZoneDataFiles.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+/**
+ * Utility methods associated with finding updateable time zone data files.
+ */
+public final class TimeZoneDataFiles {
+
+    private static final String ANDROID_ROOT_ENV = "ANDROID_ROOT";
+    private static final String ANDROID_DATA_ENV = "ANDROID_DATA";
+
+    private TimeZoneDataFiles() {}
+
+    /**
+     * Returns two time zone file paths for the specified file name in an array in the order they
+     * should be tried. See {@link #generateIcuDataPath()} for ICU files instead.
+     * <ul>
+     * <li>[0] - the location of the file in the /data partition (may not exist).</li>
+     * <li>[1] - the location of the file in the /system partition (should exist).</li>
+     * </ul>
+     */
+    // VisibleForTesting
+    public static String[] getTimeZoneFilePaths(String fileName) {
+        return new String[] {
+                getDataTimeZoneFile(fileName),
+                getSystemTimeZoneFile(fileName)
+        };
+    }
+
+    private static String getDataTimeZoneFile(String fileName) {
+        return System.getenv(ANDROID_DATA_ENV) + "/misc/zoneinfo/current/" + fileName;
+    }
+
+    // VisibleForTesting
+    public static String getSystemTimeZoneFile(String fileName) {
+        return System.getenv(ANDROID_ROOT_ENV) + "/usr/share/zoneinfo/" + fileName;
+    }
+
+    public static String generateIcuDataPath() {
+        StringBuilder icuDataPathBuilder = new StringBuilder();
+        // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
+        String dataIcuDataPath = getEnvironmentPath(ANDROID_DATA_ENV, "/misc/zoneinfo/current/icu");
+        if (dataIcuDataPath != null) {
+            icuDataPathBuilder.append(dataIcuDataPath);
+        }
+
+        // ICU should always look in ANDROID_ROOT.
+        String systemIcuDataPath = getEnvironmentPath(ANDROID_ROOT_ENV, "/usr/icu");
+        if (systemIcuDataPath != null) {
+            if (icuDataPathBuilder.length() > 0) {
+                icuDataPathBuilder.append(":");
+            }
+            icuDataPathBuilder.append(systemIcuDataPath);
+        }
+        return icuDataPathBuilder.toString();
+    }
+
+    /**
+     * Creates a path by combining the value of an environment variable with a relative path.
+     * Returns {@code null} if the environment variable is not set.
+     */
+    private static String getEnvironmentPath(String environmentVariable, String path) {
+        String variable = System.getenv(environmentVariable);
+        if (variable == null) {
+            return null;
+        }
+        return variable + path;
+    }
+}
diff --git a/luni/src/main/java/libcore/util/TimeZoneFinder.java b/luni/src/main/java/libcore/util/TimeZoneFinder.java
new file mode 100644
index 0000000..4e47df4
--- /dev/null
+++ b/luni/src/main/java/libcore/util/TimeZoneFinder.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.icu.util.TimeZone;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A structure that can find matching time zones.
+ */
+public class TimeZoneFinder {
+
+    private static final String TZLOOKUP_FILE_NAME = "tzlookup.xml";
+    private static final String TIMEZONES_ELEMENT = "timezones";
+    private static final String COUNTRY_ZONES_ELEMENT = "countryzones";
+    private static final String COUNTRY_ELEMENT = "country";
+    private static final String COUNTRY_CODE_ATTRIBUTE = "code";
+    private static final String ID_ELEMENT = "id";
+
+    private static TimeZoneFinder instance;
+
+    private final ReaderSupplier xmlSource;
+
+    // Cached fields for the last country looked up.
+    private String lastCountryIso;
+    private List<TimeZone> lastCountryTimeZones;
+
+    private TimeZoneFinder(ReaderSupplier xmlSource) {
+        this.xmlSource = xmlSource;
+    }
+
+    /**
+     * Obtains an instance for use when resolving time zones. This method handles using the correct
+     * file when there are several to choose from. This method never returns {@code null}. No
+     * in-depth validation is performed on the file content, see {@link #validate()}.
+     */
+    public static TimeZoneFinder getInstance() {
+        synchronized(TimeZoneFinder.class) {
+            if (instance == null) {
+                String[] tzLookupFilePaths =
+                        TimeZoneDataFiles.getTimeZoneFilePaths(TZLOOKUP_FILE_NAME);
+                instance = createInstanceWithFallback(tzLookupFilePaths[0], tzLookupFilePaths[1]);
+            }
+        }
+        return instance;
+    }
+
+    // VisibleForTesting
+    public static TimeZoneFinder createInstanceWithFallback(String... tzLookupFilePaths) {
+        for (String tzLookupFilePath : tzLookupFilePaths) {
+            try {
+                // We assume that any file in /data was validated before install, and the system
+                // file was validated before the device shipped. Therefore, we do not pay the
+                // validation cost here.
+                return createInstance(tzLookupFilePath);
+            } catch (IOException e) {
+                System.logE("Unable to process file: " + tzLookupFilePath + " Trying next one.", e);
+            }
+        }
+
+        System.logE("No valid file found in set: " + Arrays.toString(tzLookupFilePaths)
+                + " Falling back to empty map.");
+        return createInstanceForTests("<timezones><countryzones /></timezones>");
+    }
+
+    /**
+     * Obtains an instance using a specific data file, throwing an IOException if the file does not
+     * exist or is not readable. This method never returns {@code null}. No in-depth validation is
+     * performed on the file content, see {@link #validate()}.
+     */
+    public static TimeZoneFinder createInstance(String path) throws IOException {
+        ReaderSupplier xmlSupplier = ReaderSupplier.forFile(path, StandardCharsets.UTF_8);
+        return new TimeZoneFinder(xmlSupplier);
+    }
+
+    /** Used to create an instance using an in-memory XML String instead of a file. */
+    // VisibleForTesting
+    public static TimeZoneFinder createInstanceForTests(String xml) {
+        return new TimeZoneFinder(ReaderSupplier.forString(xml));
+    }
+
+    /**
+     * Parses the data file, throws an exception if it is invalid or cannot be read.
+     */
+    public void validate() throws IOException {
+        try {
+            processXml(new CountryZonesValidator());
+        } catch (XmlPullParserException e) {
+            throw new IOException("Parsing error", e);
+        }
+    }
+
+    /**
+     * Return a time zone that has / would have had the specified offset and DST value at the
+     * specified moment in the specified country.
+     *
+     * <p>In order to be considered a configured zone must match the supplied offset information.
+     *
+     * <p>Matches are considered in a well-defined order. If multiple zones match and one of them
+     * also matches the (optional) bias parameter then the bias time zone will be returned.
+     * Otherwise the first match found is returned.
+     */
+    public TimeZone lookupTimeZoneByCountryAndOffset(
+            String countryIso, int offsetSeconds, boolean isDst, long whenMillis, TimeZone bias) {
+
+        List<TimeZone> candidates = lookupTimeZonesByCountry(countryIso);
+        if (candidates == null || candidates.isEmpty()) {
+            return null;
+        }
+
+        TimeZone firstMatch = null;
+        for (int i = 0; i < candidates.size(); i++) {
+            TimeZone match = candidates.get(i);
+            if (!offsetMatchesAtTime(match, offsetSeconds, isDst, whenMillis)) {
+                continue;
+            }
+
+            if (firstMatch == null) {
+                if (bias == null) {
+                    // No bias, so we can stop at the first match.
+                    return match;
+                }
+                // We have to carry on checking in case the bias matches. We want to return the
+                // first if it doesn't, though.
+                firstMatch = match;
+            }
+
+            // Check if match is also the bias. There must be a bias otherwise we'd have terminated
+            // already.
+            if (match.getID().equals(bias.getID())) {
+                return match;
+            }
+        }
+        // Return firstMatch, which can be null if there was no match.
+        return firstMatch;
+    }
+
+    /**
+     * Returns {@code true} if the specified offset, DST state and time would be valid in the
+     * timeZone.
+     */
+    private static boolean offsetMatchesAtTime(TimeZone timeZone, int offsetMillis, boolean isDst,
+            long whenMillis) {
+        int[] offsets = new int[2];
+        timeZone.getOffset(whenMillis, false /* local */, offsets);
+
+        // offsets[1] == 0 when the zone is not in DST.
+        boolean zoneIsDst = offsets[1] != 0;
+        if (isDst != zoneIsDst) {
+            return false;
+        }
+        return offsetMillis == (offsets[0] + offsets[1]);
+    }
+
+    /**
+     * Returns a list of time zones known to be used in the specified country. If the country code
+     * is not recognized or there is an error during lookup this can return null. The TimeZones
+     * returned will never contain {@link TimeZone#UNKNOWN_ZONE}. This method can return an empty
+     * list in a case when the underlying configuration references only unknown zone IDs.
+     */
+    public List<TimeZone> lookupTimeZonesByCountry(String countryIso) {
+        synchronized(this) {
+            if (countryIso.equals(lastCountryIso)) {
+                return lastCountryTimeZones;
+            }
+        }
+
+        CountryZonesExtractor extractor = new CountryZonesExtractor(countryIso);
+        List<TimeZone> countryTimeZones = null;
+        try {
+            processXml(extractor);
+            countryTimeZones = extractor.getMatchedZones();
+        } catch (IOException e) {
+            System.logW("Error reading country zones ", e);
+
+            // Clear the cached code so we will try again next time.
+            countryIso = null;
+        } catch (XmlPullParserException e) {
+            System.logW("Error reading country zones ", e);
+            // We want to cache the null. This won't get better over time.
+        }
+
+        synchronized(this) {
+            lastCountryIso = countryIso;
+            lastCountryTimeZones = countryTimeZones;
+        }
+        return countryTimeZones;
+    }
+
+    /**
+     * Processes the XML, applying the {@link CountryZonesProcessor} to the &lt;countryzones&gt;
+     * element. Processing can terminate early if the
+     * {@link CountryZonesProcessor#process(String, List, String)} returns
+     * {@link CountryZonesProcessor#HALT} or it throws an exception.
+     */
+    private void processXml(CountryZonesProcessor processor)
+            throws XmlPullParserException, IOException {
+        try (Reader reader = xmlSource.get()) {
+            XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
+            xmlPullParserFactory.setNamespaceAware(false);
+
+            XmlPullParser parser = xmlPullParserFactory.newPullParser();
+            parser.setInput(reader);
+
+            /*
+             * The expected XML structure is:
+             * <timezones>
+             *   <countryzones>
+             *     <country code="us">
+             *       <id>America/New_York"</id>
+             *       ...
+             *       <id>America/Los_Angeles</id>
+             *     </country>
+             *     <country code="gb">
+             *       <id>Europe/London</id>
+             *     </country>
+             *   </countryzones>
+             * </timezones>
+             */
+
+            findRequiredStartTag(parser, TIMEZONES_ELEMENT);
+
+            // There is only one expected sub-element <countryzones> in the format currently, skip
+            // over anything before it.
+            findRequiredStartTag(parser, COUNTRY_ZONES_ELEMENT);
+
+            if (processCountryZones(parser, processor) == CountryZonesProcessor.HALT) {
+                return;
+            }
+
+            // Make sure we are on the </countryzones> tag.
+            checkOnEndTag(parser, COUNTRY_ZONES_ELEMENT);
+
+            // Advance to the next tag.
+            parser.next();
+
+            // Skip anything until </timezones>, and make sure the file is not truncated and we can
+            // find the end.
+            consumeUntilEndTag(parser, TIMEZONES_ELEMENT);
+
+            // Make sure we are on the </timezones> tag.
+            checkOnEndTag(parser, TIMEZONES_ELEMENT);
+        }
+    }
+
+    private static boolean processCountryZones(XmlPullParser parser,
+            CountryZonesProcessor processor) throws IOException, XmlPullParserException {
+
+        // Skip over any unexpected elements and process <country> elements.
+        while (findOptionalStartTag(parser, COUNTRY_ELEMENT)) {
+            if (processor == null) {
+                consumeUntilEndTag(parser, COUNTRY_ELEMENT);
+            } else {
+                String code = parser.getAttributeValue(
+                        null /* namespace */, COUNTRY_CODE_ATTRIBUTE);
+                if (code == null || code.isEmpty()) {
+                    throw new XmlPullParserException(
+                            "Unable to find country code: " + parser.getPositionDescription());
+                }
+
+                String debugInfo = parser.getPositionDescription();
+                List<String> timeZoneIds = parseZoneIds(parser);
+                if (processor.process(code, timeZoneIds, debugInfo)
+                        == CountryZonesProcessor.HALT) {
+                    return CountryZonesProcessor.HALT;
+                }
+            }
+
+            // Make sure we are on the </country> element.
+            checkOnEndTag(parser, COUNTRY_ELEMENT);
+        }
+
+        return CountryZonesExtractor.CONTINUE;
+    }
+
+    private static List<String> parseZoneIds(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        List<String> timeZones = new ArrayList<>();
+
+        // Skip over any unexpected elements and process <id> elements.
+        while (findOptionalStartTag(parser, ID_ELEMENT)) {
+            String zoneIdString = consumeText(parser);
+
+            // Make sure we are on the </id> element.
+            checkOnEndTag(parser, ID_ELEMENT);
+
+            // Process the zone ID.
+            timeZones.add(zoneIdString);
+        }
+
+        // The list is made unmodifiable to avoid callers changing it.
+        return Collections.unmodifiableList(timeZones);
+    }
+
+    private static void findRequiredStartTag(XmlPullParser parser, String elementName)
+            throws IOException, XmlPullParserException {
+        findStartTag(parser, elementName, true /* elementRequired */);
+    }
+
+    /** Called when on a START_TAG. When returning false, it leaves the parser on the END_TAG. */
+    private static boolean findOptionalStartTag(XmlPullParser parser, String elementName)
+            throws IOException, XmlPullParserException {
+        return findStartTag(parser, elementName, false /* elementRequired */);
+    }
+
+    /**
+     * Find a START_TAG with the specified name without decreasing the depth, or increasing the
+     * depth by more than one. More deeply nested elements and text are skipped, even START_TAGs
+     * with matching names. Returns when the START_TAG is found or the next (non-nested) END_TAG is
+     * encountered. The return can take the form of an exception or a false if the START_TAG is not
+     * found. True is returned when it is.
+     */
+    private static boolean findStartTag(
+            XmlPullParser parser, String elementName, boolean elementRequired)
+            throws IOException, XmlPullParserException {
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            switch (type) {
+                case XmlPullParser.START_TAG:
+                    String currentElementName = parser.getName();
+                    if (elementName.equals(currentElementName)) {
+                        return true;
+                    }
+
+                    // It was not the START_TAG we were looking for. Consume until the end.
+                    parser.next();
+                    consumeUntilEndTag(parser, currentElementName);
+                    break;
+                case XmlPullParser.END_TAG:
+                    if (elementRequired) {
+                        throw new XmlPullParserException(
+                                "No child element found with name " + elementName);
+                    }
+                    return false;
+                default:
+                    // Ignore.
+                    break;
+            }
+        }
+        throw new XmlPullParserException("Unexpected end of document while looking for "
+                + elementName);
+    }
+
+    /**
+     * Consume the remaining contents of an element and move to the END_TAG. Used when processing
+     * within an element can stop. The parser must be pointing at either the END_TAG we are looking
+     * for, a TEXT, or a START_TAG nested within the element to be consumed.
+     */
+    private static void consumeUntilEndTag(XmlPullParser parser, String elementName)
+            throws IOException, XmlPullParserException {
+
+        if (parser.getEventType() == XmlPullParser.END_TAG
+                && elementName.equals(parser.getName())) {
+            // Early return - we are already there.
+            return;
+        }
+
+        // Keep track of the required depth in case there are nested elements to be consumed.
+        // Both the name and the depth must match our expectation to complete.
+
+        int requiredDepth = parser.getDepth();
+        // A TEXT tag would be at the same depth as the END_TAG we are looking for.
+        if (parser.getEventType() == XmlPullParser.START_TAG) {
+            // A START_TAG would have incremented the depth, so we're looking for an END_TAG one
+            // higher than the current tag.
+            requiredDepth--;
+        }
+
+        while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+            int type = parser.next();
+
+            int currentDepth = parser.getDepth();
+            if (currentDepth < requiredDepth) {
+                throw new XmlPullParserException(
+                        "Unexpected depth while looking for end tag: "
+                                + parser.getPositionDescription());
+            } else if (currentDepth == requiredDepth) {
+                if (type == XmlPullParser.END_TAG) {
+                    if (elementName.equals(parser.getName())) {
+                        return;
+                    }
+                    throw new XmlPullParserException(
+                            "Unexpected eng tag: " + parser.getPositionDescription());
+                }
+            }
+            // Everything else is either a type we are not interested in or is too deep and so is
+            // ignored.
+        }
+        throw new XmlPullParserException("Unexpected end of document");
+    }
+
+    /**
+     * Reads the text inside the current element. Should be called when the parser is currently
+     * on the START_TAG before the TEXT. The parser will be positioned on the END_TAG after this
+     * call when it completes successfully.
+     */
+    private static String consumeText(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+
+        int type = parser.next();
+        String text;
+        if (type == XmlPullParser.TEXT) {
+            text = parser.getText();
+        } else {
+            throw new XmlPullParserException("Text not found. Found type=" + type
+                    + " at " + parser.getPositionDescription());
+        }
+
+        type = parser.next();
+        if (type != XmlPullParser.END_TAG) {
+            throw new XmlPullParserException(
+                    "Unexpected nested tag or end of document when expecting text: type=" + type
+                            + " at " + parser.getPositionDescription());
+        }
+        return text;
+    }
+
+    private static void checkOnEndTag(XmlPullParser parser, String elementName)
+            throws XmlPullParserException {
+        if (!(parser.getEventType() == XmlPullParser.END_TAG
+                && parser.getName().equals(elementName))) {
+            throw new XmlPullParserException(
+                    "Unexpected tag encountered: " + parser.getPositionDescription());
+        }
+    }
+
+    /**
+     * Processes &lt;countryzones&gt; data.
+     */
+    private interface CountryZonesProcessor {
+
+        boolean CONTINUE = true;
+        boolean HALT = false;
+
+        /**
+         * Returns {@code #CONTINUE} if processing of the XML should continue, {@code HALT} if it
+         * should stop (but without considering this an error). Problems with parser are reported as
+         * an exception.
+         */
+        boolean process(String countryCode, List<String> timeZoneIds, String debugInfo)
+                throws XmlPullParserException;
+    }
+
+    /**
+     * Validates &lt;countryzones&gt; elements. To be valid the country ISO code must be unique
+     * and it must not be empty.
+     */
+    private static class CountryZonesValidator implements CountryZonesProcessor {
+
+        private final Set<String> knownCountryCodes = new HashSet<>();
+
+        @Override
+        public boolean process(String countryCode, List<String> timeZoneIds, String debugInfo)
+                throws XmlPullParserException {
+            if (knownCountryCodes.contains(countryCode)) {
+                throw new XmlPullParserException("Second entry for country code: " + countryCode
+                        + " at " + debugInfo);
+            }
+            if (timeZoneIds.isEmpty()) {
+                throw new XmlPullParserException("No time zone IDs for country code: " + countryCode
+                        + " at " + debugInfo);
+            }
+
+            // We don't validate the zone IDs - they may be new and we can't easily check them
+            // against other timezone data that may be associated with this file.
+
+            knownCountryCodes.add(countryCode);
+
+            return CONTINUE;
+        }
+    }
+
+    /**
+     * Extracts the zones associated with a country code, halting when the country code is matched
+     * and making them available via {@link #getMatchedZones()}.
+     */
+    private static class CountryZonesExtractor implements CountryZonesProcessor {
+
+        private final String countryCodeToMatch;
+        private List<TimeZone> matchedZones;
+
+        private CountryZonesExtractor(String countryCodeToMatch) {
+            this.countryCodeToMatch = countryCodeToMatch;
+        }
+
+        @Override
+        public boolean process(String countryCode, List<String> timeZoneIds, String debugInfo) {
+            if (!countryCodeToMatch.equals(countryCode)) {
+                return CONTINUE;
+            }
+
+            List<TimeZone> timeZones = new ArrayList<>();
+            for (String zoneIdString : timeZoneIds) {
+                TimeZone tz = TimeZone.getTimeZone(zoneIdString);
+                if (tz.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
+                    System.logW("Skipping invalid zone: " + zoneIdString + " at " + debugInfo);
+                } else {
+                    // The zone is frozen to prevent mutation by callers.
+                    timeZones.add(tz.freeze());
+                }
+            }
+            matchedZones = Collections.unmodifiableList(timeZones);
+            return HALT;
+        }
+
+        /**
+         * Returns the matched zones, or {@code null} if there were no matches. Unknown zone IDs are
+         * ignored so the list can be empty if there were no zones or the zone IDs were not
+         * recognized.
+         */
+        List<TimeZone> getMatchedZones() {
+            return matchedZones;
+        }
+    }
+
+    /**
+     * A source of Readers that can be used repeatedly.
+     */
+    private interface ReaderSupplier {
+        /** Returns a Reader. Throws an IOException if the Reader cannot be created. */
+        Reader get() throws IOException;
+
+        static ReaderSupplier forFile(String fileName, Charset charSet) throws IOException {
+            Path file = Paths.get(fileName);
+            if (!Files.exists(file)) {
+                throw new FileNotFoundException(fileName + " does not exist");
+            }
+            if (!Files.isRegularFile(file) && Files.isReadable(file)) {
+                throw new IOException(fileName + " must be a regular readable file.");
+            }
+            return () -> Files.newBufferedReader(file, charSet);
+        }
+
+        static ReaderSupplier forString(String xml) {
+            return () -> new StringReader(xml);
+        }
+    }
+}
diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java
index d9ff323..acb9c12 100644
--- a/luni/src/main/java/libcore/util/ZoneInfoDB.java
+++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java
@@ -37,9 +37,12 @@
  * @hide - used to implement TimeZone
  */
 public final class ZoneInfoDB {
-  private static final TzData DATA = TzData.loadTzDataWithFallback(
-          System.getenv("ANDROID_DATA") + "/misc/zoneinfo/current/tzdata",
-          System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata");
+
+  // VisibleForTesting
+  public static final String TZDATA_FILE = "tzdata";
+
+  private static final TzData DATA =
+          TzData.loadTzDataWithFallback(TimeZoneDataFiles.getTimeZoneFilePaths(TZDATA_FILE));
 
   public static class TzData {
 
@@ -114,7 +117,7 @@
       // We didn't find any usable tzdata on disk, so let's just hard-code knowledge of "GMT".
       // This is actually implemented in TimeZone itself, so if this is the only time zone
       // we report, we won't be asked any more questions.
-      System.logE("Couldn't find any tzdata!");
+      System.logE("Couldn't find any " + TZDATA_FILE + " file!");
       return TzData.createFallback();
     }
 
@@ -183,7 +186,7 @@
 
         // Something's wrong with the file.
         // Log the problem and return false so we try the next choice.
-        System.logE("tzdata file \"" + path + "\" was present but invalid!", ex);
+        System.logE(TZDATA_FILE + " file \"" + path + "\" was present but invalid!", ex);
         return false;
       }
     }
diff --git a/luni/src/main/native/java_util_regex_Matcher.cpp b/luni/src/main/native/java_util_regex_Matcher.cpp
index 2461afc..2271ba8 100644
--- a/luni/src/main/native/java_util_regex_Matcher.cpp
+++ b/luni/src/main/native/java_util_regex_Matcher.cpp
@@ -16,75 +16,114 @@
 
 #define LOG_TAG "Matcher"
 
+#include <memory>
 #include <stdlib.h>
 
+#include <android-base/logging.h>
+
 #include "IcuUtilities.h"
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
-#include "ScopedPrimitiveArray.h"
 #include "ScopedJavaUnicodeString.h"
+#include "ScopedPrimitiveArray.h"
+#include "ScopedStringChars.h"
 #include "jni.h"
 #include "unicode/parseerr.h"
 #include "unicode/regex.h"
 
 // ICU documentation: http://icu-project.org/apiref/icu4c/classRegexMatcher.html
 
-static icu::RegexMatcher* toRegexMatcher(jlong address) {
-    return reinterpret_cast<icu::RegexMatcher*>(static_cast<uintptr_t>(address));
-}
-
 /**
- * We use ICU4C's RegexMatcher class, but our input is on the Java heap and potentially moving
- * around between calls. This wrapper class ensures that our RegexMatcher is always pointing at
- * the current location of the char[]. Earlier versions of Android simply copied the data to the
- * native heap, but that's wasteful and hides allocations from the garbage collector.
+ * Encapsulates an instance of ICU4C's RegexMatcher class along with a copy of
+ * the input it's currently operating on in the native heap.
+ *
+ * Rationale: We choose to make a copy here because it turns out to be a lot
+ * cheaper when a moving GC and/or string compression is enabled. This is
+ * because env->GetStringChars() always copies in this scenario. This becomes
+ * especially bad when the String in question is long and/or contains a large
+ * number of matches.
+ *
+ * Drawbacks: The native allocation associated with this class is no longer
+ * fixed size, so we're effectively lying to the NativeAllocationRegistry about
+ * the size of the object(s) we're allocating on the native heap. The peak
+ * memory usage doesn't change though, given that GetStringChars would have
+ * made an allocation of precisely the same size.
  */
-class MatcherAccessor {
+class MatcherState {
 public:
-    MatcherAccessor(JNIEnv* env, jlong address, jstring javaInput, bool reset) {
-        init(env, address);
+    MatcherState(icu::RegexMatcher* matcher) :
+        mMatcher(matcher),
+        mUChars(nullptr),
+        mUText(nullptr),
+        mStatus(U_ZERO_ERROR) {
+    }
 
-        mJavaInput = javaInput;
-        mChars = env->GetStringChars(mJavaInput, NULL);
-        if (mChars == NULL) {
-            return;
+    bool updateInput(JNIEnv* env, jstring input) {
+        // First, close the UText struct, since we're about to allocate a new one.
+        if (mUText != nullptr) {
+            utext_close(mUText);
+            mUText = nullptr;
         }
 
-        mUText = utext_openUChars(NULL, mChars, env->GetStringLength(mJavaInput), &mStatus);
-        if (mUText == NULL) {
-            return;
+        // Then delete the UChar* associated with the UText struct..
+        mUChars.reset(nullptr);
+
+        // TODO: We should investigate whether we can avoid an additional copy
+        // in the native heap when is_copy == JNI_TRUE. The problem with doing
+        // that is that we might call ReleaseStringChars with a different
+        // JNIEnv* on a different downcall. This is currently safe as
+        // implemented in ART, but is unlikely to be portable and the spec stays
+        // silent on the matter.
+        ScopedStringChars inputChars(env, input);
+        if (inputChars.get() == nullptr) {
+            // There will be an exception pending if we get here.
+            return false;
         }
 
-        if (reset) {
-            mMatcher->reset(mUText);
-        } else {
-            mMatcher->refreshInputText(mUText, mStatus);
+        // Make a copy of |input| on the native heap. This copy will be live
+        // until the next call to updateInput or close.
+        mUChars.reset(new (std::nothrow) UChar[inputChars.size()]);
+        if (mUChars.get() == nullptr) {
+            env->ThrowNew(env->FindClass("Ljava/lang/OutOfMemoryError;"), "Out of memory");
+            return false;
+        }
+
+        static_assert(sizeof(UChar) == sizeof(jchar), "sizeof(Uchar) != sizeof(jchar)");
+        memcpy(mUChars.get(), inputChars.get(), inputChars.size() * sizeof(jchar));
+
+        // Reset any errors that might have occurred on previous patches.
+        mStatus = U_ZERO_ERROR;
+        mUText = utext_openUChars(nullptr, mUChars.get(), inputChars.size(), &mStatus);
+        if (mUText == nullptr) {
+            CHECK(maybeThrowIcuException(env, "utext_openUChars", mStatus));
+            return false;
+        }
+
+        // It is an error for ICU to have returned a non-null mUText but to
+        // still have indicated an error.
+        CHECK(U_SUCCESS(mStatus));
+
+        mMatcher->reset(mUText);
+        return true;
+    }
+
+    ~MatcherState() {
+        if (mUText != nullptr) {
+            utext_close(mUText);
         }
     }
 
-    MatcherAccessor(JNIEnv* env, jlong address) {
-        init(env, address);
-    }
-
-    ~MatcherAccessor() {
-        utext_close(mUText);
-        if (mJavaInput) {
-            mEnv->ReleaseStringChars(mJavaInput, mChars);
-        }
-        maybeThrowIcuException(mEnv, "utext_close", mStatus);
-    }
-
-    icu::RegexMatcher* operator->() {
-        return mMatcher;
+    icu::RegexMatcher* matcher() {
+        return mMatcher.get();
     }
 
     UErrorCode& status() {
         return mStatus;
     }
 
-    void updateOffsets(jintArray javaOffsets) {
-        ScopedIntArrayRW offsets(mEnv, javaOffsets);
+    void updateOffsets(JNIEnv* env, jintArray javaOffsets) {
+        ScopedIntArrayRW offsets(env, javaOffsets);
         if (offsets.get() == NULL) {
             return;
         }
@@ -96,29 +135,23 @@
     }
 
 private:
-    void init(JNIEnv* env, jlong address) {
-        mEnv = env;
-        mJavaInput = NULL;
-        mMatcher = toRegexMatcher(address);
-        mChars = NULL;
-        mStatus = U_ZERO_ERROR;
-        mUText = NULL;
-    }
-
-    JNIEnv* mEnv;
-    jstring mJavaInput;
-    icu::RegexMatcher* mMatcher;
-    const jchar* mChars;
-    UErrorCode mStatus;
+    std::unique_ptr<icu::RegexMatcher> mMatcher;
+    std::unique_ptr<UChar[]> mUChars;
     UText* mUText;
+    UErrorCode mStatus;
 
     // Disallow copy and assignment.
-    MatcherAccessor(const MatcherAccessor&);
-    void operator=(const MatcherAccessor&);
+    MatcherState(const MatcherState&);
+    void operator=(const MatcherState&);
 };
 
+static inline MatcherState* toMatcherState(jlong address) {
+    return reinterpret_cast<MatcherState*>(static_cast<uintptr_t>(address));
+}
+
 static void Matcher_free(void* address) {
-    delete reinterpret_cast<icu::RegexMatcher*>(address);
+    MatcherState* state = reinterpret_cast<MatcherState*>(address);
+    delete state;
 }
 
 static jlong Matcher_getNativeFinalizer(JNIEnv*, jclass) {
@@ -131,51 +164,48 @@
     return 200;  // Very rough guess based on a quick look at the implementation.
 }
 
-static jint Matcher_findImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint startIndex, jintArray offsets) {
-    MatcherAccessor matcher(env, addr, javaText, false);
-    UBool result = matcher->find(startIndex, matcher.status());
+static jint Matcher_findImpl(JNIEnv* env, jclass, jlong addr, jint startIndex, jintArray offsets) {
+    MatcherState* state = toMatcherState(addr);
+    UBool result = state->matcher()->find(startIndex, state->status());
     if (result) {
-        matcher.updateOffsets(offsets);
+        state->updateOffsets(env, offsets);
     }
     return result;
 }
 
-static jint Matcher_findNextImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
-    MatcherAccessor matcher(env, addr, javaText, false);
-    if (matcher.status() != U_ZERO_ERROR) {
-        return -1;
-    }
-    UBool result = matcher->find();
+static jint Matcher_findNextImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+    MatcherState* state = toMatcherState(addr);
+    UBool result = state->matcher()->find();
     if (result) {
-        matcher.updateOffsets(offsets);
+        state->updateOffsets(env, offsets);
     }
     return result;
 }
 
-static jint Matcher_groupCountImpl(JNIEnv* env, jclass, jlong addr) {
-    MatcherAccessor matcher(env, addr);
-    return matcher->groupCount();
+static jint Matcher_groupCountImpl(JNIEnv*, jclass, jlong addr) {
+    MatcherState* state = toMatcherState(addr);
+    return state->matcher()->groupCount();
 }
 
-static jint Matcher_hitEndImpl(JNIEnv* env, jclass, jlong addr) {
-    MatcherAccessor matcher(env, addr);
-    return matcher->hitEnd();
+static jint Matcher_hitEndImpl(JNIEnv*, jclass, jlong addr) {
+    MatcherState* state = toMatcherState(addr);
+    return state->matcher()->hitEnd();
 }
 
-static jint Matcher_lookingAtImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
-    MatcherAccessor matcher(env, addr, javaText, false);
-    UBool result = matcher->lookingAt(matcher.status());
+static jint Matcher_lookingAtImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+    MatcherState* state = toMatcherState(addr);
+    UBool result = state->matcher()->lookingAt(state->status());
     if (result) {
-        matcher.updateOffsets(offsets);
+        state->updateOffsets(env, offsets);
     }
     return result;
 }
 
-static jint Matcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
-    MatcherAccessor matcher(env, addr, javaText, false);
-    UBool result = matcher->matches(matcher.status());
+static jint Matcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+    MatcherState* state = toMatcherState(addr);
+    UBool result = state->matcher()->matches(state->status());
     if (result) {
-        matcher.updateOffsets(offsets);
+        state->updateOffsets(env, offsets);
     }
     return result;
 }
@@ -184,28 +214,33 @@
     icu::RegexPattern* pattern = reinterpret_cast<icu::RegexPattern*>(static_cast<uintptr_t>(patternAddr));
     UErrorCode status = U_ZERO_ERROR;
     icu::RegexMatcher* result = pattern->matcher(status);
-    maybeThrowIcuException(env, "RegexPattern::matcher", status);
-    return reinterpret_cast<uintptr_t>(result);
+    if (maybeThrowIcuException(env, "RegexPattern::matcher", status)) {
+        return 0;
+    }
+
+    return reinterpret_cast<uintptr_t>(new MatcherState(result));
 }
 
-static jint Matcher_requireEndImpl(JNIEnv* env, jclass, jlong addr) {
-    MatcherAccessor matcher(env, addr);
-    return matcher->requireEnd();
+static jint Matcher_requireEndImpl(JNIEnv*, jclass, jlong addr) {
+    MatcherState* state = toMatcherState(addr);
+    return state->matcher()->requireEnd();
 }
 
 static void Matcher_setInputImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint start, jint end) {
-    MatcherAccessor matcher(env, addr, javaText, true);
-    matcher->region(start, end, matcher.status());
+    MatcherState* state = toMatcherState(addr);
+    if (state->updateInput(env, javaText)) {
+        state->matcher()->region(start, end, state->status());
+    }
 }
 
-static void Matcher_useAnchoringBoundsImpl(JNIEnv* env, jclass, jlong addr, jboolean value) {
-    MatcherAccessor matcher(env, addr);
-    matcher->useAnchoringBounds(value);
+static void Matcher_useAnchoringBoundsImpl(JNIEnv*, jclass, jlong addr, jboolean value) {
+    MatcherState* state = toMatcherState(addr);
+    state->matcher()->useAnchoringBounds(value);
 }
 
-static void Matcher_useTransparentBoundsImpl(JNIEnv* env, jclass, jlong addr, jboolean value) {
-    MatcherAccessor matcher(env, addr);
-    matcher->useTransparentBounds(value);
+static void Matcher_useTransparentBoundsImpl(JNIEnv*, jclass, jlong addr, jboolean value) {
+    MatcherState* state = toMatcherState(addr);
+    state->matcher()->useTransparentBounds(value);
 }
 
 static jint Matcher_getMatchedGroupIndex0(JNIEnv* env, jclass, jlong patternAddr, jstring javaGroupName) {
@@ -227,13 +262,13 @@
 
 static JNINativeMethod gMethods[] = {
     NATIVE_METHOD(Matcher, getMatchedGroupIndex0, "(JLjava/lang/String;)I"),
-    NATIVE_METHOD(Matcher, findImpl, "(JLjava/lang/String;I[I)Z"),
-    NATIVE_METHOD(Matcher, findNextImpl, "(JLjava/lang/String;[I)Z"),
+    NATIVE_METHOD(Matcher, findImpl, "(JI[I)Z"),
+    NATIVE_METHOD(Matcher, findNextImpl, "(J[I)Z"),
     NATIVE_METHOD(Matcher, getNativeFinalizer, "()J"),
     NATIVE_METHOD(Matcher, groupCountImpl, "(J)I"),
     NATIVE_METHOD(Matcher, hitEndImpl, "(J)Z"),
-    NATIVE_METHOD(Matcher, lookingAtImpl, "(JLjava/lang/String;[I)Z"),
-    NATIVE_METHOD(Matcher, matchesImpl, "(JLjava/lang/String;[I)Z"),
+    NATIVE_METHOD(Matcher, lookingAtImpl, "(J[I)Z"),
+    NATIVE_METHOD(Matcher, matchesImpl, "(J[I)Z"),
     NATIVE_METHOD(Matcher, nativeSize, "()I"),
     NATIVE_METHOD(Matcher, openImpl, "(J)J"),
     NATIVE_METHOD(Matcher, requireEndImpl, "(J)Z"),
diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
index d30e7a3..c4f25e0 100644
--- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
@@ -46,10 +46,7 @@
 }
 
 static bool setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const icu::UnicodeString& s) {
-  // Fill in whatever we got. We don't use the display names if they're "GMT[+-]xx:xx"
-  // because icu4c doesn't use the up-to-date time zone transition data, so it gets these
-  // wrong. TimeZone.getDisplayName creates accurate names on demand.
-  // TODO: investigate whether it's worth doing that work once in the Java wrapper instead of on-demand.
+  // Don't use "GMT" string, for backwards compatibility.
   static const icu::UnicodeString kGmt("GMT", 3, US_INV);
   if (!s.isBogus() && !s.startsWith(kGmt)) {
     ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length()));
diff --git a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java
index c199bca..b85f69a 100644
--- a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java
+++ b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java
@@ -264,6 +264,11 @@
                 assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR));
                 assertEquals(lastByte, fis.skip(lastByte));
             }
+
+            FileInputStream fis = new FileInputStream(largeFile);
+            long lastByte = 3 * 1024 * 1024 * 1024L - 1;
+            assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR));
+            assertEquals(lastByte, fis.skip(lastByte));
         } finally {
             // Proactively cleanup - it's a pretty large file.
             assertTrue(largeFile.delete());
diff --git a/luni/src/test/java/libcore/java/lang/SystemTest.java b/luni/src/test/java/libcore/java/lang/SystemTest.java
index 2de659d..48f4591 100644
--- a/luni/src/test/java/libcore/java/lang/SystemTest.java
+++ b/luni/src/test/java/libcore/java/lang/SystemTest.java
@@ -16,17 +16,16 @@
 
 package libcore.java.lang;
 
+import junit.framework.TestCase;
+
 import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.lang.SecurityException;
-import java.lang.SecurityManager;
 import java.util.Formatter;
 import java.util.Properties;
 import java.util.concurrent.atomic.AtomicBoolean;
-import junit.framework.TestCase;
 
 public class SystemTest extends TestCase {
 
@@ -250,14 +249,4 @@
         } catch (SecurityException expected) {
         }
     }
-
-    // http://b/34867424
-    public void testIcuPathIncludesTimeZoneOverride() {
-        String icuDataPath = System.getProperty("android.icu.impl.ICUBinary.dataPath");
-        String[] paths = icuDataPath.split(":");
-        assertEquals(2, paths.length);
-
-        assertTrue(paths[0].contains("/misc/zoneinfo/current/icu"));
-        assertTrue(paths[1].contains("/usr/icu"));
-    }
 }
diff --git a/luni/src/test/java/libcore/java/net/URLTest.java b/luni/src/test/java/libcore/java/net/URLTest.java
index 3a14063..231e09c 100644
--- a/luni/src/test/java/libcore/java/net/URLTest.java
+++ b/luni/src/test/java/libcore/java/net/URLTest.java
@@ -194,6 +194,14 @@
         assertEquals(null, url.getRef());
     }
 
+    // This behavior of URLs with invalid user info changed due to bug http://b/33351987 after
+    // Android security level 1 April 2017.
+    public void testAtSignInUserInfo() throws Exception {
+        URL url = new URL("http://user@userhost.com:password@host");
+        assertNull(url.getUserInfo());
+        assertTrue(url.getHost().isEmpty());
+    }
+
     public void testUserNoPassword() throws Exception {
         URL url = new URL("http://user@host");
         assertEquals("user@host", url.getAuthority());
@@ -755,6 +763,28 @@
         assertEquals("/some/path", new URL("http://foobar.com/some/path?#").getFile());
     }
 
+    // http://b/31858037
+    public void testFragmentWithSlash() throws Exception {
+        final String host = "example.com";
+        final String fragment = "@not-a-host-name/a";
+        URL url = new URL(String.format("http://%s#%s", host, fragment));
+        assertNull(url.getUserInfo());
+        assertEquals(host, url.getAuthority());
+        assertEquals(host, url.getHost());
+        assertEquals(fragment, url.getRef());
+    }
+
+    // http://b/31858037
+    public void testFragmentWithQuery() throws Exception {
+        final String host = "example.com";
+        final String fragment = "@not-a-host-name?a";
+        URL url = new URL(String.format("http://%s#%s", host, fragment));
+        assertNull(url.getUserInfo());
+        assertEquals(host, url.getAuthority());
+        assertEquals(host, url.getHost());
+        assertEquals(fragment, url.getRef());
+    }
+
     // http://b/33351987
     public void testMultipleUserField() throws Exception {
         final String host = "http://multiple@users@url.com";
diff --git a/luni/src/test/java/libcore/java/nio/file/DefaultFileStoreTest.java b/luni/src/test/java/libcore/java/nio/file/DefaultFileStoreTest.java
deleted file mode 100644
index 763efbf..0000000
--- a/luni/src/test/java/libcore/java/nio/file/DefaultFileStoreTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.java.nio.file;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.DosFileAttributeView;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.UserDefinedFileAttributeView;
-
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertNull;
-import static junit.framework.TestCase.assertTrue;
-import static libcore.java.nio.file.FilesSetup.execCmdAndWaitForTermination;
-import static libcore.java.nio.file.FilesSetup.readFromInputStream;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-public class DefaultFileStoreTest {
-
-    @Rule
-    public FilesSetup filesSetup = new FilesSetup();
-
-    @Test
-    public void test_name() throws IOException, InterruptedException {
-        Path path = filesSetup.getPathInTestDir("dir");
-        Files.createDirectory(path);
-        Process p = execCmdAndWaitForTermination("df", path.toAbsolutePath().toString());
-        String shellOutput = readFromInputStream(p.getInputStream()).split("\n")[1];
-        String storeTypeFromShell = shellOutput.split("\\s")[0];
-        assertEquals(storeTypeFromShell, Files.getFileStore(path).name());
-    }
-
-    @Test
-    public void test_type() throws IOException, InterruptedException {
-        Path path = filesSetup.getPathInTestDir("dir");
-        Files.createDirectory(path);
-        String fileStore = Files.getFileStore(path).name();
-        Process p = execCmdAndWaitForTermination("mount");
-        String mountOutput[] = readFromInputStream(p.getInputStream()).split("\n");
-        for (String mountInfo : mountOutput) {
-            if (mountInfo.contains(fileStore)
-                    && mountInfo.contains(Files.getFileStore(path).type())) {
-                return;
-            }
-        }
-        fail();
-    }
-
-    @Test
-    public void test_isReadOnly() throws IOException {
-        Path path = Paths.get("/system");
-        assertTrue(Files.getFileStore(path).isReadOnly());
-
-        path = Paths.get("/data");
-        assertFalse(Files.getFileStore(path).isReadOnly());
-    }
-
-    @Test
-    public void test_getTotalSpace() throws IOException {
-        Path path = Paths.get("/data");
-        assertTrue(Files.getFileStore(path).getTotalSpace() > 0);
-    }
-
-    @Test
-    public void test_getUsableSpace() throws IOException {
-        Path path = Paths.get("/data");
-        long usableSpace = Files.getFileStore(path).getUsableSpace();
-        long totalSpace = Files.getFileStore(path).getTotalSpace();
-        assertTrue(usableSpace <= totalSpace && usableSpace > 0);
-    }
-
-    @Test
-    public void test_getUnallocatedSpace() throws IOException {
-        Path path = Paths.get("/data");
-        long unallocatedSpace = Files.getFileStore(path).getUnallocatedSpace();
-        assertTrue(unallocatedSpace >= 0);
-    }
-
-    @Test
-    public void test_supportsFileAttributeView$Class() throws IOException {
-        Path path = filesSetup.getPathInTestDir("dir");
-        Files.createDirectories(path);
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView(
-                BasicFileAttributeView.class));
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView(
-                FileOwnerAttributeView.class));
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView(
-                PosixFileAttributeView.class));
-        assertFalse(Files.getFileStore(path).supportsFileAttributeView(DosFileAttributeView.class));
-        assertFalse(Files.getFileStore(path).
-                supportsFileAttributeView(UserDefinedFileAttributeView.class));
-        assertFalse(Files.getFileStore(path).
-                supportsFileAttributeView(NonStandardFileAttributeView.class));
-    }
-
-    @Test
-    public void test_supportsFileAttributeView$String() throws IOException {
-        Path path = filesSetup.getPathInTestDir("dir");
-        Files.createDirectories(path);
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView("basic"));
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView("unix"));
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView("posix"));
-        assertTrue(Files.getFileStore(path).supportsFileAttributeView("owner"));
-        assertFalse(Files.getFileStore(path).supportsFileAttributeView("user"));
-        assertFalse(Files.getFileStore(path).supportsFileAttributeView("dos"));
-        assertFalse(Files.getFileStore(path).supportsFileAttributeView("nonStandardView"));
-    }
-
-    @Test
-    public void test_getFileStoreAttributeView() throws IOException {
-        Path path = filesSetup.getPathInTestDir("dir");
-        Files.createDirectories(path);
-        assertNull(Files.getFileStore(path).getFileStoreAttributeView(
-                FileStoreAttributeView.class));
-        try {
-            Files.getFileStore(path).getFileStoreAttributeView(null);
-            fail();
-        } catch (NullPointerException expected) {}
-    }
-
-    @Test
-    public void test_getAttribute() throws IOException {
-        Path p = filesSetup.getPathInTestDir("dir");
-        Files.createDirectories(p);
-        FileStore store = Files.getFileStore(p);
-        assertEquals(store.getTotalSpace(), store.getAttribute("totalSpace"));
-        assertEquals(store.getUnallocatedSpace(), store.getAttribute("unallocatedSpace"));
-        assertEquals(store.getUsableSpace(), store.getAttribute("usableSpace"));
-        try {
-            store.getAttribute("test");
-            fail();
-        } catch (UnsupportedOperationException expected) {}
-    }
-
-    private static class NonStandardFileAttributeView implements FileAttributeView {
-        @Override
-        public String name() {
-            return null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java b/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java
index dbbfeae..2916b03 100644
--- a/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java
+++ b/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java
@@ -263,24 +263,35 @@
 
     @Test
     public void test_getFileStore() throws IOException {
-        FileStore fileStore = provider.getFileStore(filesSetup.getDataFilePath());
-        assertNotNull(fileStore);
-    }
+        try {
+            provider.getFileStore(filesSetup.getDataFilePath());
+            fail();
+        } catch (SecurityException expected) {
+        }
 
-    @Test
-    public void test_getFileStore_NPE() throws IOException {
         try {
             provider.getFileStore(null);
             fail();
-        } catch(NullPointerException expected) {}
+        } catch (SecurityException expected) {
+        }
     }
 
     @Test
     public void test_isHidden() throws IOException {
         assertFalse(provider.isHidden(filesSetup.getDataFilePath()));
-        Files.setAttribute(filesSetup.getDataFilePath(), "dos:hidden", true);
 
-        // Files can't be hid.
+        // Files can't be hidden using the "dos" view, which is unsupported since it relies
+        // on a custom xattr, which may or may not be available on all FSs.
+        //
+        // Note that this weirdly asymmetric : setting the hidden attribute uses xattrs to
+        // emulate dos attributes whereas isHidden checks whether the the file name begins with a
+        // leading period. <shrug>
+        try {
+            Files.setAttribute(filesSetup.getDataFilePath(), "dos:hidden", true);
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+
         assertFalse(provider.isHidden(filesSetup.getDataFilePath()));
     }
 
diff --git a/luni/src/test/java/libcore/java/nio/file/Files2Test.java b/luni/src/test/java/libcore/java/nio/file/Files2Test.java
index 394643a..436d3e3 100644
--- a/luni/src/test/java/libcore/java/nio/file/Files2Test.java
+++ b/luni/src/test/java/libcore/java/nio/file/Files2Test.java
@@ -138,9 +138,12 @@
 
     @Test
     public void test_getFileStore() throws IOException {
-        FileStore mockFileStore = mock(FileStore.class);
-        when(mockFileSystemProvider.getFileStore(mockPath)).thenReturn(mockFileStore);
-        assertEquals(mockFileStore, Files.getFileStore(mockPath));
+        when(mockFileSystemProvider.getFileStore(mockPath)).thenThrow(new SecurityException());
+        try {
+            Files.getFileStore(mockPath);
+            fail();
+        } catch (SecurityException expected) {
+        }
     }
 
     @Test
diff --git a/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java
index 26afa1d..34c6aab 100644
--- a/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java
@@ -124,7 +124,7 @@
         // It is expected that the symbols may change with future CLDR updates.
 
         dfs = new DecimalFormatSymbols(Locale.forLanguageTag("ar"));
-        assertEquals('%', dfs.getPercent());
+        assertEquals('Ùª', dfs.getPercent());
         assertEquals('-', dfs.getMinusSign());
 
         dfs = new DecimalFormatSymbols(Locale.forLanguageTag("fa"));
@@ -186,4 +186,26 @@
         compareDfs(dfs, icuSymb);
     }
 
+    // http://b/36562145
+    public void testMaybeStripMarkers() {
+        final char ltr = '\u200E';
+        final char rtl = '\u200F';
+        final char alm = '\u061C';
+        final char fallback = 'F';
+        assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("", fallback));
+        assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("XY", fallback));
+        assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("" + ltr, fallback));
+        assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("" + rtl, fallback));
+        assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("" + alm, fallback));
+        assertEquals(fallback,
+                DecimalFormatSymbols.maybeStripMarkers("X" + ltr + rtl + alm + "Y", fallback));
+        assertEquals(fallback,
+                DecimalFormatSymbols.maybeStripMarkers("" + ltr + rtl + alm, fallback));
+        assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers(alm + "XY" + rtl, fallback));
+        assertEquals('X', DecimalFormatSymbols.maybeStripMarkers("X", fallback));
+        assertEquals('X', DecimalFormatSymbols.maybeStripMarkers("X" + ltr, fallback));
+        assertEquals('X', DecimalFormatSymbols.maybeStripMarkers("X" + rtl, fallback));
+        assertEquals('X', DecimalFormatSymbols.maybeStripMarkers(alm + "X", fallback));
+        assertEquals('X', DecimalFormatSymbols.maybeStripMarkers(alm + "X" + rtl, fallback));
+    }
 }
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 6b692cf..375eb36 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -216,15 +216,26 @@
 
     // http://b/7955614 and http://b/8026776.
     public void testDisplayNames() throws Exception {
+        checkDisplayNames(Locale.US);
+    }
+
+    public void testDisplayNames_nonUS() throws Exception {
+        // run checkDisplayNames with an arbitrary set of Locales.
+        checkDisplayNames(Locale.CHINESE);
+        checkDisplayNames(Locale.FRENCH);
+        checkDisplayNames(Locale.forLanguageTag("bn-BD"));
+    }
+
+    public void checkDisplayNames(Locale locale) throws Exception {
         // Check that there are no time zones that use DST but have the same display name for
         // both standard and daylight time.
         StringBuilder failures = new StringBuilder();
         for (String id : TimeZone.getAvailableIDs()) {
             TimeZone tz = TimeZone.getTimeZone(id);
-            String longDst = tz.getDisplayName(true, TimeZone.LONG, Locale.US);
-            String longStd = tz.getDisplayName(false, TimeZone.LONG, Locale.US);
-            String shortDst = tz.getDisplayName(true, TimeZone.SHORT, Locale.US);
-            String shortStd = tz.getDisplayName(false, TimeZone.SHORT, Locale.US);
+            String longDst = tz.getDisplayName(true, TimeZone.LONG, locale);
+            String longStd = tz.getDisplayName(false, TimeZone.LONG, locale);
+            String shortDst = tz.getDisplayName(true, TimeZone.SHORT, locale);
+            String shortStd = tz.getDisplayName(false, TimeZone.SHORT, locale);
 
             if (tz.useDaylightTime()) {
                 // The long std and dst strings must differ!
@@ -329,15 +340,6 @@
         return String.format("GMT%c%02d:%02d", sign, offset / 60, offset % 60);
     }
 
-    public void testAllDisplayNames() throws Exception {
-      for (Locale locale : Locale.getAvailableLocales()) {
-        for (String id : TimeZone.getAvailableIDs()) {
-          TimeZone tz = TimeZone.getTimeZone(id);
-          assertNotNull(tz.getDisplayName(false, TimeZone.LONG, locale));
-        }
-      }
-    }
-
     // http://b/18839557
     public void testOverflowing32BitUnixDates() {
         final TimeZone tz = TimeZone.getTimeZone("America/New_York");
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java b/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java
new file mode 100644
index 0000000..dd9a9a0
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.javax.crypto;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.AEADBadTagException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+public final class CipherOutputStreamTest extends TestCase {
+
+    // From b/36636576. CipherOutputStream had a bug where it would ignore exceptions
+    // thrown during close().
+    public void testDecryptCorruptGCM() throws Exception {
+        for (Provider provider : Security.getProviders()) {
+            Cipher cipher;
+            try {
+                cipher = Cipher.getInstance("AES/GCM/NoPadding", provider);
+            } catch (NoSuchAlgorithmException e) {
+                continue;
+            }
+            SecretKey key;
+            if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) {
+                key = getAndroidKeyStoreSecretKey();
+            } else {
+                KeyGenerator keygen = KeyGenerator.getInstance("AES");
+                keygen.init(256);
+                key = keygen.generateKey();
+            }
+            GCMParameterSpec params = new GCMParameterSpec(128, new byte[12]);
+            byte[] unencrypted = new byte[200];
+
+            // Normal providers require specifying the IV, but KeyStore prohibits it, so
+            // we have to special-case it
+            if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) {
+                cipher.init(Cipher.ENCRYPT_MODE, key);
+            } else {
+                cipher.init(Cipher.ENCRYPT_MODE, key, params);
+            }
+            byte[] encrypted = cipher.doFinal(unencrypted);
+
+            // Corrupt the final byte, which will corrupt the authentication tag
+            encrypted[encrypted.length - 1] ^= 1;
+
+            cipher.init(Cipher.DECRYPT_MODE, key, params);
+            CipherOutputStream cos = new CipherOutputStream(new ByteArrayOutputStream(), cipher);
+            try {
+                cos.write(encrypted);
+                cos.close();
+                fail("Writing a corrupted stream should throw an exception."
+                        + "  Provider: " + provider);
+            } catch (IOException expected) {
+                assertTrue(expected.getCause() instanceof AEADBadTagException);
+            }
+        }
+
+    }
+
+    // The AndroidKeyStoreBCWorkaround provider can't use keys created by anything
+    // but Android KeyStore, which requires using its own parameters class to create
+    // keys.  Since we're in javax, we can't link against the frameworks classes, so
+    // we have to use reflection to make a suitable key.  This will always be safe
+    // because if we're making a key for AndroidKeyStoreBCWorkaround, the KeyStore
+    // classes must be present.
+    private static SecretKey getAndroidKeyStoreSecretKey() throws Exception {
+        KeyGenerator keygen = KeyGenerator.getInstance("AES", "AndroidKeyStore");
+        Class<?> keyParamsBuilderClass = keygen.getClass().getClassLoader().loadClass(
+                "android.security.keystore.KeyGenParameterSpec$Builder");
+        Object keyParamsBuilder = keyParamsBuilderClass.getConstructor(String.class, Integer.TYPE)
+                // 3 is PURPOSE_ENCRYPT | PURPOSE_DECRYPT
+                .newInstance("testDecryptCorruptGCM", 3);
+        keyParamsBuilderClass.getMethod("setBlockModes", new Class[]{String[].class})
+                .invoke(keyParamsBuilder, new Object[]{new String[]{"GCM"}});
+        keyParamsBuilderClass.getMethod("setEncryptionPaddings", new Class[]{String[].class})
+                .invoke(keyParamsBuilder, new Object[]{new String[]{"NoPadding"}});
+        AlgorithmParameterSpec spec = (AlgorithmParameterSpec)
+                keyParamsBuilderClass.getMethod("build", new Class[]{}).invoke(keyParamsBuilder);
+        keygen.init(spec);
+        return keygen.generateKey();
+    }
+}
diff --git a/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java b/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java
new file mode 100644
index 0000000..efba900
--- /dev/null
+++ b/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TimeZoneDataFilesTest {
+
+    @Test
+    public void getTimeZoneFilePaths() {
+        String[] paths = TimeZoneDataFiles.getTimeZoneFilePaths("foo");
+        assertEquals(2, paths.length);
+
+        assertTrue(paths[0].contains("/misc/zoneinfo/current/"));
+        assertTrue(paths[0].endsWith("foo"));
+
+        assertTrue(paths[1].contains("/usr/share/zoneinfo/"));
+        assertTrue(paths[1].endsWith("foo"));
+    }
+
+    // http://b/34867424
+    @Test
+    public void generateIcuDataPath_includesTimeZoneOverride() {
+        String icuDataPath = System.getProperty("android.icu.impl.ICUBinary.dataPath");
+        assertEquals(icuDataPath, TimeZoneDataFiles.generateIcuDataPath());
+
+        String[] paths = icuDataPath.split(":");
+        assertEquals(2, paths.length);
+
+        assertTrue(paths[0].contains("/misc/zoneinfo/current/icu"));
+        assertTrue(paths[1].contains("/usr/icu"));
+    }
+}
diff --git a/luni/src/test/java/libcore/util/TimeZoneFinderTest.java b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
new file mode 100644
index 0000000..0b31c9a
--- /dev/null
+++ b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.icu.util.TimeZone;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class TimeZoneFinderTest {
+
+    private static final int HOUR_MILLIS = 60 * 60 * 1000;
+
+    // Zones used in the tests. NEW_YORK_TZ and LONDON_TZ chosen because they never overlap but both
+    // have DST.
+    private static final TimeZone NEW_YORK_TZ = TimeZone.getTimeZone("America/New_York");
+    private static final TimeZone LONDON_TZ = TimeZone.getTimeZone("Europe/London");
+    // A zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for WHEN_DST.
+    private static final TimeZone REYKJAVIK_TZ = TimeZone.getTimeZone("Atlantic/Reykjavik");
+    // Another zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for
+    // WHEN_DST.
+    private static final TimeZone UTC_TZ = TimeZone.getTimeZone("Etc/UTC");
+
+    // 22nd July 2017, 13:14:15 UTC (DST time in all the timezones used in these tests that observe
+    // DST).
+    private static final long WHEN_DST = 1500729255000L;
+    // 22nd January 2018, 13:14:15 UTC (non-DST time in all timezones used in these tests).
+    private static final long WHEN_NO_DST = 1516626855000L;
+
+    private static final int LONDON_DST_OFFSET_MILLIS = HOUR_MILLIS;
+    private static final int LONDON_NO_DST_OFFSET_MILLIS = 0;
+
+    private static final int NEW_YORK_DST_OFFSET_MILLIS = -4 * HOUR_MILLIS;
+    private static final int NEW_YORK_NO_DST_OFFSET_MILLIS = -5 * HOUR_MILLIS;
+
+    private Path testDir;
+
+    @Before
+    public void setUp() throws Exception {
+        testDir = Files.createTempDirectory("TimeZoneFinderTest");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Delete the testDir and all contents.
+        Files.walkFileTree(testDir, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+                    throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+                    throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
+    @Test
+    public void createInstanceWithFallback() throws Exception {
+        String validXml1 = "<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n";
+        String validXml2 = "<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/Paris</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n";
+
+        String invalidXml = "<foo></foo>\n";
+        checkValidateThrowsParserException(invalidXml);
+
+        String validFile1 = createFile(validXml1);
+        String validFile2 = createFile(validXml2);
+        String invalidFile = createFile(invalidXml);
+        String missingFile = createMissingFile();
+
+        TimeZoneFinder file1ThenFile2 =
+                TimeZoneFinder.createInstanceWithFallback(validFile1, validFile2);
+        assertZonesEqual(zones("Europe/London"), file1ThenFile2.lookupTimeZonesByCountry("gb"));
+
+        TimeZoneFinder missingFileThenFile1 =
+                TimeZoneFinder.createInstanceWithFallback(missingFile, validFile1);
+        assertZonesEqual(zones("Europe/London"),
+                missingFileThenFile1.lookupTimeZonesByCountry("gb"));
+
+        TimeZoneFinder file2ThenFile1 =
+                TimeZoneFinder.createInstanceWithFallback(validFile2, validFile1);
+        assertZonesEqual(zones("Europe/Paris"), file2ThenFile1.lookupTimeZonesByCountry("gb"));
+
+        // We assume the file has been validated so an invalid file is not checked ahead of time.
+        // We will find out when we look something up.
+        TimeZoneFinder invalidThenValid =
+                TimeZoneFinder.createInstanceWithFallback(invalidFile, validFile1);
+        assertNull(invalidThenValid.lookupTimeZonesByCountry("gb"));
+
+        // This is not a normal case: It would imply a define shipped without a file in /system!
+        TimeZoneFinder missingFiles =
+                TimeZoneFinder.createInstanceWithFallback(missingFile, missingFile);
+        assertNull(missingFiles.lookupTimeZonesByCountry("gb"));
+    }
+
+    @Test
+    public void xmlParsing_emptyFile() throws Exception {
+        checkValidateThrowsParserException("");
+    }
+
+    @Test
+    public void xmlParsing_unexpectedRootElement() throws Exception {
+        checkValidateThrowsParserException("<foo></foo>\n");
+    }
+
+    @Test
+    public void xmlParsing_missingCountryZones() throws Exception {
+        checkValidateThrowsParserException("<timezones></timezones>\n");
+    }
+
+    @Test
+    public void xmlParsing_noCountriesOk() throws Exception {
+        validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+    }
+
+    @Test
+    public void xmlParsing_unexpectedComments() throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <!-- This is a comment -->"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        // This is a crazy comment, but also helps prove that TEXT nodes are coalesced by the
+        // parser.
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/<!-- Don't freak out! -->London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+    }
+
+    @Test
+    public void xmlParsing_unexpectedElementsIgnored() throws Exception {
+        String unexpectedElement = "<unexpected-element>\n<a /></unexpected-element>\n";
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  " + unexpectedElement
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    " + unexpectedElement
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      " + unexpectedElement
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "      " + unexpectedElement
+                + "      <id>Europe/Paris</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London", "Europe/Paris"),
+                finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "    " + unexpectedElement
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        // This test is important because it ensures we can extend the format in future with
+        // more information.
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "  " + unexpectedElement
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+    }
+
+    @Test
+    public void xmlParsing_unexpectedTextIgnored() throws Exception {
+        String unexpectedText = "unexpected-text";
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  " + unexpectedText
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    " + unexpectedText
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      " + unexpectedText
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+        finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "      " + unexpectedText
+                + "      <id>Europe/Paris</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London", "Europe/Paris"),
+                finder.lookupTimeZonesByCountry("gb"));
+    }
+
+    @Test
+    public void xmlParsing_truncatedInput() throws Exception {
+        checkValidateThrowsParserException("<timezones>\n");
+
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n");
+
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n");
+
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n");
+
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n");
+
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n");
+    }
+
+    @Test
+    public void xmlParsing_unexpectedChildInTimeZoneIdThrows() throws Exception {
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id><unexpected-element /></id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+    }
+
+    @Test
+    public void xmlParsing_unknownTimeZoneIdIgnored() throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Unknown_Id</id>\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+    }
+
+    @Test
+    public void xmlParsing_missingCountryCode() throws Exception {
+        checkValidateThrowsParserException("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country>\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+    }
+
+    @Test
+    public void xmlParsing_unknownCountryReturnsNull() throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertNull(finder.lookupTimeZonesByCountry("gb"));
+    }
+
+    @Test
+    public void lookupTimeZonesByCountry_structuresAreImmutable() throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        List<TimeZone> gbList = finder.lookupTimeZonesByCountry("gb");
+        assertEquals(1, gbList.size());
+        assertImmutableList(gbList);
+        assertImmutableTimeZone(gbList.get(0));
+
+        assertNull(finder.lookupTimeZonesByCountry("unknown"));
+    }
+
+    @Test
+    public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"xx\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        // Demonstrate the arguments work for a known country.
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                        true /* isDst */, WHEN_DST, null /* bias */));
+
+        // Test with an unknown country.
+        String unknownCountryCode = "yy";
+        assertNull(finder.lookupTimeZoneByCountryAndOffset(unknownCountryCode,
+                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
+
+        assertNull(finder.lookupTimeZoneByCountryAndOffset(unknownCountryCode,
+                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
+    }
+
+    @Test
+    public void lookupTimeZoneByCountryAndOffset_oneCandidate() throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"xx\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        // The three parameters match the configured zone: offset, isDst and when.
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                        true /* isDst */, WHEN_DST, null /* bias */));
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
+                        false /* isDst */, WHEN_NO_DST, null /* bias */));
+
+        // Some lookup failure cases where the offset, isDst and when do not match the configured
+        // zone.
+        TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch1);
+
+        TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch2);
+
+        TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch3);
+
+        TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch4);
+
+        TimeZone noDstMatch5 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch5);
+
+        TimeZone noDstMatch6 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch6);
+
+        // Some bias cases below.
+
+        // The bias is irrelevant here: it matches what would be returned anyway.
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                        true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
+                        false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+        // A sample of a non-matching case with bias.
+        assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+
+        // The bias should be ignored: it doesn't match any of the country's zones.
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                        true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
+
+        // The bias should still be ignored even though it matches the offset information given:
+        // it doesn't match any of the country's configured zones.
+        assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", NEW_YORK_DST_OFFSET_MILLIS,
+                true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
+    }
+
+    @Test
+    public void lookupTimeZoneByCountryAndOffset_multipleNonOverlappingCandidates()
+            throws Exception {
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"xx\">\n"
+                + "      <id>America/New_York</id>\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        // The three parameters match the configured zone: offset, isDst and when.
+        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
+        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
+        assertZoneEquals(NEW_YORK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+                NEW_YORK_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
+        assertZoneEquals(NEW_YORK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+                NEW_YORK_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
+
+        // Some lookup failure cases where the offset, isDst and when do not match the configured
+        // zone. This is a sample, not complete.
+        TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch1);
+
+        TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch2);
+
+        TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch3);
+
+        TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch4);
+
+        TimeZone noDstMatch5 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch5);
+
+        TimeZone noDstMatch6 = finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch6);
+
+        // Some bias cases below.
+
+        // The bias is irrelevant here: it matches what would be returned anyway.
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                        true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
+                        false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+        // A sample of a non-matching case with bias.
+        assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+
+        // The bias should be ignored: it matches a configured zone, but the offset is wrong so
+        // should not be considered a match.
+        assertZoneEquals(LONDON_TZ,
+                finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+                        true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
+    }
+
+    // This is an artificial case very similar to America/Denver and America/Phoenix in the US: both
+    // have the same offset for 6 months of the year but diverge. Australia/Lord_Howe too.
+    @Test
+    public void lookupTimeZoneByCountryAndOffset_multipleOverlappingCandidates() throws Exception {
+        // Three zones that have the same offset for some of the year. Europe/London changes
+        // offset WHEN_DST, the others do not.
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"xx\">\n"
+                + "      <id>Atlantic/Reykjavik</id>\n"
+                + "      <id>Europe/London</id>\n"
+                + "      <id>Etc/UTC</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        // This is the no-DST offset for LONDON_TZ, REYKJAVIK_TZ. UTC_TZ.
+        final int noDstOffset = LONDON_NO_DST_OFFSET_MILLIS;
+        // This is the DST offset for LONDON_TZ.
+        final int dstOffset = LONDON_DST_OFFSET_MILLIS;
+
+        // The three parameters match the configured zone: offset, isDst and when.
+        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+                true /* isDst */, WHEN_DST, null /* bias */));
+        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                false /* isDst */, WHEN_NO_DST, null /* bias */));
+        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+                true /* isDst */, WHEN_DST, null /* bias */));
+        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                false /* isDst */, WHEN_NO_DST, null /* bias */));
+        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                false /* isDst */, WHEN_DST, null /* bias */));
+
+        // Some lookup failure cases where the offset, isDst and when do not match the configured
+        // zones.
+        TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+                true /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch1);
+
+        TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                true /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch2);
+
+        TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                true /* isDst */, WHEN_NO_DST, null /* bias */);
+        assertNull(noDstMatch3);
+
+        TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+                false /* isDst */, WHEN_DST, null /* bias */);
+        assertNull(noDstMatch4);
+
+
+        // Some bias cases below.
+
+        // The bias is relevant here: it overrides what would be returned naturally.
+        assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                false /* isDst */, WHEN_NO_DST, null /* bias */));
+        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+        assertZoneEquals(UTC_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+                false /* isDst */, WHEN_NO_DST, UTC_TZ /* bias */));
+
+        // The bias should be ignored: it matches a configured zone, but the offset is wrong so
+        // should not be considered a match.
+        assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+                LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, REYKJAVIK_TZ /* bias */));
+    }
+
+    @Test
+    public void consistencyTest() throws Exception {
+        // Confirm that no new zones have been added to zones.tab without also adding them to the
+        // configuration used to drive TimeZoneFinder.
+
+        // zone.tab is a tab separated ASCII file provided by IANA and included in Android's tzdata
+        // file. Each line contains a mapping from country code -> zone ID. The ordering used by
+        // TimeZoneFinder is Android-specific, but we can use zone.tab to make sure we know about
+        // all country zones. Any update to tzdata that adds, renames, or removes zones should be
+        // reflected in the file used by TimeZoneFinder.
+        Map<String, Set<String>> zoneTabMappings = new HashMap<>();
+        for (String line : ZoneInfoDB.getInstance().getZoneTab().split("\n")) {
+            int countryCodeEnd = line.indexOf('\t', 1);
+            int olsonIdStart = line.indexOf('\t', 4) + 1;
+            int olsonIdEnd = line.indexOf('\t', olsonIdStart);
+            if (olsonIdEnd == -1) {
+                olsonIdEnd = line.length(); // Not all zone.tab lines have a comment.
+            }
+            String countryCode = line.substring(0, countryCodeEnd);
+            String olsonId = line.substring(olsonIdStart, olsonIdEnd);
+            Set<String> zoneIds = zoneTabMappings.get(countryCode);
+            if (zoneIds == null) {
+                zoneIds = new HashSet<>();
+                zoneTabMappings.put(countryCode, zoneIds);
+            }
+            zoneIds.add(olsonId);
+        }
+
+        TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
+        for (Map.Entry<String, Set<String>> countryEntry : zoneTabMappings.entrySet()) {
+            String countryCode = countryEntry.getKey();
+            // Android uses lower case, IANA uses upper.
+            countryCode = countryCode.toLowerCase();
+
+            List<String> ianaZoneIds = countryEntry.getValue().stream().sorted()
+                    .collect(Collectors.toList());
+            List<TimeZone> androidZones = timeZoneFinder.lookupTimeZonesByCountry(countryCode);
+            List<String> androidZoneIds =
+                    androidZones.stream().map(TimeZone::getID).sorted()
+                            .collect(Collectors.toList());
+
+            assertEquals("Android zones for " + countryCode + " do not match IANA data",
+                    ianaZoneIds, androidZoneIds);
+        }
+    }
+
+    private void assertImmutableTimeZone(TimeZone timeZone) {
+        try {
+            timeZone.setRawOffset(1000);
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    private static void assertImmutableList(List<TimeZone> timeZones) {
+        try {
+            timeZones.add(null);
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    private static void assertZoneEquals(TimeZone expected, TimeZone actual) {
+        // TimeZone.equals() only checks the ID, but that's ok for these tests.
+        assertEquals(expected, actual);
+    }
+
+    private static void assertZonesEqual(List<TimeZone> expected, List<TimeZone> actual) {
+        // TimeZone.equals() only checks the ID, but that's ok for these tests.
+        assertEquals(expected, actual);
+    }
+
+    private static void checkValidateThrowsParserException(String xml) throws Exception {
+        try {
+            validate(xml);
+            fail();
+        } catch (IOException expected) {
+        }
+    }
+
+    private static TimeZoneFinder validate(String xml) throws IOException {
+        TimeZoneFinder timeZoneFinder = TimeZoneFinder.createInstanceForTests(xml);
+        timeZoneFinder.validate();
+        return timeZoneFinder;
+    }
+
+    private static List<TimeZone> zones(String... ids) {
+        return Arrays.stream(ids).map(TimeZone::getTimeZone).collect(Collectors.toList());
+    }
+
+    private String createFile(String fileContent) throws IOException {
+        Path filePath = Files.createTempFile(testDir, null, null);
+        Files.write(filePath, fileContent.getBytes(StandardCharsets.UTF_8));
+        return filePath.toString();
+    }
+
+    private String createMissingFile() throws IOException {
+        Path filePath = Files.createTempFile(testDir, null, null);
+        Files.delete(filePath);
+        return filePath.toString();
+    }
+}
diff --git a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
index 901519e..e6ec4df 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
@@ -28,27 +28,27 @@
 public class ZoneInfoDBTest extends junit.framework.TestCase {
 
   // The base tzdata file, always present on a device.
-  private static final String TZDATA_IN_ROOT =
-      System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata";
+  private static final String SYSTEM_TZDATA_FILE =
+      TimeZoneDataFiles.getSystemTimeZoneFile(ZoneInfoDB.TZDATA_FILE);
 
   // An empty override file should fall back to the default file.
   public void testLoadTzDataWithFallback_emptyOverrideFile() throws Exception {
-    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     String emptyFilePath = makeEmptyFile().getPath();
 
     ZoneInfoDB.TzData dataWithEmptyOverride =
-        ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, TZDATA_IN_ROOT);
+        ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, SYSTEM_TZDATA_FILE);
     assertEquals(data.getVersion(), dataWithEmptyOverride.getVersion());
     assertEquals(data.getAvailableIDs().length, dataWithEmptyOverride.getAvailableIDs().length);
   }
 
   // A corrupt override file should fall back to the default file.
   public void testLoadTzDataWithFallback_corruptOverrideFile() throws Exception {
-    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     String corruptFilePath = makeCorruptFile().getPath();
 
     ZoneInfoDB.TzData dataWithCorruptOverride =
-        ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, TZDATA_IN_ROOT);
+        ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, SYSTEM_TZDATA_FILE);
     assertEquals(data.getVersion(), dataWithCorruptOverride.getVersion());
     assertEquals(data.getAvailableIDs().length, dataWithCorruptOverride.getAvailableIDs().length);
   }
@@ -64,7 +64,7 @@
 
   // Given a valid override file, we should find ourselves using that.
   public void testLoadTzDataWithFallback_goodOverrideFile() throws Exception {
-    RandomAccessFile in = new RandomAccessFile(TZDATA_IN_ROOT, "r");
+    RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
     byte[] content = new byte[(int) in.length()];
     in.readFully(content);
     in.close();
@@ -79,9 +79,9 @@
     File goodFile = makeTemporaryFile(content);
     try {
       ZoneInfoDB.TzData dataWithOverride =
-              ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), TZDATA_IN_ROOT);
+              ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), SYSTEM_TZDATA_FILE);
       assertEquals("9999z", dataWithOverride.getVersion());
-      ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+      ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
       assertEquals(data.getAvailableIDs().length, dataWithOverride.getAvailableIDs().length);
     } finally {
       goodFile.delete();
@@ -89,7 +89,7 @@
   }
 
   public void testLoadTzData_badHeader() throws Exception {
-    RandomAccessFile in = new RandomAccessFile(TZDATA_IN_ROOT, "r");
+    RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
     byte[] content = new byte[(int) in.length()];
     in.readFully(content);
     in.close();
@@ -212,7 +212,7 @@
 
   // Confirms any caching that exists correctly handles TimeZone mutability.
   public void testMakeTimeZone_timeZoneMutability() throws Exception {
-    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     String tzId = "Europe/London";
     ZoneInfo first = data.makeTimeZone(tzId);
     ZoneInfo second = data.makeTimeZone(tzId);
@@ -229,21 +229,21 @@
   }
 
   public void testMakeTimeZone_notFound() throws Exception {
-    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     assertNull(data.makeTimeZone("THIS_TZ_DOES_NOT_EXIST"));
     assertFalse(data.hasTimeZone("THIS_TZ_DOES_NOT_EXIST"));
   }
 
   public void testMakeTimeZone_found() throws Exception {
-    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     assertNotNull(data.makeTimeZone("Europe/London"));
     assertTrue(data.hasTimeZone("Europe/London"));
   }
 
   public void testGetRulesVersion() throws Exception {
-    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+    ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
 
-    String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(TZDATA_IN_ROOT));
+    String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(SYSTEM_TZDATA_FILE));
     assertEquals(data.getVersion(), rulesVersion);
   }
 
diff --git a/non_openjdk_java_files.mk b/non_openjdk_java_files.mk
index fd1353f..9d33f46 100644
--- a/non_openjdk_java_files.mk
+++ b/non_openjdk_java_files.mk
@@ -307,6 +307,8 @@
   luni/src/main/java/libcore/util/Objects.java \
   luni/src/main/java/libcore/util/RecoverySystem.java \
   luni/src/main/java/libcore/util/SneakyThrow.java \
+  luni/src/main/java/libcore/util/TimeZoneDataFiles.java \
+  luni/src/main/java/libcore/util/TimeZoneFinder.java \
   luni/src/main/java/libcore/util/ZoneInfo.java \
   luni/src/main/java/libcore/util/ZoneInfoDB.java \
   luni/src/main/java/libcore/util/HexEncoding.java \
diff --git a/ojluni/src/main/java/java/beans/ChangeListenerMap.java b/ojluni/src/main/java/java/beans/ChangeListenerMap.java
index fead0a4..fa8be47 100644
--- a/ojluni/src/main/java/java/beans/ChangeListenerMap.java
+++ b/ojluni/src/main/java/java/beans/ChangeListenerMap.java
@@ -76,7 +76,7 @@
      */
     public final synchronized void add(String name, L listener) {
         if (this.map == null) {
-            this.map = new HashMap<String, L[]>();
+            this.map = new HashMap<>();
         }
         L[] array = this.map.get(name);
         int size = (array != null)
@@ -146,7 +146,7 @@
     public final void set(String name, L[] listeners) {
         if (listeners != null) {
             if (this.map == null) {
-                this.map = new HashMap<String, L[]>();
+                this.map = new HashMap<>();
             }
             this.map.put(name, listeners);
         }
@@ -167,7 +167,7 @@
         if (this.map == null) {
             return newArray(0);
         }
-        List<L> list = new ArrayList<L>();
+        List<L> list = new ArrayList<>();
 
         L[] listeners = this.map.get(null);
         if (listeners != null) {
diff --git a/ojluni/src/main/java/java/lang/System.java b/ojluni/src/main/java/java/lang/System.java
index 4797c1b..6e8f74f 100644
--- a/ojluni/src/main/java/java/lang/System.java
+++ b/ojluni/src/main/java/java/lang/System.java
@@ -41,6 +41,8 @@
 import java.util.PropertyPermission;
 import libcore.icu.ICU;
 import libcore.io.Libcore;
+import libcore.util.TimeZoneDataFiles;
+
 import sun.reflect.CallerSensitive;
 import sun.security.util.SecurityConstants;
 /**
@@ -996,7 +998,7 @@
         // is prioritized over the properties in ICUConfig.properties. The issue with using
         // that is that it doesn't play well with jarjar and it needs complicated build rules
         // to change its default value.
-        String icuDataPath = generateIcuDataPath();
+        String icuDataPath = TimeZoneDataFiles.generateIcuDataPath();
         p.put("android.icu.impl.ICUBinary.dataPath", icuDataPath);
 
         parsePropertyAssignments(p, specialProperties());
@@ -1023,37 +1025,6 @@
         return p;
     }
 
-    private static String generateIcuDataPath() {
-        StringBuilder icuDataPathBuilder = new StringBuilder();
-        // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
-        String dataIcuDataPath = getEnvironmentPath("ANDROID_DATA", "/misc/zoneinfo/current/icu");
-        if (dataIcuDataPath != null) {
-            icuDataPathBuilder.append(dataIcuDataPath);
-        }
-
-        // ICU should always look in ANDROID_ROOT.
-        String systemIcuDataPath = getEnvironmentPath("ANDROID_ROOT", "/usr/icu");
-        if (systemIcuDataPath != null) {
-            if (icuDataPathBuilder.length() > 0) {
-                icuDataPathBuilder.append(":");
-            }
-            icuDataPathBuilder.append(systemIcuDataPath);
-        }
-        return icuDataPathBuilder.toString();
-    }
-
-    /**
-     * Creates a path by combining the value of an environment variable with a relative path.
-     * Returns {@code null} if the environment variable is not set.
-     */
-    private static String getEnvironmentPath(String environmentVariable, String path) {
-        String variable = getenv(environmentVariable);
-        if (variable == null) {
-            return null;
-        }
-        return variable + path;
-    }
-
     private static Properties initProperties() {
         Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
         setDefaultChangeableProperties(p);
diff --git a/ojluni/src/main/java/java/lang/reflect/Array.java b/ojluni/src/main/java/java/lang/reflect/Array.java
index 8285be7..790a930 100644
--- a/ojluni/src/main/java/java/lang/reflect/Array.java
+++ b/ojluni/src/main/java/java/lang/reflect/Array.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -57,6 +57,8 @@
      * Array.newInstance(componentType, x);
      * </pre>
      * </blockquote>
+     * <p>The number of dimensions of the new array must not
+     * exceed 255.
      *
      * @param componentType the {@code Class} object representing the
      * component type of the new array
@@ -64,7 +66,9 @@
      * @return the new array
      * @exception NullPointerException if the specified
      * {@code componentType} parameter is null
-     * @exception IllegalArgumentException if componentType is {@link Void#TYPE}
+     * @exception IllegalArgumentException if componentType is {@link
+     * Void#TYPE} or if the number of dimensions of the requested array
+     * instance exceed 255.
      * @exception NegativeArraySizeException if the specified {@code length}
      * is negative
      */
@@ -88,8 +92,7 @@
      * {@code componentType}.
      *
      * <p>The number of dimensions of the new array must not
-     * exceed the number of array dimensions supported by the
-     * implementation (typically 255).
+     * exceed 255.
      *
      * @param componentType the {@code Class} object representing the component
      * type of the new array
@@ -99,10 +102,9 @@
      * @exception NullPointerException if the specified
      * {@code componentType} argument is null
      * @exception IllegalArgumentException if the specified {@code dimensions}
-     * argument is a zero-dimensional array, or if the number of
-     * requested dimensions exceeds the limit on the number of array dimensions
-     * supported by the implementation (typically 255), or if componentType
-     * is {@link Void#TYPE}.
+     * argument is a zero-dimensional array, if componentType is {@link
+     * Void#TYPE}, or if the number of dimensions of the requested array
+     * instance exceed 255.
      * @exception NegativeArraySizeException if any of the components in
      * the specified {@code dimensions} argument is negative.
      */
diff --git a/ojluni/src/main/java/java/lang/reflect/Constructor.java b/ojluni/src/main/java/java/lang/reflect/Constructor.java
index 3054e5f..de7176b 100644
--- a/ojluni/src/main/java/java/lang/reflect/Constructor.java
+++ b/ojluni/src/main/java/java/lang/reflect/Constructor.java
@@ -139,6 +139,7 @@
 
     /**
      * {@inheritDoc}
+     * @since 1.8
      */
     public int getParameterCount() {
         // Android-changed: This is handled by Executable.
diff --git a/ojluni/src/main/java/java/lang/reflect/Method.java b/ojluni/src/main/java/java/lang/reflect/Method.java
index 55d741c..ddda93d 100644
--- a/ojluni/src/main/java/java/lang/reflect/Method.java
+++ b/ojluni/src/main/java/java/lang/reflect/Method.java
@@ -186,6 +186,7 @@
 
     /**
      * {@inheritDoc}
+     * @since 1.8
      */
     public int getParameterCount() {
         // Android-changed: This is handled by Executable.
diff --git a/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java b/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
index b8e8e63..96a2d21 100644
--- a/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
+++ b/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java b/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java
index 18465ba..ad62c58 100644
--- a/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java
+++ b/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007,2011, 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
@@ -35,7 +35,7 @@
  */
 
 class DefaultDatagramSocketImplFactory {
-    static Class prefixImplClass = null;
+    static Class<?> prefixImplClass = null;
 
     static {
         String prefix = null;
@@ -53,10 +53,10 @@
     }
 
     /**
-     * Creates a new {@code DatagramSocketImpl} instance.
+     * Creates a new <code>DatagramSocketImpl</code> instance.
      *
      * @param   isMulticast     true if this impl if for a MutlicastSocket
-     * @return  a new instance of a {@code DatagramSocketImpl}.
+     * @return  a new instance of a <code>DatagramSocketImpl</code>.
      */
     static DatagramSocketImpl createDatagramSocketImpl(boolean isMulticast /*unused on unix*/)
         throws SocketException {
diff --git a/ojluni/src/main/java/java/net/DefaultInterface.java b/ojluni/src/main/java/java/net/DefaultInterface.java
index 9f4dfe1..0c31cb6 100644
--- a/ojluni/src/main/java/java/net/DefaultInterface.java
+++ b/ojluni/src/main/java/java/net/DefaultInterface.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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,7 +26,7 @@
 package java.net;
 
 /**
- * Choose a network inteface to be the default for
+ * Choose a network interface to be the default for
  * outgoing IPv6 traffic that does not specify a scope_id (and which needs one).
  *
  * Platforms that do not require a default interface may return null
diff --git a/ojluni/src/main/java/java/net/PlainSocketImpl.java b/ojluni/src/main/java/java/net/PlainSocketImpl.java
index 7177098..f02006d 100644
--- a/ojluni/src/main/java/java/net/PlainSocketImpl.java
+++ b/ojluni/src/main/java/java/net/PlainSocketImpl.java
@@ -25,15 +25,34 @@
  */
 package java.net;
 
+import android.system.ErrnoException;
+
 import java.io.IOException;
 import java.io.FileDescriptor;
 import java.util.Set;
 import java.util.HashSet;
 import java.util.Collections;
+import libcore.io.AsynchronousCloseMonitor;
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
 import jdk.net.*;
 
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.EAGAIN;
+import static android.system.OsConstants.EBADF;
+import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.MSG_OOB;
+import static android.system.OsConstants.POLLERR;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SHUT_RDWR;
 import static sun.net.ExtendedOptionsImpl.*;
 
+// Android-changed: Rewritten to use android.system POSIX calls and assume AF_INET6.
 /*
  * On Unix systems we simply delegate to native methods.
  *
@@ -91,29 +110,180 @@
         }
     }
 
-    native void socketCreate(boolean isServer) throws IOException;
+    void socketCreate(boolean isStream) throws IOException {
+        // The fd object must not change after calling bind, because we rely on this undocumented
+        // behaviour. See libcore.java.net.SocketTest#testFileDescriptorStaysSame.
+        fd.setInt$(IoBridge.socket(AF_INET6, isStream ? SOCK_STREAM : SOCK_DGRAM, 0).getInt$());
 
-    native void socketConnect(InetAddress address, int port, int timeout)
-        throws IOException;
+        if (serverSocket != null) {
+            IoUtils.setBlocking(fd, false);
+            IoBridge.setSocketOption(fd, SO_REUSEADDR, true);
+        }
+    }
 
-    native void socketBind(InetAddress address, int port)
-        throws IOException;
+    void socketConnect(InetAddress address, int port, int timeout) throws IOException {
+        if (fd == null || !fd.valid()) {
+            throw new SocketException("Socket closed");
+        }
 
-    native void socketListen(int count) throws IOException;
+        IoBridge.connect(fd, address, port, timeout);
 
-    native void socketAccept(SocketImpl s) throws IOException;
+        this.address = address;
+        this.port = port;
 
-    native int socketAvailable() throws IOException;
+        if (localport == 0) {
+            // If socket is pending close, fd becomes an AF_UNIX socket and calling
+            // getLocalInetSocketAddress will fail.
+            // http://b/34645743
+            if (!isClosedOrPending()) {
+                localport = IoBridge.getLocalInetSocketAddress(fd).getPort();
+            }
+        }
+    }
 
-    native void socketClose0(boolean useDeferredClose) throws IOException;
+    void socketBind(InetAddress address, int port) throws IOException {
+        if (fd == null || !fd.valid()) {
+            throw new SocketException("Socket closed");
+        }
 
-    native void socketShutdown(int howto) throws IOException;
+        IoBridge.bind(fd, address, port);
+
+        this.address = address;
+        if (port == 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 = port;
+        }
+    }
+
+    void socketListen(int count) throws IOException {
+        if (fd == null || !fd.valid()) {
+            throw new SocketException("Socket closed");
+        }
+
+        try {
+            Libcore.os.listen(fd, count);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
+
+    void socketAccept(SocketImpl s) throws IOException {
+        if (fd == null || !fd.valid()) {
+            throw new SocketException("Socket closed");
+        }
+
+        // poll() with a timeout of 0 means "poll for zero millis", but a Socket timeout == 0 means
+        // "wait forever". When timeout == 0 we pass -1 to poll.
+        if (timeout <= 0) {
+            IoBridge.poll(fd, POLLIN | POLLERR, -1);
+        } else {
+            IoBridge.poll(fd, POLLIN | POLLERR, timeout);
+        }
+
+        InetSocketAddress peerAddress = new InetSocketAddress();
+        try {
+            FileDescriptor newfd = Libcore.os.accept(fd, peerAddress);
+
+            s.fd.setInt$(newfd.getInt$());
+            s.address = peerAddress.getAddress();
+            s.port = peerAddress.getPort();
+        } catch (ErrnoException errnoException) {
+            if (errnoException.errno == EAGAIN) {
+                throw new SocketTimeoutException(errnoException);
+            } else if (errnoException.errno == EINVAL || errnoException.errno == EBADF) {
+                throw new SocketException("Socket closed");
+            }
+            errnoException.rethrowAsSocketException();
+        }
+
+        s.localport = IoBridge.getLocalInetSocketAddress(s.fd).getPort();
+    }
+
+    int socketAvailable() throws IOException {
+        return IoBridge.available(fd);
+    }
+
+    void socketClose0(boolean useDeferredClose) throws IOException {
+        if (fd == null || !fd.valid()) {
+            throw new SocketException("socket already closed");
+        }
+
+        FileDescriptor markerFD = null;
+        if (useDeferredClose) {
+            markerFD = getMarkerFD();
+        }
+
+        if (useDeferredClose && markerFD != null) {
+            try {
+                Libcore.os.dup2(markerFD, fd.getInt$());
+                Libcore.os.close(markerFD);
+
+                // This effectively closes the socket, needs to signal threads that blocks on this
+                // file descriptor.
+                AsynchronousCloseMonitor.signalBlockedThreads(fd);
+            } catch (ErrnoException errnoException) {
+                // close should not throw
+            }
+        } else {
+            // If requested or a markerFD cannot be created, a non-deferred close is performed
+            // instead.
+            IoBridge.closeAndSignalBlockedThreads(fd);
+        }
+    }
+
+    /*
+     * Create the marker file descriptor by establishing a loopback connection which we shutdown but
+     * do not close the fd. The result is an fd that can be used for read/write.
+     *
+     * The purpose is to keep hold of the raw fd handle until we are sure it is not used in any
+     * thread. Otherwise if we close the file descriptor directly, the system might reuse the raw fd
+     * number and threads holding old fd value might behave incorrectly.
+     */
+    private FileDescriptor getMarkerFD() throws SocketException {
+        FileDescriptor fd1 = new FileDescriptor();
+        FileDescriptor fd2 = new FileDescriptor();
+        try {
+            Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd1, fd2);
+
+            // Shutdown fd1, any reads to this fd will get EOF; any writes will get an error.
+            Libcore.os.shutdown(fd1, SHUT_RDWR);
+            Libcore.os.close(fd2);
+        } catch (ErrnoException errnoException) {
+            // We might have reached the maximum file descriptor number and socketpair(2) would
+            // fail. In this case, return null and let caller to fall back to an alternative method
+            // that does not allocate more file descriptors.
+            return null;
+        }
+        return fd1;
+    }
+
+    void socketShutdown(int howto) throws IOException {
+        try {
+            Libcore.os.shutdown(fd, howto);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsIOException();
+        }
+    }
 
     native void socketSetOption0(int cmd, boolean on, Object value)
         throws SocketException;
 
     native int socketGetOption(int opt, Object iaContainerObj) throws SocketException;
 
-    native void socketSendUrgentData(int data) throws IOException;
+    void socketSendUrgentData(int data) throws IOException {
+        if (fd == null || !fd.valid()) {
+            throw new SocketException("Socket closed");
+        }
+
+        try {
+            byte[] buffer = new byte[] { (byte) data };
+            Libcore.os.sendto(fd, buffer, 0, 1, MSG_OOB, null, 0);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
 
 }
diff --git a/ojluni/src/main/java/java/net/SocketInputStream.java b/ojluni/src/main/java/java/net/SocketInputStream.java
index 2757412..f5c4c8f 100644
--- a/ojluni/src/main/java/java/net/SocketInputStream.java
+++ b/ojluni/src/main/java/java/net/SocketInputStream.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2016, 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
@@ -153,11 +153,12 @@
         }
 
         // bounds check
-        if (length <= 0 || off < 0 || off + length > b.length) {
+        if (length <= 0 || off < 0 || length > b.length - off) {
             if (length == 0) {
                 return 0;
             }
-            throw new ArrayIndexOutOfBoundsException();
+            throw new ArrayIndexOutOfBoundsException("length == " + length
+                    + " off == " + off + " buffer length == " + b.length);
         }
 
         boolean gotReset = false;
diff --git a/ojluni/src/main/java/java/net/SocketOutputStream.java b/ojluni/src/main/java/java/net/SocketOutputStream.java
index d37a7d7..c0173f0 100644
--- a/ojluni/src/main/java/java/net/SocketOutputStream.java
+++ b/ojluni/src/main/java/java/net/SocketOutputStream.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2016, 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
@@ -95,12 +95,12 @@
      * @exception IOException If an I/O error has occurred.
      */
     private void socketWrite(byte b[], int off, int len) throws IOException {
-
-        if (len <= 0 || off < 0 || off + len > b.length) {
+        if (len <= 0 || off < 0 || len > b.length - off) {
             if (len == 0) {
                 return;
             }
-            throw new ArrayIndexOutOfBoundsException();
+            throw new ArrayIndexOutOfBoundsException("len == " + len
+                    + " off == " + off + " buffer length == " + b.length);
         }
 
         FileDescriptor fd = impl.acquireFD();
diff --git a/ojluni/src/main/java/java/net/URLStreamHandler.java b/ojluni/src/main/java/java/net/URLStreamHandler.java
index 1521b1b..eac8a78 100644
--- a/ojluni/src/main/java/java/net/URLStreamHandler.java
+++ b/ojluni/src/main/java/java/net/URLStreamHandler.java
@@ -168,9 +168,9 @@
             (spec.charAt(start + 1) == '/')) {
             start += 2;
             i = spec.indexOf('/', start);
-            if (i < 0) {
+            if (i < 0 || i > limit) {
                 i = spec.indexOf('?', start);
-                if (i < 0)
+                if (i < 0 || i > limit)
                     i = limit;
             }
 
@@ -178,8 +178,14 @@
 
             int ind = authority.indexOf('@');
             if (ind != -1) {
-                userInfo = authority.substring(0, ind);
-                host = authority.substring(ind+1);
+                if (ind != authority.lastIndexOf('@')) {
+                    // more than one '@' in authority. This is not server based
+                    userInfo = null;
+                    host = null;
+                } else {
+                    userInfo = authority.substring(0, ind);
+                    host = authority.substring(ind+1);
+                }
             } else {
                 userInfo = null;
             }
diff --git a/ojluni/src/main/java/java/security/AccessControlContext.java b/ojluni/src/main/java/java/security/AccessControlContext.java
index 86a88f0..e95cff2 100644
--- a/ojluni/src/main/java/java/security/AccessControlContext.java
+++ b/ojluni/src/main/java/java/security/AccessControlContext.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, 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
diff --git a/ojluni/src/main/java/java/security/AccessController.java b/ojluni/src/main/java/java/security/AccessController.java
index 1f7bdc5..b4d544c 100644
--- a/ojluni/src/main/java/java/security/AccessController.java
+++ b/ojluni/src/main/java/java/security/AccessController.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/AuthProvider.java b/ojluni/src/main/java/java/security/AuthProvider.java
index 0308291..23ddb0a 100644
--- a/ojluni/src/main/java/java/security/AuthProvider.java
+++ b/ojluni/src/main/java/java/security/AuthProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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
diff --git a/ojluni/src/main/java/java/security/CodeSource.java b/ojluni/src/main/java/java/security/CodeSource.java
index d678011..7396d91 100644
--- a/ojluni/src/main/java/java/security/CodeSource.java
+++ b/ojluni/src/main/java/java/security/CodeSource.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/DomainCombiner.java b/ojluni/src/main/java/java/security/DomainCombiner.java
index 7dc0849..5426bf6 100644
--- a/ojluni/src/main/java/java/security/DomainCombiner.java
+++ b/ojluni/src/main/java/java/security/DomainCombiner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2006, 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
diff --git a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
index fb34981..951e44e 100644
--- a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
+++ b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2003, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -58,13 +58,13 @@
     }
 
     /**
-     * Creates a <code>NoSuchAlgorithmException</code> with the specified
+     * Creates a {@code NoSuchAlgorithmException} with the specified
      * detail message and cause.
      *
      * @param message the detail message (which is saved for later retrieval
      *        by the {@link #getMessage()} method).
      * @param cause the cause (which is saved for later retrieval by the
-     *        {@link #getCause()} method).  (A <tt>null</tt> value is permitted,
+     *        {@link #getCause()} method).  (A {@code null} value is permitted,
      *        and indicates that the cause is nonexistent or unknown.)
      * @since 1.5
      */
@@ -73,13 +73,13 @@
     }
 
     /**
-     * Creates a <code>NoSuchAlgorithmException</code> with the specified cause
-     * and a detail message of <tt>(cause==null ? null : cause.toString())</tt>
+     * Creates a {@code NoSuchAlgorithmException} with the specified cause
+     * and a detail message of {@code (cause==null ? null : cause.toString())}
      * (which typically contains the class and detail message of
-     * <tt>cause</tt>).
+     * {@code cause}).
      *
      * @param cause the cause (which is saved for later retrieval by the
-     *        {@link #getCause()} method).  (A <tt>null</tt> value is permitted,
+     *        {@link #getCause()} method).  (A {@code null} value is permitted,
      *        and indicates that the cause is nonexistent or unknown.)
      * @since 1.5
      */
diff --git a/ojluni/src/main/java/java/security/Permission.java b/ojluni/src/main/java/java/security/Permission.java
index 9f04ac4..8d170c0 100644
--- a/ojluni/src/main/java/java/security/Permission.java
+++ b/ojluni/src/main/java/java/security/Permission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/Permissions.java b/ojluni/src/main/java/java/security/Permissions.java
index de5d451..7411e06 100644
--- a/ojluni/src/main/java/java/security/Permissions.java
+++ b/ojluni/src/main/java/java/security/Permissions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/PrivilegedAction.java b/ojluni/src/main/java/java/security/PrivilegedAction.java
index ab39f98..bef7e44 100644
--- a/ojluni/src/main/java/java/security/PrivilegedAction.java
+++ b/ojluni/src/main/java/java/security/PrivilegedAction.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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
diff --git a/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java b/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
index 87e4209..bae883e 100644
--- a/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
+++ b/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 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
diff --git a/ojluni/src/main/java/java/security/SecurityPermission.java b/ojluni/src/main/java/java/security/SecurityPermission.java
index 432ee4b..7d2ec47 100644
--- a/ojluni/src/main/java/java/security/SecurityPermission.java
+++ b/ojluni/src/main/java/java/security/SecurityPermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/security/UnresolvedPermission.java b/ojluni/src/main/java/java/security/UnresolvedPermission.java
index dec952b..6d97fbe 100644
--- a/ojluni/src/main/java/java/security/UnresolvedPermission.java
+++ b/ojluni/src/main/java/java/security/UnresolvedPermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
diff --git a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
index 833b6b6..9e82738 100644
--- a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
+++ b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
@@ -712,24 +712,34 @@
 
     // Android-changed: maybeStripMarkers added in b/26207216, fixed in b/32465689.
     /**
-     * Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}. If the symbol's
-     * length is 1, then the first char of the symbol is returned. If the symbol's length is more
-     * than 1 and the first char is a marker, then this marker is ignored. In all other cases,
-     * {@code fallback} is returned.
+     * Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}.
+     * If the string contains a single non-marker character (and any number of marker characters),
+     * then that character is returned, otherwise {@code fallback} is returned.
+     *
+     * @hide
      */
-    private static char maybeStripMarkers(String symbol, char fallback) {
+    // VisibleForTesting
+    public static char maybeStripMarkers(String symbol, char fallback) {
         final int length = symbol.length();
-        if (length == 1) {
-            return symbol.charAt(0);
-        }
-
-        if (length > 1) {
-            char first = symbol.charAt(0);
-            if (first =='\u200E' || first =='\u200F' || first =='\u061C') {
-                return symbol.charAt(1);
+        if (length >= 1) {
+            boolean sawNonMarker = false;
+            char nonMarker = 0;
+            for (int i = 0; i < length; i++) {
+                final char c = symbol.charAt(i);
+                if (c == '\u200E' || c == '\u200F' || c == '\u061C') {
+                    continue;
+                }
+                if (sawNonMarker) {
+                    // More than one non-marker character.
+                    return fallback;
+                }
+                sawNonMarker = true;
+                nonMarker = c;
+            }
+            if (sawNonMarker) {
+                return nonMarker;
             }
         }
-
         return fallback;
     }
 
diff --git a/ojluni/src/main/java/java/util/Hashtable.java b/ojluni/src/main/java/java/util/Hashtable.java
index a07569c..6061290 100644
--- a/ojluni/src/main/java/java/util/Hashtable.java
+++ b/ojluni/src/main/java/java/util/Hashtable.java
@@ -861,6 +861,12 @@
         return h;
     }
 
+    @Override
+    public synchronized V getOrDefault(Object key, V defaultValue) {
+        V result = get(key);
+        return (null == result) ? defaultValue : result;
+    }
+
     @SuppressWarnings("unchecked")
     @Override
     public synchronized void forEach(BiConsumer<? super K, ? super V> action) {
@@ -902,57 +908,216 @@
         }
     }
 
-    // BEGIN Android-changed: Just wrap synchronization around Map.super implementations.
-    // Upstream uses different overridden implementations.
-    @Override
-    public synchronized V getOrDefault(Object key, V defaultValue) {
-        return Map.super.getOrDefault(key, defaultValue);
-    }
-
     @Override
     public synchronized V putIfAbsent(K key, V value) {
-        return Map.super.putIfAbsent(key, value);
+        Objects.requireNonNull(value);
+
+        // Makes sure the key is not already in the hashtable.
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> entry = (HashtableEntry<K,V>)tab[index];
+        for (; entry != null; entry = entry.next) {
+            if ((entry.hash == hash) && entry.key.equals(key)) {
+                V old = entry.value;
+                if (old == null) {
+                    entry.value = value;
+                }
+                return old;
+            }
+        }
+
+        addEntry(hash, key, value, index);
+        return null;
     }
 
     @Override
     public synchronized boolean remove(Object key, Object value) {
-       return Map.super.remove(key, value);
+        Objects.requireNonNull(value);
+
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
+                modCount++;
+                if (prev != null) {
+                    prev.next = e.next;
+                } else {
+                    tab[index] = e.next;
+                }
+                count--;
+                e.value = null;
+                return true;
+            }
+        }
+        return false;
     }
 
     @Override
     public synchronized boolean replace(K key, V oldValue, V newValue) {
-        return Map.super.replace(key, oldValue, newValue);
+        Objects.requireNonNull(oldValue);
+        Objects.requireNonNull(newValue);
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (; e != null; e = e.next) {
+            if ((e.hash == hash) && e.key.equals(key)) {
+                if (e.value.equals(oldValue)) {
+                    e.value = newValue;
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return false;
     }
 
     @Override
     public synchronized V replace(K key, V value) {
-        return Map.super.replace(key, value);
+        Objects.requireNonNull(value);
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (; e != null; e = e.next) {
+            if ((e.hash == hash) && e.key.equals(key)) {
+                V oldValue = e.value;
+                e.value = value;
+                return oldValue;
+            }
+        }
+        return null;
     }
 
     @Override
-    public synchronized V computeIfAbsent(K key, Function<? super K,
-            ? extends V> mappingFunction) {
-        return Map.super.computeIfAbsent(key, mappingFunction);
+    public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+        Objects.requireNonNull(mappingFunction);
+
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (; e != null; e = e.next) {
+            if (e.hash == hash && e.key.equals(key)) {
+                // Hashtable not accept null value
+                return e.value;
+            }
+        }
+
+        V newValue = mappingFunction.apply(key);
+        if (newValue != null) {
+            addEntry(hash, key, newValue, index);
+        }
+
+        return newValue;
     }
 
     @Override
-    public synchronized V computeIfPresent(K key, BiFunction<? super K,
-            ? super V, ? extends V> remappingFunction) {
-        return Map.super.computeIfPresent(key, remappingFunction);
+    public synchronized V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if (e.hash == hash && e.key.equals(key)) {
+                V newValue = remappingFunction.apply(key, e.value);
+                if (newValue == null) {
+                    modCount++;
+                    if (prev != null) {
+                        prev.next = e.next;
+                    } else {
+                        tab[index] = e.next;
+                    }
+                    count--;
+                } else {
+                    e.value = newValue;
+                }
+                return newValue;
+            }
+        }
+        return null;
     }
 
     @Override
-    public synchronized V compute(K key, BiFunction<? super K, ? super V,
-            ? extends V> remappingFunction) {
-        return Map.super.compute(key, remappingFunction);
+    public synchronized V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V newValue = remappingFunction.apply(key, e.value);
+                if (newValue == null) {
+                    modCount++;
+                    if (prev != null) {
+                        prev.next = e.next;
+                    } else {
+                        tab[index] = e.next;
+                    }
+                    count--;
+                } else {
+                    e.value = newValue;
+                }
+                return newValue;
+            }
+        }
+
+        V newValue = remappingFunction.apply(key, null);
+        if (newValue != null) {
+            addEntry(hash, key, newValue, index);
+        }
+
+        return newValue;
     }
 
     @Override
-    public synchronized V merge(K key, V value, BiFunction<? super V, ? super V,
-            ? extends V> remappingFunction) {
-        return Map.super.merge(key, value, remappingFunction);
+    public synchronized V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+
+        HashtableEntry<?,?> tab[] = table;
+        int hash = key.hashCode();
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+        for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if (e.hash == hash && e.key.equals(key)) {
+                V newValue = remappingFunction.apply(e.value, value);
+                if (newValue == null) {
+                    modCount++;
+                    if (prev != null) {
+                        prev.next = e.next;
+                    } else {
+                        tab[index] = e.next;
+                    }
+                    count--;
+                } else {
+                    e.value = newValue;
+                }
+                return newValue;
+            }
+        }
+
+        if (value != null) {
+            addEntry(hash, key, value, index);
+        }
+
+        return value;
     }
-    // END Android-changed: Just add synchronization around Map's default implementations.
 
     /**
      * Save the state of the Hashtable to a stream (i.e., serialize it).
diff --git a/ojluni/src/main/java/java/util/TimeZone.java b/ojluni/src/main/java/java/util/TimeZone.java
index c745291..28ad0ca 100644
--- a/ojluni/src/main/java/java/util/TimeZone.java
+++ b/ojluni/src/main/java/java/util/TimeZone.java
@@ -398,16 +398,8 @@
             return result;
         }
 
-        // If we get here, it's because icu4c has nothing for us. Most commonly, this is in the
-        // case of short names. For Pacific/Fiji, for example, icu4c has nothing better to offer
-        // than "GMT+12:00". Why do we re-do this work ourselves? Because we have up-to-date
-        // time zone transition data, which icu4c _doesn't_ use --- it uses its own baked-in copy,
-        // which only gets updated when we update icu4c. http://b/7955614 and http://b/8026776.
-
-        // TODO: should we generate these once, in TimeZoneNames.getDisplayName? Revisit when we
-        // upgrade to icu4c 50 and rewrite the underlying native code. See also the
-        // "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in
-        // DateFormatSymbols.getZoneStrings.
+        // We get here if this is a custom timezone or ICU doesn't have name data for the specific
+        // style and locale.
         int offsetMillis = getRawOffset();
         if (daylightTime) {
             offsetMillis += getDSTSavings();
diff --git a/ojluni/src/main/java/java/util/regex/MatchResult.java b/ojluni/src/main/java/java/util/regex/MatchResult.java
index 9b52331..4f42eae 100644
--- a/ojluni/src/main/java/java/util/regex/MatchResult.java
+++ b/ojluni/src/main/java/java/util/regex/MatchResult.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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
@@ -77,9 +77,9 @@
     public int start(int group);
 
     /**
-     * Returns the offset after the last character matched.  </p>
+     * Returns the offset after the last character matched.
      *
-     * @return  @return  The offset after the last character matched
+     * @return  The offset after the last character matched
      *
      * @throws  IllegalStateException
      *          If no match has yet been attempted,
diff --git a/ojluni/src/main/java/java/util/regex/Matcher.java b/ojluni/src/main/java/java/util/regex/Matcher.java
index 3d79ba7..9af28aa 100644
--- a/ojluni/src/main/java/java/util/regex/Matcher.java
+++ b/ojluni/src/main/java/java/util/regex/Matcher.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1999, 2009, 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
@@ -444,7 +444,7 @@
      */
     public boolean matches() {
         synchronized (this) {
-            matchFound = matchesImpl(address, input, matchOffsets);
+            matchFound = matchesImpl(address, matchOffsets);
         }
         return matchFound;
     }
@@ -466,7 +466,7 @@
      */
     public boolean find() {
         synchronized (this) {
-            matchFound = findNextImpl(address, input, matchOffsets);
+            matchFound = findNextImpl(address, matchOffsets);
         }
         return matchFound;
     }
@@ -495,7 +495,7 @@
         }
 
         synchronized (this) {
-            matchFound = findImpl(address, input, start, matchOffsets);
+            matchFound = findImpl(address, start, matchOffsets);
         }
         return matchFound;
     }
@@ -516,7 +516,7 @@
      */
     public boolean lookingAt() {
         synchronized (this) {
-            matchFound = lookingAtImpl(address, input, matchOffsets);
+            matchFound = lookingAtImpl(address, matchOffsets);
         }
         return matchFound;
     }
@@ -1181,13 +1181,13 @@
     }
 
     private static native int getMatchedGroupIndex0(long patternAddr, String name);
-    private static native boolean findImpl(long addr, String s, int startIndex, int[] offsets);
-    private static native boolean findNextImpl(long addr, String s, int[] offsets);
+    private static native boolean findImpl(long addr, int startIndex, int[] offsets);
+    private static native boolean findNextImpl(long addr, int[] offsets);
     private static native long getNativeFinalizer();
     private static native int groupCountImpl(long addr);
     private static native boolean hitEndImpl(long addr);
-    private static native boolean lookingAtImpl(long addr, String s, int[] offsets);
-    private static native boolean matchesImpl(long addr, String s, int[] offsets);
+    private static native boolean lookingAtImpl(long addr, int[] offsets);
+    private static native boolean matchesImpl(long addr, int[] offsets);
     private static native int nativeSize();
     private static native long openImpl(long patternAddr);
     private static native boolean requireEndImpl(long addr);
diff --git a/ojluni/src/main/java/javax/crypto/CipherOutputStream.java b/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
index 6b8d273..370f7af 100644
--- a/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
+++ b/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
@@ -210,6 +210,8 @@
             obuffer = cipher.doFinal();
         } catch (IllegalBlockSizeException | BadPaddingException e) {
             obuffer = null;
+            // Android-added: Throw an exception when the underlying cipher does.  http://b/36636576
+            throw new IOException(e);
         }
         try {
             flush();
diff --git a/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java b/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java
index 8f1fa47..e6468d1 100644
--- a/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java
+++ b/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java
@@ -46,7 +46,11 @@
 
     @Override
     LinuxFileStore getFileStore(UnixPath path) throws IOException {
-        return new LinuxFileStore(path);
+        // Android-changed: Complete information about file systems is neither available to regular
+        // apps nor the system server due to SELinux policies.
+        // return new LinuxFileStore(path);
+
+        throw new SecurityException("getFileStore");
     }
 
     @Override
@@ -55,14 +59,18 @@
                                                                 Class<V> type,
                                                                 LinkOption... options)
     {
-        if (type == DosFileAttributeView.class) {
-            return (V) new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
-                                                     Util.followLinks(options));
-        }
-        if (type == UserDefinedFileAttributeView.class) {
-            return (V) new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
-                                                             Util.followLinks(options));
-        }
+        // Android-changed: Delegate to UnixFileSystemProvider, remove support for "dos" and
+        // "user" file attribute views which are not supported.
+        //
+        // if (type == DosFileAttributeView.class) {
+        //     return (V) new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
+        //                                              Util.followLinks(options));
+        // }
+        // if (type == UserDefinedFileAttributeView.class) {
+        //     return (V) new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
+        //                                                      Util.followLinks(options));
+        // }
+
         return super.getFileAttributeView(obj, type, options);
     }
 
@@ -71,14 +79,18 @@
                                                          String name,
                                                          LinkOption... options)
     {
-        if (name.equals("dos")) {
-            return new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
-                                                 Util.followLinks(options));
-        }
-        if (name.equals("user")) {
-            return new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
-                                                         Util.followLinks(options));
-        }
+        // Android-changed: Delegate to UnixFileSystemProvider, remove support for "dos" and
+        // "user" file attribute views which are not supported.
+        //
+        // if (name.equals("dos")) {
+        //     return new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
+        //                                          Util.followLinks(options));
+        // }
+        // if (name.equals("user")) {
+        //     return new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
+        //                                                  Util.followLinks(options));
+        // }
+
         return super.getFileAttributeView(obj, name, options);
     }
 
@@ -89,13 +101,17 @@
                                                             LinkOption... options)
         throws IOException
     {
-        if (type == DosFileAttributes.class) {
-            DosFileAttributeView view =
-                getFileAttributeView(file, DosFileAttributeView.class, options);
-            return (A) view.readAttributes();
-        } else {
-            return super.readAttributes(file, type, options);
-        }
+        // Android-changed: Delegate to UnixFileSystemProvider, remove support for "dos" and
+        // "user" file attribute views which are not supported.
+        //
+        // if (type == DosFileAttributes.class) {
+        //     DosFileAttributeView view =
+        //         getFileAttributeView(file, DosFileAttributeView.class, options);
+        //     return (A) view.readAttributes();
+        // } else {
+        //     return super.readAttributes(file, type, options);
+        // }
+        return super.readAttributes(file, type, options);
     }
 
     @Override
diff --git a/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java b/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java
index 182a57b..3724956 100644
--- a/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java
+++ b/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java
@@ -359,13 +359,17 @@
 
     @Override
     public FileStore getFileStore(Path obj) throws IOException {
-        UnixPath file = UnixPath.toUnixPath(obj);
-        SecurityManager sm = System.getSecurityManager();
-        if (sm != null) {
-            sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
-            file.checkRead();
-        }
-        return getFileStore(file);
+        // Android-changed: Complete information about file systems is neither available to regular
+        // apps nor the system server due to SELinux policies.
+        //
+        // UnixPath file = UnixPath.toUnixPath(obj);
+        // SecurityManager sm = System.getSecurityManager();
+        // if (sm != null) {
+        //     sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
+        //     file.checkRead();
+        // }
+        // return getFileStore(file);
+        throw new SecurityException("getFileStore");
     }
 
     @Override
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java b/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java
index db36c0e..0ad5387 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java
@@ -36,9 +36,7 @@
 
 import sun.security.util.Debug;
 import sun.security.util.DerInputStream;
-import sun.security.util.DerOutputStream;
 import sun.security.x509.SerialNumber;
-import sun.security.x509.KeyIdentifier;
 import sun.security.x509.AuthorityKeyIdentifierExtension;
 
 /**
@@ -131,13 +129,7 @@
         serial = null;
 
         if (ext != null) {
-            KeyIdentifier akid = (KeyIdentifier)ext.get(
-                AuthorityKeyIdentifierExtension.KEY_ID);
-            if (akid != null) {
-                DerOutputStream derout = new DerOutputStream();
-                derout.putOctetString(akid.getIdentifier());
-                ski = derout.toByteArray();
-            }
+            ski = ext.getEncodedKeyIdentifier();
             SerialNumber asn = (SerialNumber)ext.get(
                 AuthorityKeyIdentifierExtension.SERIAL_NUMBER);
             if (asn != null) {
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java b/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java
index ab75ff0..e92590a 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2016, 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
@@ -31,12 +31,10 @@
 import java.util.Collections;
 import java.util.Set;
 import java.util.EnumSet;
-import java.util.HashSet;
 import java.math.BigInteger;
 import java.security.PublicKey;
 import java.security.KeyFactory;
 import java.security.AlgorithmParameters;
-import java.security.NoSuchAlgorithmException;
 import java.security.GeneralSecurityException;
 import java.security.cert.Certificate;
 import java.security.cert.X509CRL;
@@ -48,10 +46,13 @@
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertPathValidatorException.BasicReason;
 import java.security.cert.PKIXReason;
-import java.io.IOException;
-import java.security.interfaces.*;
-import java.security.spec.*;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
 
+import sun.security.util.AnchorCertificates;
+import sun.security.util.CertConstraintParameters;
+import sun.security.util.Debug;
 import sun.security.util.DisabledAlgorithmConstraints;
 import sun.security.x509.X509CertImpl;
 import sun.security.x509.X509CRLImpl;
@@ -69,18 +70,34 @@
  * @see PKIXParameters
  */
 final public class AlgorithmChecker extends PKIXCertPathChecker {
+    private static final Debug debug = Debug.getInstance("certpath");
 
     private final AlgorithmConstraints constraints;
     private final PublicKey trustedPubKey;
     private PublicKey prevPubKey;
 
     private final static Set<CryptoPrimitive> SIGNATURE_PRIMITIVE_SET =
-                                    EnumSet.of(CryptoPrimitive.SIGNATURE);
+        Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
+
+    private final static Set<CryptoPrimitive> KU_PRIMITIVE_SET =
+        Collections.unmodifiableSet(EnumSet.of(
+            CryptoPrimitive.SIGNATURE,
+            CryptoPrimitive.KEY_ENCAPSULATION,
+            CryptoPrimitive.PUBLIC_KEY_ENCRYPTION,
+            CryptoPrimitive.KEY_AGREEMENT));
 
     private final static DisabledAlgorithmConstraints
         certPathDefaultConstraints = new DisabledAlgorithmConstraints(
             DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
 
+    // If there is no "cacerts" keyword, then disable anchor checking
+    private static final boolean publicCALimits =
+            certPathDefaultConstraints.checkProperty("jdkCA");
+
+    // If anchor checking enabled, this will be true if the trust anchor
+    // has a match in the cacerts file
+    private boolean trustedMatch = false;
+
     /**
      * Create a new <code>AlgorithmChecker</code> with the algorithm
      * constraints specified in security property
@@ -129,6 +146,11 @@
 
         if (anchor.getTrustedCert() != null) {
             this.trustedPubKey = anchor.getTrustedCert().getPublicKey();
+            // Check for anchor certificate restrictions
+            trustedMatch = checkFingerprint(anchor.getTrustedCert());
+            if (trustedMatch && debug != null) {
+                debug.println("trustedMatch = true");
+            }
         } else {
             this.trustedPubKey = anchor.getCAPublicKey();
         }
@@ -137,6 +159,19 @@
         this.constraints = constraints;
     }
 
+    // Check this 'cert' for restrictions in the AnchorCertificates
+    // trusted certificates list
+    private static boolean checkFingerprint(X509Certificate cert) {
+        if (!publicCALimits) {
+            return false;
+        }
+
+        if (debug != null) {
+            debug.println("AlgorithmChecker.contains: " + cert.getSigAlgName());
+        }
+        return AnchorCertificates.contains(cert);
+    }
+
     @Override
     public void init(boolean forward) throws CertPathValidatorException {
         //  Note that this class does not support forward mode.
@@ -174,45 +209,19 @@
             return;
         }
 
-        X509CertImpl x509Cert = null;
-        try {
-            x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
-        } catch (CertificateException ce) {
-            throw new CertPathValidatorException(ce);
-        }
-
-        PublicKey currPubKey = x509Cert.getPublicKey();
-        String currSigAlg = x509Cert.getSigAlgName();
-
-        AlgorithmId algorithmId = null;
-        try {
-            algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
-        } catch (CertificateException ce) {
-            throw new CertPathValidatorException(ce);
-        }
-
-        AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
-
-        // Check the current signature algorithm
-        if (!constraints.permits(
-                SIGNATURE_PRIMITIVE_SET,
-                currSigAlg, currSigAlgParams)) {
-            throw new CertPathValidatorException(
-                "Algorithm constraints check failed: " + currSigAlg,
-                null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
-        }
-
         // check the key usage and key size
-        boolean[] keyUsage = x509Cert.getKeyUsage();
+        boolean[] keyUsage = ((X509Certificate) cert).getKeyUsage();
         if (keyUsage != null && keyUsage.length < 9) {
             throw new CertPathValidatorException(
                 "incorrect KeyUsage extension",
                 null, null, -1, PKIXReason.INVALID_KEY_USAGE);
         }
 
+        // Assume all key usage bits are set if key usage is not present
+        Set<CryptoPrimitive> primitives = KU_PRIMITIVE_SET;
+
         if (keyUsage != null) {
-            Set<CryptoPrimitive> primitives =
-                        EnumSet.noneOf(CryptoPrimitive.class);
+            primitives = EnumSet.noneOf(CryptoPrimitive.class);
 
             if (keyUsage[0] || keyUsage[1] || keyUsage[5] || keyUsage[6]) {
                 // keyUsage[0]: KeyUsage.digitalSignature
@@ -237,26 +246,70 @@
             // KeyUsage.encipherOnly and KeyUsage.decipherOnly are
             // undefined in the absence of the keyAgreement bit.
 
-            if (!primitives.isEmpty()) {
-                if (!constraints.permits(primitives, currPubKey)) {
-                    throw new CertPathValidatorException(
-                        "algorithm constraints check failed",
-                        null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
-                }
+            if (primitives.isEmpty()) {
+                throw new CertPathValidatorException(
+                    "incorrect KeyUsage extension bits",
+                    null, null, -1, PKIXReason.INVALID_KEY_USAGE);
             }
         }
 
+        PublicKey currPubKey = cert.getPublicKey();
+
+        // Check against DisabledAlgorithmConstraints certpath constraints.
+        // permits() will throw exception on failure.
+        certPathDefaultConstraints.permits(primitives,
+                new CertConstraintParameters((X509Certificate)cert,
+                        trustedMatch));
+                // new CertConstraintParameters(x509Cert, trustedMatch));
+        // If there is no previous key, set one and exit
+        if (prevPubKey == null) {
+            prevPubKey = currPubKey;
+            return;
+        }
+
+        X509CertImpl x509Cert;
+        AlgorithmId algorithmId;
+        try {
+            x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
+            algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
+        } catch (CertificateException ce) {
+            throw new CertPathValidatorException(ce);
+        }
+
+        AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
+        String currSigAlg = x509Cert.getSigAlgName();
+
+        // If 'constraints' is not of DisabledAlgorithmConstraints, check all
+        // everything individually
+        if (!(constraints instanceof DisabledAlgorithmConstraints)) {
+            // Check the current signature algorithm
+            if (!constraints.permits(
+                    SIGNATURE_PRIMITIVE_SET,
+                    currSigAlg, currSigAlgParams)) {
+                throw new CertPathValidatorException(
+                        "Algorithm constraints check failed on signature " +
+                                "algorithm: " + currSigAlg, null, null, -1,
+                        BasicReason.ALGORITHM_CONSTRAINED);
+            }
+
+        if (!constraints.permits(primitives, currPubKey)) {
+            throw new CertPathValidatorException(
+                        "Algorithm constraints check failed on keysize: " +
+                                sun.security.util.KeyUtil.getKeySize(currPubKey),
+                null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+        }
+        }
+
         // Check with previous cert for signature algorithm and public key
         if (prevPubKey != null) {
-            if (currSigAlg != null) {
                 if (!constraints.permits(
                         SIGNATURE_PRIMITIVE_SET,
                         currSigAlg, prevPubKey, currSigAlgParams)) {
                     throw new CertPathValidatorException(
-                        "Algorithm constraints check failed: " + currSigAlg,
+                    "Algorithm constraints check failed on " +
+                            "signature algorithm: " + currSigAlg,
                         null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
                 }
-            }
 
             // Inherit key parameters from previous key
             if (PKIX.isDSAPublicKeyWithoutParams(currPubKey)) {
@@ -269,7 +322,7 @@
                 DSAParams params = ((DSAPublicKey)prevPubKey).getParams();
                 if (params == null) {
                     throw new CertPathValidatorException(
-                                    "Key parameters missing");
+                        "Key parameters missing from public key.");
                 }
 
                 try {
@@ -317,6 +370,11 @@
             // Don't bother to change the trustedPubKey.
             if (anchor.getTrustedCert() != null) {
                 prevPubKey = anchor.getTrustedCert().getPublicKey();
+                // Check for anchor certificate restrictions
+                trustedMatch = checkFingerprint(anchor.getTrustedCert());
+                if (trustedMatch && debug != null) {
+                    debug.println("trustedMatch = true");
+                }
             } else {
                 prevPubKey = anchor.getCAPublicKey();
             }
@@ -357,10 +415,10 @@
         if (!certPathDefaultConstraints.permits(
                 SIGNATURE_PRIMITIVE_SET, sigAlgName, key, sigAlgParams)) {
             throw new CertPathValidatorException(
-                "algorithm check failed: " + sigAlgName + " is disabled",
+                "Algorithm constraints check failed on signature algorithm: " +
+                sigAlgName + " is disabled",
                 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
         }
     }
 
 }
-
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java b/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java
index 5b9dd60..5d9e657 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java
@@ -33,7 +33,6 @@
 import java.util.*;
 
 import sun.security.util.Debug;
-import sun.security.util.DerOutputStream;
 import static sun.security.x509.PKIXExtensions.*;
 import sun.security.x509.*;
 
@@ -608,12 +607,9 @@
             AuthorityKeyIdentifierExtension akidext =
                                             crlImpl.getAuthKeyIdExtension();
             if (akidext != null) {
-                KeyIdentifier akid = (KeyIdentifier)akidext.get(
-                        AuthorityKeyIdentifierExtension.KEY_ID);
-                if (akid != null) {
-                    DerOutputStream derout = new DerOutputStream();
-                    derout.putOctetString(akid.getIdentifier());
-                    certSel.setSubjectKeyIdentifier(derout.toByteArray());
+                byte[] kid = akidext.getEncodedKeyIdentifier();
+                if (kid != null) {
+                    certSel.setSubjectKeyIdentifier(kid);
                 }
 
                 SerialNumber asn = (SerialNumber)akidext.get(
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java b/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java
index d708ebf..57e819c 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java
@@ -46,9 +46,10 @@
 import sun.security.util.Debug;
 import sun.security.x509.AccessDescription;
 import sun.security.x509.AuthorityInfoAccessExtension;
+import sun.security.x509.AuthorityKeyIdentifierExtension;
 import static sun.security.x509.PKIXExtensions.*;
 import sun.security.x509.X500Name;
-import sun.security.x509.AuthorityKeyIdentifierExtension;
+import sun.security.x509.X509CertImpl;
 
 /**
  * This class represents a forward builder, which is able to retrieve
@@ -69,7 +70,6 @@
     private AdaptableX509CertSelector caSelector;
     private X509CertSelector caTargetSelector;
     TrustAnchor trustAnchor;
-    private Comparator<X509Certificate> comparator;
     private boolean searchAllCertStores = true;
 
     /**
@@ -93,7 +93,6 @@
                 trustedSubjectDNs.add(anchor.getCA());
             }
         }
-        comparator = new PKIXCertComparator(trustedSubjectDNs);
         this.searchAllCertStores = searchAllCertStores;
     }
 
@@ -122,6 +121,8 @@
          * As each cert is added, it is sorted based on the PKIXCertComparator
          * algorithm.
          */
+        Comparator<X509Certificate> comparator =
+            new PKIXCertComparator(trustedSubjectDNs, currState.cert);
         Set<X509Certificate> certs = new TreeSet<>(comparator);
 
         /*
@@ -265,14 +266,6 @@
                 (caSelector, currentState.subjectNamesTraversed);
 
             /*
-             * Facilitate certification path construction with authority
-             * key identifier and subject key identifier.
-             */
-            AuthorityKeyIdentifierExtension akidext =
-                    currentState.cert.getAuthorityKeyIdentifierExtension();
-            caSelector.setSkiAndSerialNumber(akidext);
-
-            /*
              * check the validity period
              */
             caSelector.setValidityPeriod(currentState.cert.getNotBefore(),
@@ -404,41 +397,68 @@
      *
      * Preference order for current cert:
      *
-     * 1) Issuer matches a trusted subject
+     * 1) The key identifier of an AKID extension (if present) in the
+     *    previous certificate matches the key identifier in the SKID extension
+     *
+     * 2) Issuer matches a trusted subject
      *    Issuer: ou=D,ou=C,o=B,c=A
      *
-     * 2) Issuer is a descendant of a trusted subject (in order of
+     * 3) Issuer is a descendant of a trusted subject (in order of
      *    number of links to the trusted subject)
      *    a) Issuer: ou=E,ou=D,ou=C,o=B,c=A        [links=1]
      *    b) Issuer: ou=F,ou=E,ou=D,ou=C,ou=B,c=A  [links=2]
      *
-     * 3) Issuer is an ancestor of a trusted subject (in order of number of
+     * 4) Issuer is an ancestor of a trusted subject (in order of number of
      *    links to the trusted subject)
      *    a) Issuer: ou=C,o=B,c=A [links=1]
      *    b) Issuer: o=B,c=A      [links=2]
      *
-     * 4) Issuer is in the same namespace as a trusted subject (in order of
+     * 5) Issuer is in the same namespace as a trusted subject (in order of
      *    number of links to the trusted subject)
      *    a) Issuer: ou=G,ou=C,o=B,c=A  [links=2]
      *    b) Issuer: ou=H,o=B,c=A       [links=3]
      *
-     * 5) Issuer is an ancestor of certificate subject (in order of number
+     * 6) Issuer is an ancestor of certificate subject (in order of number
      *    of links to the certificate subject)
      *    a) Issuer:  ou=K,o=J,c=A
      *       Subject: ou=L,ou=K,o=J,c=A
      *    b) Issuer:  o=J,c=A
      *       Subject: ou=L,ou=K,0=J,c=A
      *
-     * 6) Any other certificates
+     * 7) Any other certificates
      */
     static class PKIXCertComparator implements Comparator<X509Certificate> {
 
-        final static String METHOD_NME = "PKIXCertComparator.compare()";
+        static final String METHOD_NME = "PKIXCertComparator.compare()";
 
         private final Set<X500Principal> trustedSubjectDNs;
+        private final X509CertSelector certSkidSelector;
 
-        PKIXCertComparator(Set<X500Principal> trustedSubjectDNs) {
+        PKIXCertComparator(Set<X500Principal> trustedSubjectDNs,
+                           X509CertImpl previousCert) throws IOException {
             this.trustedSubjectDNs = trustedSubjectDNs;
+            this.certSkidSelector = getSelector(previousCert);
+        }
+
+        /**
+         * Returns an X509CertSelector for matching on the authority key
+         * identifier, or null if not applicable.
+         */
+        private X509CertSelector getSelector(X509CertImpl previousCert)
+            throws IOException {
+            if (previousCert != null) {
+                AuthorityKeyIdentifierExtension akidExt =
+                    previousCert.getAuthorityKeyIdentifierExtension();
+                if (akidExt != null) {
+                    byte[] skid = akidExt.getEncodedKeyIdentifier();
+                    if (skid != null) {
+                        X509CertSelector selector = new X509CertSelector();
+                        selector.setSubjectKeyIdentifier(skid);
+                        return selector;
+                    }
+                }
+            }
+            return null;
         }
 
         /**
@@ -462,6 +482,16 @@
             // if certs are the same, return 0
             if (oCert1.equals(oCert2)) return 0;
 
+            // If akid/skid match then it is preferable
+            if (certSkidSelector != null) {
+                if (certSkidSelector.match(oCert1)) {
+                    return -1;
+                }
+                if (certSkidSelector.match(oCert2)) {
+                    return 1;
+                }
+            }
+
             X500Principal cIssuer1 = oCert1.getIssuerX500Principal();
             X500Principal cIssuer2 = oCert2.getIssuerX500Principal();
             X500Name cIssuer1Name = X500Name.asX500Name(cIssuer1);
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java b/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java
index cfffba8..e85ef2d 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java
@@ -131,8 +131,8 @@
 
                 } catch (CertPathValidatorException cpve) {
                     throw new CertPathValidatorException(cpve.getMessage(),
-                        cpve.getCause(), cpOriginal, cpSize - (i + 1),
-                        cpve.getReason());
+                        (cpve.getCause() != null) ? cpve.getCause() : cpve,
+                            cpOriginal, cpSize - (i + 1), cpve.getReason());
                 }
             }
 
diff --git a/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java b/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java
index 4c3efd9..1b64d9d 100644
--- a/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java
+++ b/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016 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
@@ -29,7 +29,6 @@
 import java.security.AlgorithmConstraints;
 import java.security.PrivilegedAction;
 import java.security.Security;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -45,8 +44,7 @@
     }
 
     // Get algorithm constraints from the specified security property.
-    private static void loadAlgorithmsMap(Map<String, String[]> algorithmsMap,
-            String propertyName) {
+    static String[] getAlgorithms(String propertyName) {
         String property = AccessController.doPrivileged(
                 (PrivilegedAction<String>) () -> Security.getProperty(
                         propertyName));
@@ -69,18 +67,7 @@
         if (algorithmsInProperty == null) {
             algorithmsInProperty = new String[0];
         }
-        algorithmsMap.put(propertyName, algorithmsInProperty);
-    }
-
-    static String[] getAlgorithms(Map<String, String[]> algorithmsMap,
-            String propertyName) {
-        synchronized (algorithmsMap) {
-            if (!algorithmsMap.containsKey(propertyName)) {
-                loadAlgorithmsMap(algorithmsMap, propertyName);
-            }
-
-            return algorithmsMap.get(propertyName);
-        }
+        return algorithmsInProperty;
     }
 
     static boolean checkAlgorithm(String[] algorithms, String algorithm,
diff --git a/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java b/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java
index 394b846..dae529a 100644
--- a/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java
+++ b/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, 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
@@ -38,19 +38,7 @@
     private static final Pattern pattern =
                     Pattern.compile("with|and", Pattern.CASE_INSENSITIVE);
 
-    /**
-     * Decompose the standard algorithm name into sub-elements.
-     * <p>
-     * For example, we need to decompose "SHA1WithRSA" into "SHA1" and "RSA"
-     * so that we can check the "SHA1" and "RSA" algorithm constraints
-     * separately.
-     * <p>
-     * Please override the method if need to support more name pattern.
-     */
-    public Set<String> decompose(String algorithm) {
-        if (algorithm == null || algorithm.length() == 0) {
-            return new HashSet<>();
-        }
+    private static Set<String> decomposeImpl(String algorithm) {
 
         // algorithm/mode/padding
         String[] transTockens = transPattern.split(algorithm);
@@ -76,6 +64,24 @@
                 elements.add(token);
             }
         }
+        return elements;
+    }
+
+    /**
+     * Decompose the standard algorithm name into sub-elements.
+     * <p>
+     * For example, we need to decompose "SHA1WithRSA" into "SHA1" and "RSA"
+     * so that we can check the "SHA1" and "RSA" algorithm constraints
+     * separately.
+     * <p>
+     * Please override the method if need to support more name pattern.
+     */
+    public Set<String> decompose(String algorithm) {
+        if (algorithm == null || algorithm.length() == 0) {
+            return new HashSet<>();
+        }
+
+        Set<String> elements = decomposeImpl(algorithm);
 
         // In Java standard algorithm name specification, for different
         // purpose, the SHA-1 and SHA-2 algorithm names are different. For
@@ -127,4 +133,40 @@
         return elements;
     }
 
+    private static void hasLoop(Set<String> elements, String find, String replace) {
+        if (elements.contains(find)) {
+            if (!elements.contains(replace)) {
+                elements.add(replace);
+}
+            elements.remove(find);
+        }
+    }
+
+    /*
+     * This decomposes a standard name into sub-elements with a consistent
+     * message digest algorithm name to avoid overly complicated checking.
+     */
+    public static Set<String> decomposeOneHash(String algorithm) {
+        if (algorithm == null || algorithm.length() == 0) {
+            return new HashSet<>();
+        }
+
+        Set<String> elements = decomposeImpl(algorithm);
+
+        hasLoop(elements, "SHA-1", "SHA1");
+        hasLoop(elements, "SHA-224", "SHA224");
+        hasLoop(elements, "SHA-256", "SHA256");
+        hasLoop(elements, "SHA-384", "SHA384");
+        hasLoop(elements, "SHA-512", "SHA512");
+
+        return elements;
+    }
+
+    /*
+     * The provided message digest algorithm name will return a consistent
+     * naming scheme.
+     */
+    public static String hashName(String algorithm) {
+        return algorithm.replace("-", "");
+    }
 }
diff --git a/ojluni/src/main/java/sun/security/util/AnchorCertificates.java b/ojluni/src/main/java/sun/security/util/AnchorCertificates.java
new file mode 100644
index 0000000..6bc0030
--- /dev/null
+++ b/ojluni/src/main/java/sun/security/util/AnchorCertificates.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016, 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 sun.security.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.AccessController;
+import java.security.KeyStore;
+import java.security.PrivilegedAction;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.HashSet;
+
+import sun.security.x509.X509CertImpl;
+
+/**
+ * The purpose of this class is to determine the trust anchor certificates is in
+ * the cacerts file.  This is used for PKIX CertPath checking.
+ */
+public class AnchorCertificates {
+
+    private static final Debug debug = Debug.getInstance("certpath");
+    private static final String HASH = "SHA-256";
+    private static HashSet<String> certs;
+
+    static  {
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                File f = new File(System.getProperty("java.home"),
+                        "lib/security/cacerts");
+                KeyStore cacerts;
+                try {
+                    cacerts = KeyStore.getInstance("JKS");
+                    try (FileInputStream fis = new FileInputStream(f)) {
+                        cacerts.load(fis, "changeit".toCharArray());
+                        certs = new HashSet<>();
+                        Enumeration<String> list = cacerts.aliases();
+                        String alias;
+                        while (list.hasMoreElements()) {
+                            alias = list.nextElement();
+                            // Check if this cert is labeled a trust anchor.
+                            if (alias.contains(" [jdk")) {
+                                X509Certificate cert = (X509Certificate) cacerts
+                                        .getCertificate(alias);
+                                certs.add(X509CertImpl.getFingerprint(HASH, cert));
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    if (debug != null) {
+                        debug.println("Error parsing cacerts");
+                    }
+                    e.printStackTrace();
+                }
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Checks if a certificate is a trust anchor.
+     *
+     * @param cert the certificate to check
+     * @return true if the certificate is trusted.
+     */
+    public static boolean contains(X509Certificate cert) {
+        String key = X509CertImpl.getFingerprint(HASH, cert);
+        boolean result = certs.contains(key);
+        if (result && debug != null) {
+            debug.println("AnchorCertificate.contains: matched " +
+                    cert.getSubjectDN());
+        }
+        return result;
+    }
+
+    private AnchorCertificates() {}
+}
diff --git a/ojluni/src/main/java/sun/security/util/CertConstraintParameters.java b/ojluni/src/main/java/sun/security/util/CertConstraintParameters.java
new file mode 100644
index 0000000..9f7a938
--- /dev/null
+++ b/ojluni/src/main/java/sun/security/util/CertConstraintParameters.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016, 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 sun.security.util;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * This class is a wrapper for keeping state and passing objects between PKIX,
+ * AlgorithmChecker, and DisabledAlgorithmConstraints.
+ */
+public class CertConstraintParameters {
+    // A certificate being passed to check against constraints.
+    private final X509Certificate cert;
+
+    // This is true if the trust anchor in the certificate chain matches a cert
+    // in AnchorCertificates
+    private final boolean trustedMatch;
+
+    public CertConstraintParameters(X509Certificate c, boolean match) {
+        cert = c;
+        trustedMatch = match;
+    }
+
+    public CertConstraintParameters(X509Certificate c) {
+        this(c, false);
+    }
+
+    // Returns if the trust anchor has a match if anchor checking is enabled.
+    public boolean isTrustedMatch() {
+        return trustedMatch;
+    }
+
+    public X509Certificate getCertificate() {
+        return cert;
+    }
+}
diff --git a/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java b/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java
index 05da9ba..d8c1462 100644
--- a/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java
+++ b/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2015, 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
@@ -28,12 +28,14 @@
 import java.security.CryptoPrimitive;
 import java.security.AlgorithmParameters;
 import java.security.Key;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertPathValidatorException.BasicReason;
+import java.security.cert.X509Certificate;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 
@@ -44,6 +46,7 @@
  * for the syntax of the disabled algorithm string.
  */
 public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints {
+    private static final Debug debug = Debug.getInstance("certpath");
 
     // the known security property, jdk.certpath.disabledAlgorithms
     public final static String PROPERTY_CERTPATH_DISABLED_ALGS =
@@ -53,13 +56,9 @@
     public final static String PROPERTY_TLS_DISABLED_ALGS =
             "jdk.tls.disabledAlgorithms";
 
-    private final static Map<String, String[]> disabledAlgorithmsMap =
-                                                            new HashMap<>();
-    private final static Map<String, KeySizeConstraints> keySizeConstraintsMap =
-                                                            new HashMap<>();
 
     private final String[] disabledAlgorithms;
-    private final KeySizeConstraints keySizeConstraints;
+    private final Constraints algorithmConstraints;
 
     /**
      * Initialize algorithm constraints with the specified security property.
@@ -71,14 +70,25 @@
         this(propertyName, new AlgorithmDecomposer());
     }
 
+    /**
+     * Initialize algorithm constraints with the specified security property
+     * for a specific usage type.
+     *
+     * @param propertyName the security property name that define the disabled
+     *        algorithm constraints
+     * @param decomposer an alternate AlgorithmDecomposer.
+     */
     public DisabledAlgorithmConstraints(String propertyName,
             AlgorithmDecomposer decomposer) {
         super(decomposer);
-        disabledAlgorithms = getAlgorithms(disabledAlgorithmsMap, propertyName);
-        keySizeConstraints = getKeySizeConstraints(disabledAlgorithms,
-                propertyName);
+        disabledAlgorithms = getAlgorithms(propertyName);
+        algorithmConstraints = new Constraints(disabledAlgorithms);
     }
 
+    /*
+     * This only checks if the algorithm has been completely disabled.  If
+     * there are keysize or other limit, this method allow the algorithm.
+     */
     @Override
     final public boolean permits(Set<CryptoPrimitive> primitives,
             String algorithm, AlgorithmParameters parameters) {
@@ -90,11 +100,19 @@
         return checkAlgorithm(disabledAlgorithms, algorithm, decomposer);
     }
 
+    /*
+     * Checks if the key algorithm has been disabled or constraints have been
+     * placed on the key.
+     */
     @Override
     final public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
         return checkConstraints(primitives, "", key, null);
     }
 
+    /*
+     * Checks if the key algorithm has been disabled or if constraints have
+     * been placed on the key.
+     */
     @Override
     final public boolean permits(Set<CryptoPrimitive> primitives,
             String algorithm, Key key, AlgorithmParameters parameters) {
@@ -106,7 +124,39 @@
         return checkConstraints(primitives, algorithm, key, parameters);
     }
 
-    // Check algorithm constraints
+    /*
+     * Check if a x509Certificate object is permitted.  Check if all
+     * algorithms are allowed, certificate constraints, and the
+     * public key against key constraints.
+     *
+     * Uses new style permit() which throws exceptions.
+     */
+    public final void permits(Set<CryptoPrimitive> primitives,
+            CertConstraintParameters cp) throws CertPathValidatorException {
+        checkConstraints(primitives, cp);
+    }
+
+    /*
+     * Check if Certificate object is within the constraints.
+     * Uses new style permit() which throws exceptions.
+     */
+    public final void permits(Set<CryptoPrimitive> primitives,
+            X509Certificate cert) throws CertPathValidatorException {
+        checkConstraints(primitives, new CertConstraintParameters(cert));
+    }
+
+    // Check if a string is contained inside the property
+    public boolean checkProperty(String param) {
+        param = param.toLowerCase(Locale.ENGLISH);
+        for (String block : disabledAlgorithms) {
+            if (block.toLowerCase(Locale.ENGLISH).indexOf(param) >= 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Check algorithm constraints with key and algorithm
     private boolean checkConstraints(Set<CryptoPrimitive> primitives,
             String algorithm, Key key, AlgorithmParameters parameters) {
 
@@ -115,7 +165,7 @@
             throw new IllegalArgumentException("The key cannot be null");
         }
 
-        // check the target algorithm
+        // check the signature algorithm
         if (algorithm != null && algorithm.length() != 0) {
             if (!permits(primitives, algorithm, parameters)) {
                 return false;
@@ -128,97 +178,203 @@
         }
 
         // check the key constraints
-        if (keySizeConstraints.disables(key)) {
-            return false;
-        }
-
-        return true;
+        return algorithmConstraints.permits(key);
     }
 
-    private static KeySizeConstraints getKeySizeConstraints(
-            String[] disabledAlgorithms, String propertyName) {
-        synchronized (keySizeConstraintsMap) {
-            if(!keySizeConstraintsMap.containsKey(propertyName)) {
-                // map the key constraints
-                KeySizeConstraints keySizeConstraints =
-                        new KeySizeConstraints(disabledAlgorithms);
-                keySizeConstraintsMap.put(propertyName, keySizeConstraints);
-            }
+    /*
+     * Check algorithm constraints with Certificate
+     * Uses new style permit() which throws exceptions.
+     */
+    private void checkConstraints(Set<CryptoPrimitive> primitives,
+            CertConstraintParameters cp) throws CertPathValidatorException {
 
-            return keySizeConstraintsMap.get(propertyName);
+        X509Certificate cert = cp.getCertificate();
+        String algorithm = cert.getSigAlgName();
+
+        // Check signature algorithm is not disabled
+        if (!permits(primitives, algorithm, null)) {
+            throw new CertPathValidatorException(
+                    "Algorithm constraints check failed on disabled "+
+                            "signature algorithm: " + algorithm,
+                    null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
         }
+
+        // Check key algorithm is not disabled
+        if (!permits(primitives, cert.getPublicKey().getAlgorithm(), null)) {
+            throw new CertPathValidatorException(
+                    "Algorithm constraints check failed on disabled "+
+                            "public key algorithm: " + algorithm,
+                    null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+        }
+
+        // Check the certificate and key constraints
+        algorithmConstraints.permits(cp);
+
     }
 
     /**
-     * key constraints
+     * Key and Certificate Constraints
+     *
+     * The complete disabling of an algorithm is not handled by Constraints or
+     * Constraint classes.  That is addressed with
+     *   permit(Set<CryptoPrimitive>, String, AlgorithmParameters)
+     *
+     * When passing a Key to permit(), the boolean return values follow the
+     * same as the interface class AlgorithmConstraints.permit().  This is to
+     * maintain compatibility:
+     * 'true' means the operation is allowed.
+     * 'false' means it failed the constraints and is disallowed.
+     *
+     * When passing CertConstraintParameters through permit(), an exception
+     * will be thrown on a failure to better identify why the operation was
+     * disallowed.
      */
-    private static class KeySizeConstraints {
-        private static final Pattern pattern = Pattern.compile(
-                "(\\S+)\\s+keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
 
-        private Map<String, Set<KeySizeConstraint>> constraintsMap =
-            Collections.synchronizedMap(
-                        new HashMap<String, Set<KeySizeConstraint>>());
+    private static class Constraints {
+        private Map<String, Set<Constraint>> constraintsMap = new HashMap<>();
+        private static final Pattern keySizePattern = Pattern.compile(
+                "keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
 
-        public KeySizeConstraints(String[] restrictions) {
-            for (String restriction : restrictions) {
-                if (restriction == null || restriction.isEmpty()) {
+        public Constraints(String[] constraintArray) {
+            for (String constraintEntry : constraintArray) {
+                if (constraintEntry == null || constraintEntry.isEmpty()) {
                     continue;
                 }
 
-                Matcher matcher = pattern.matcher(restriction);
-                if (matcher.matches()) {
-                    String algorithm = matcher.group(1);
+                constraintEntry = constraintEntry.trim();
+                if (debug != null) {
+                    debug.println("Constraints: " + constraintEntry);
+                }
 
-                    KeySizeConstraint.Operator operator =
-                             KeySizeConstraint.Operator.of(matcher.group(2));
-                    int length = Integer.parseInt(matcher.group(3));
+                // Check if constraint is a complete disabling of an
+                // algorithm or has conditions.
+                String algorithm;
+                String policy;
+                int space = constraintEntry.indexOf(' ');
+                if (space > 0) {
+                    algorithm = AlgorithmDecomposer.hashName(
+                            constraintEntry.substring(0, space).
+                                    toUpperCase(Locale.ENGLISH));
+                    policy = constraintEntry.substring(space + 1);
+                } else {
+                    constraintsMap.computeIfAbsent(
+                            constraintEntry.toUpperCase(Locale.ENGLISH),
+                            k -> new HashSet<>());
+                    continue;
+                }
 
-                    algorithm = algorithm.toLowerCase(Locale.ENGLISH);
+                // Convert constraint conditions into Constraint classes
+                Constraint c, lastConstraint = null;
+                // Allow only one jdkCA entry per constraint entry
+                boolean jdkCALimit = false;
 
-                    synchronized (constraintsMap) {
-                        if (!constraintsMap.containsKey(algorithm)) {
-                            constraintsMap.put(algorithm,
-                                new HashSet<KeySizeConstraint>());
+                for (String entry : policy.split("&")) {
+                    entry = entry.trim();
+
+                    Matcher matcher = keySizePattern.matcher(entry);
+                    if (matcher.matches()) {
+                        if (debug != null) {
+                            debug.println("Constraints set to keySize: " +
+                                    entry);
                         }
+                        c = new KeySizeConstraint(algorithm,
+                                KeySizeConstraint.Operator.of(matcher.group(1)),
+                                Integer.parseInt(matcher.group(2)));
 
-                        Set<KeySizeConstraint> constraintSet =
-                            constraintsMap.get(algorithm);
-                        KeySizeConstraint constraint =
-                            new KeySizeConstraint(operator, length);
-                        constraintSet.add(constraint);
+                    } else if (entry.equalsIgnoreCase("jdkCA")) {
+                        if (debug != null) {
+                            debug.println("Constraints set to jdkCA.");
+                        }
+                        if (jdkCALimit) {
+                            throw new IllegalArgumentException("Only one " +
+                                    "jdkCA entry allowed in property. " +
+                                    "Constraint: " + constraintEntry);
+                        }
+                        c = new jdkCAConstraint(algorithm);
+                        jdkCALimit = true;
+                    } else {
+                        throw new IllegalArgumentException("Error in security" +
+                                " property. Constraint unknown: " + entry);
                     }
+
+                    // Link multiple conditions for a single constraint
+                    // into a linked list.
+                    if (lastConstraint == null) {
+                        if (!constraintsMap.containsKey(algorithm)) {
+                            constraintsMap.putIfAbsent(algorithm,
+                                    new HashSet<>());
+                        }
+                        constraintsMap.get(algorithm).add(c);
+                    } else {
+                        lastConstraint.nextConstraint = c;
+                    }
+                    lastConstraint = c;
                 }
             }
         }
 
-        // Does this KeySizeConstraints disable the specified key?
-        public boolean disables(Key key) {
-            String algorithm = key.getAlgorithm().toLowerCase(Locale.ENGLISH);
-            synchronized (constraintsMap) {
-                if (constraintsMap.containsKey(algorithm)) {
-                    Set<KeySizeConstraint> constraintSet =
-                                        constraintsMap.get(algorithm);
-                    for (KeySizeConstraint constraint : constraintSet) {
-                        if (constraint.disables(key)) {
-                            return true;
-                        }
-                    }
-                }
-            }
+        // Get applicable constraints based off the signature algorithm
+        private Set<Constraint> getConstraints(String algorithm) {
+            return constraintsMap.get(algorithm);
+        }
 
+        // Check if KeySizeConstraints permit the specified key
+        public boolean permits(Key key) {
+            Set<Constraint> set = getConstraints(key.getAlgorithm());
+            if (set == null) {
+                return true;
+            }
+            for (Constraint constraint : set) {
+                if (!constraint.permits(key)) {
+                    if (debug != null) {
+                        debug.println("keySizeConstraint: failed key " +
+                                "constraint check " + KeyUtil.getKeySize(key));
+                    }
             return false;
         }
+            }
+        return true;
     }
 
-    /**
-     * Key size constraint.
-     *
-     * e.g.  "keysize <= 1024"
-     */
-    private static class KeySizeConstraint {
+        // Check if constraints permit this cert.
+        public void permits(CertConstraintParameters cp)
+                throws CertPathValidatorException {
+            X509Certificate cert = cp.getCertificate();
+
+            if (debug != null) {
+                debug.println("Constraints.permits(): " + cert.getSigAlgName());
+            }
+
+            // Get all signature algorithms to check for constraints
+            Set<String> algorithms =
+                    AlgorithmDecomposer.decomposeOneHash(cert.getSigAlgName());
+            if (algorithms == null || algorithms.isEmpty()) {
+                return;
+    }
+
+            // Attempt to add the public key algorithm to the set
+            algorithms.add(cert.getPublicKey().getAlgorithm());
+
+            // Check all applicable constraints
+            for (String algorithm : algorithms) {
+                Set<Constraint> set = getConstraints(algorithm);
+                if (set == null) {
+                    continue;
+                }
+                for (Constraint constraint : set) {
+                    constraint.permits(cp);
+                }
+            }
+        }
+                        }
+
+    // Abstract class for algorithm constraint checking
+    private abstract static class Constraint {
+        String algorithm;
+        Constraint nextConstraint = null;
+
         // operator
-        static enum Operator {
+        enum Operator {
             EQ,         // "=="
             NE,         // "!="
             LT,         // "<"
@@ -242,16 +398,77 @@
                         return GE;
                 }
 
-                throw new IllegalArgumentException(
-                        s + " is not a legal Operator");
+                throw new IllegalArgumentException("Error in security " +
+                        "property. " + s + " is not a legal Operator");
             }
         }
 
+        /**
+         * Check if an algorithm constraint permit this key to be used.
+         * @param key Public key
+         * @return true if constraints do not match
+         */
+        public boolean permits(Key key) {
+            return true;
+        }
+
+        /**
+         * Check if an algorithm constraint is permit this certificate to
+         * be used.
+         * @param cp CertificateParameter containing certificate and state info
+         * @return true if constraints do not match
+         */
+        public abstract void permits(CertConstraintParameters cp)
+                throws CertPathValidatorException;
+    }
+
+    /*
+     * This class contains constraints dealing with the certificate chain
+     * of the certificate.
+     */
+    private static class jdkCAConstraint extends Constraint {
+        jdkCAConstraint(String algo) {
+            algorithm = algo;
+        }
+
+        /*
+         * Check if each constraint fails and check if there is a linked
+         * constraint  Any permitted constraint will exit the linked list
+         * to allow the operation.
+         */
+        public void permits(CertConstraintParameters cp)
+                throws CertPathValidatorException {
+            if (debug != null) {
+                debug.println("jdkCAConstraints.permits(): " + algorithm);
+            }
+
+            // Return false if the chain has a trust anchor in cacerts
+            if (cp.isTrustedMatch()) {
+                if (nextConstraint != null) {
+                    nextConstraint.permits(cp);
+                    return;
+                }
+                throw new CertPathValidatorException(
+                        "Algorithm constraints check failed on certificate " +
+                                "anchor limits",
+                        null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+            }
+        }
+    }
+
+
+    /*
+     * This class contains constraints dealing with the key size
+     * support limits per algorithm.   e.g.  "keySize <= 1024"
+     */
+    private static class KeySizeConstraint extends Constraint {
+
         private int minSize;            // the minimal available key size
         private int maxSize;            // the maximal available key size
         private int prohibitedSize = -1;    // unavailable key sizes
 
-        public KeySizeConstraint(Operator operator, int length) {
+        public KeySizeConstraint(String algo, Operator operator, int length) {
+            algorithm = algo;
             switch (operator) {
                 case EQ:      // an unavailable key size
                     this.minSize = 0;
@@ -285,21 +502,58 @@
             }
         }
 
-        // Does this key constraint disable the specified key?
-        public boolean disables(Key key) {
-            int size = KeyUtil.getKeySize(key);
+        /*
+         * If we are passed a certificate, extract the public key and use it.
+         *
+         * Check if each constraint fails and check if there is a linked
+         * constraint  Any permitted constraint will exit the linked list
+         * to allow the operation.
+         */
+        public void permits(CertConstraintParameters cp)
+                throws CertPathValidatorException {
+            if (!permitsImpl(cp.getCertificate().getPublicKey())) {
+                if (nextConstraint != null) {
+                    nextConstraint.permits(cp);
+                    return;
+                }
+                throw new CertPathValidatorException(
+                        "Algorithm constraints check failed on keysize limits",
+                        null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+            }
+        }
 
+
+        // Check if key constraint disable the specified key
+        // Uses old style permit()
+        public boolean permits(Key key) {
+            // If we recursively find a constraint that permits us to use
+            // this key, return true and skip any other constraint checks.
+            if (nextConstraint != null && nextConstraint.permits(key)) {
+                return true;
+            }
+            if (debug != null) {
+                debug.println("KeySizeConstraints.permits(): " + algorithm);
+            }
+
+            return permitsImpl(key);
+        }
+
+        private boolean permitsImpl(Key key) {
+            // Verify this constraint is for this public key algorithm
+            if (algorithm.compareToIgnoreCase(key.getAlgorithm()) != 0) {
+                return true;
+            }
+
+            int size = KeyUtil.getKeySize(key);
             if (size == 0) {
-                return true;    // we don't allow any key of size 0.
+                return false;    // we don't allow any key of size 0.
             } else if (size > 0) {
-                return ((size < minSize) || (size > maxSize) ||
+                return !((size < minSize) || (size > maxSize) ||
                     (prohibitedSize == size));
             }   // Otherwise, the key size is not accessible. Conservatively,
                 // please don't disable such keys.
 
-            return false;
+            return true;
+        }
         }
     }
-
-}
-
diff --git a/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java b/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java
index afd7c99..9e76a3a 100644
--- a/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java
+++ b/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java
@@ -307,4 +307,16 @@
     public String getName() {
         return (NAME);
     }
+
+    /**
+     * Return the encoded key identifier, or null if not specified.
+     */
+    public byte[] getEncodedKeyIdentifier() throws IOException {
+        if (id != null) {
+            DerOutputStream derOut = new DerOutputStream();
+            id.encode(derOut);
+            return derOut.toByteArray();
+        }
+        return null;
+    }
 }
diff --git a/ojluni/src/main/java/sun/security/x509/X509CertImpl.java b/ojluni/src/main/java/sun/security/x509/X509CertImpl.java
index f9db2c5..de4d313 100644
--- a/ojluni/src/main/java/sun/security/x509/X509CertImpl.java
+++ b/ojluni/src/main/java/sun/security/x509/X509CertImpl.java
@@ -1970,7 +1970,7 @@
 // BEGIN Android-removed
 //    public String getFingerprint(String algorithm) {
 //        return fingerprints.computeIfAbsent(algorithm,
-//                x -> getCertificateFingerPrint(x));
+//                x -> getFingerprint(x, this));
 //    }
 // END Android-removed
 
@@ -1978,11 +1978,12 @@
      * Gets the requested finger print of the certificate. The result
      * only contains 0-9 and A-F. No small case, no colon.
      */
-    private String getCertificateFingerPrint(String mdAlg) {
+    public static String getFingerprint(String algorithm,
+            X509Certificate cert) {
         String fingerPrint = "";
         try {
-            byte[] encCertInfo = getEncoded();
-            MessageDigest md = MessageDigest.getInstance(mdAlg);
+            byte[] encCertInfo = cert.getEncoded();
+            MessageDigest md = MessageDigest.getInstance(algorithm);
             byte[] digest = md.digest(encCertInfo);
             StringBuffer buf = new StringBuffer();
             for (int i = 0; i < digest.length; i++) {
diff --git a/ojluni/src/main/native/PlainSocketImpl.c b/ojluni/src/main/native/PlainSocketImpl.c
index e2ba1dc..c4b5be7 100644
--- a/ojluni/src/main/native/PlainSocketImpl.c
+++ b/ojluni/src/main/native/PlainSocketImpl.c
@@ -90,33 +90,6 @@
 }
 
 /*
- * Create the marker file descriptor by establishing a loopback connection
- * which we shutdown but do not close the fd. The result is an fd that
- * can be used for read/write.
- */
-static int getMarkerFD()
-{
-    int sv[2];
-
-#ifdef AF_UNIX
-    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
-        return -1;
-    }
-#else
-    return -1;
-#endif
-
-    /*
-     * Finally shutdown sv[0] (any reads to this fd will get
-     * EOF; any writes will get an error).
-     */
-    JVM_SocketShutdown(sv[0], 2);
-    JVM_SocketClose(sv[1]);
-
-    return sv[0];
-}
-
-/*
  * Return the file descriptor given a PlainSocketImpl
  */
 static int getFD(JNIEnv *env, jobject this) {
@@ -162,724 +135,6 @@
 
 /*
  * Class:     java_net_PlainSocketImpl
- * Method:    socketCreate
- * Signature: (Z)V */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketCreate(JNIEnv *env, jobject this,
-                                           jboolean stream) {
-    jobject fdObj, ssObj;
-    int fd;
-    int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
-#ifdef AF_INET6
-    int domain = ipv6_available() ? AF_INET6 : AF_INET;
-#else
-    int domain = AF_INET;
-#endif
-
-    if (socketExceptionCls == NULL) {
-        jclass c = (*env)->FindClass(env, "java/net/SocketException");
-        CHECK_NULL(c);
-        socketExceptionCls = (jclass)(*env)->NewGlobalRef(env, c);
-        CHECK_NULL(socketExceptionCls);
-    }
-    fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-
-    if (fdObj == NULL) {
-        (*env)->ThrowNew(env, socketExceptionCls, "null fd object");
-        return;
-    }
-
-    if ((fd = JVM_Socket(domain, type, 0)) == JVM_IO_ERR) {
-        /* note: if you run out of fds, you may not be able to load
-         * the exception class, and get a NoClassDefFoundError
-         * instead.
-         */
-        NET_ThrowNew(env, errno, "can't create socket");
-        return;
-    }
-    tagSocket(env, fd);
-
-#ifdef AF_INET6
-    /* Disable IPV6_V6ONLY to ensure dual-socket support */
-    if (domain == AF_INET6) {
-        int 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 */
-
-    /*
-     * If this is a server socket then enable SO_REUSEADDR
-     * automatically and set to non blocking.
-     */
-    ssObj = (*env)->GetObjectField(env, this, psi_serverSocketID);
-    if (ssObj != NULL) {
-        int arg = 1;
-        SET_NONBLOCKING(fd);
-        if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg,
-                           sizeof(arg)) < 0) {
-            NET_ThrowNew(env, errno, "cannot set SO_REUSEADDR");
-            untagSocket(env, fd);
-            close(fd);
-            return;
-        }
-    }
-
-    (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
-}
-
-/*
- * inetAddress is the address object passed to the socket connect
- * call.
- *
- * Class:     java_net_PlainSocketImpl
- * Method:    socketConnect
- * Signature: (Ljava/net/InetAddress;I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketConnect(JNIEnv *env, jobject this,
-                                            jobject iaObj, jint port,
-                                            jint timeout)
-{
-    jint localport = (*env)->GetIntField(env, this, psi_localportID);
-    int len = 0;
-
-    /* fdObj is the FileDescriptor field on this */
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-
-    jclass clazz = (*env)->GetObjectClass(env, this);
-
-    jobject fdLock;
-
-    jint trafficClass = (*env)->GetIntField(env, this, psi_trafficClassID);
-
-    /* fd is an int field on iaObj */
-    jint fd;
-
-    SOCKADDR him;
-    /* The result of the connection */
-    int connect_rv = -1;
-
-    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, "inet address argument null.");
-        return;
-    }
-
-    /* connect */
-    if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) {
-      return;
-    }
-    setDefaultScopeID(env, (struct sockaddr *)&him);
-
-#ifdef AF_INET6
-    if (trafficClass != 0 && ipv6_available()) {
-        NET_SetTrafficClass((struct sockaddr *)&him, trafficClass);
-    }
-#endif /* AF_INET6 */
-    if (timeout <= 0) {
-        connect_rv = NET_Connect(fd, (struct sockaddr *)&him, len);
-#ifdef __solaris__
-        if (connect_rv == JVM_IO_ERR && errno == EINPROGRESS ) {
-
-            /* This can happen if a blocking connect is interrupted by a signal.
-             * See 6343810.
-             */
-            while (1) {
-#ifndef USE_SELECT
-                {
-                    struct pollfd pfd;
-                    pfd.fd = fd;
-                    pfd.events = POLLOUT;
-
-                    connect_rv = NET_Poll(&pfd, 1, -1);
-                }
-#else
-                {
-                    fd_set wr, ex;
-
-                    FD_ZERO(&wr);
-                    FD_SET(fd, &wr);
-                    FD_ZERO(&ex);
-                    FD_SET(fd, &ex);
-
-                    connect_rv = NET_Select(fd+1, 0, &wr, &ex, 0);
-                }
-#endif
-
-                if (connect_rv == JVM_IO_ERR) {
-                    if (errno == EINTR) {
-                        continue;
-                    } else {
-                        break;
-                    }
-                }
-                if (connect_rv > 0) {
-                    int optlen;
-                    /* has connection been established */
-                    optlen = sizeof(connect_rv);
-                    if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR,
-                                        (void*)&connect_rv, &optlen) <0) {
-                        connect_rv = errno;
-                    }
-
-                    if (connect_rv != 0) {
-                        /* restore errno */
-                        errno = connect_rv;
-                        connect_rv = JVM_IO_ERR;
-                    }
-                    break;
-                }
-            }
-        }
-#endif
-    } else {
-        /*
-         * A timeout was specified. We put the socket into non-blocking
-         * mode, connect, and then wait for the connection to be
-         * established, fail, or timeout.
-         */
-        SET_NONBLOCKING(fd);
-
-        /* no need to use NET_Connect as non-blocking */
-        connect_rv = connect(fd, (struct sockaddr *)&him, len);
-
-        /* connection not established immediately */
-        if (connect_rv != 0) {
-            int optlen;
-            jlong prevTime = JVM_CurrentTimeMillis(env, 0);
-
-            if (errno != EINPROGRESS) {
-                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
-                             "connect failed");
-                SET_BLOCKING(fd);
-                return;
-            }
-
-            /*
-             * Wait for the connection to be established or a
-             * timeout occurs. poll/select needs to handle EINTR in
-             * case lwp sig handler redirects any process signals to
-             * this thread.
-             */
-            while (1) {
-                jlong newTime;
-#ifndef USE_SELECT
-                {
-                    struct pollfd pfd;
-                    pfd.fd = fd;
-                    pfd.events = POLLOUT;
-
-                    errno = 0;
-                    connect_rv = NET_Poll(&pfd, 1, timeout);
-                }
-#else
-                {
-                    fd_set wr, ex;
-                    struct timeval t;
-
-                    t.tv_sec = timeout / 1000;
-                    t.tv_usec = (timeout % 1000) * 1000;
-
-                    FD_ZERO(&wr);
-                    FD_SET(fd, &wr);
-                    FD_ZERO(&ex);
-                    FD_SET(fd, &ex);
-
-                    errno = 0;
-                    connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t);
-                }
-#endif
-
-                if (connect_rv >= 0) {
-                    break;
-                }
-                if (errno != EINTR) {
-                    break;
-                }
-
-                /*
-                 * The poll was interrupted so adjust timeout and
-                 * restart
-                 */
-                newTime = JVM_CurrentTimeMillis(env, 0);
-                timeout -= (newTime - prevTime);
-                if (timeout <= 0) {
-                    connect_rv = 0;
-                    break;
-                }
-                prevTime = newTime;
-
-            } /* while */
-
-            if (connect_rv == 0) {
-                JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
-                            "connect timed out");
-
-                /*
-                 * Timeout out but connection may still be established.
-                 * At the high level it should be closed immediately but
-                 * just in case we make the socket blocking again and
-                 * shutdown input & output.
-                 */
-                SET_BLOCKING(fd);
-                JVM_SocketShutdown(fd, 2);
-                return;
-            }
-
-            /* has connection been established */
-            optlen = sizeof(connect_rv);
-            if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void*)&connect_rv,
-                               &optlen) <0) {
-                connect_rv = errno;
-            }
-        }
-
-        /* make socket blocking again */
-        SET_BLOCKING(fd);
-
-        /* restore errno */
-        if (connect_rv != 0) {
-            errno = connect_rv;
-            connect_rv = JVM_IO_ERR;
-        }
-    }
-
-    /* report the appropriate exception */
-    if (connect_rv < 0) {
-
-#ifdef __linux__
-        /*
-         * Linux/GNU distribution setup /etc/hosts so that
-         * InetAddress.getLocalHost gets back the loopback address
-         * rather than the host address. Thus a socket can be
-         * bound to the loopback address and the connect will
-         * fail with EADDRNOTAVAIL. In addition the Linux kernel
-         * returns the wrong error in this case - it returns EINVAL
-         * instead of EADDRNOTAVAIL. We handle this here so that
-         * a more descriptive exception text is used.
-         */
-        if (connect_rv == JVM_IO_ERR && errno == EINVAL) {
-            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
-                "Invalid argument or cannot assign requested address");
-            return;
-        }
-#endif
-        if (connect_rv == JVM_IO_INTR) {
-            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
-                            "operation interrupted");
-#if defined(EPROTO)
-        } else if (errno == EPROTO) {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ProtocolException",
-                           "Protocol error");
-#endif
-        } else if (errno == ECONNREFUSED) {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
-                           "Connection refused");
-        } else if (errno == ETIMEDOUT) {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
-                           "Connection timed out");
-        } else if (errno == EHOSTUNREACH) {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException",
-                           "Host unreachable");
-        } else if (errno == EADDRNOTAVAIL) {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException",
-                             "Address not available");
-        } else if ((errno == EISCONN) || (errno == EBADF)) {
-            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
-                            "Socket closed");
-        } else {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "connect failed");
-        }
-        return;
-    }
-
-    (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
-
-    /* set the remote peer address and port */
-    (*env)->SetObjectField(env, this, psi_addressID, iaObj);
-    (*env)->SetIntField(env, this, psi_portID, port);
-
-    /*
-     * we need to initialize the local port field if bind was called
-     * previously to the connect (by the client) then localport field
-     * will already be initialized
-     */
-    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.
-         */
-        len = SOCKADDR_LEN;
-        if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
-                           "Error getting socket name");
-        } else {
-            localport = NET_GetPortFromSockaddr((struct sockaddr *)&him);
-            (*env)->SetIntField(env, this, psi_localportID, localport);
-        }
-    }
-}
-
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketBind
- * Signature: (Ljava/net/InetAddress;I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketBind(JNIEnv *env, jobject this,
-                                         jobject iaObj, jint localport) {
-
-    /* fdObj is the FileDescriptor field on this */
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-    /* fd is an int field on fdObj */
-    int fd;
-    int len;
-    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;
-    }
-
-    /* set the address */
-    (*env)->SetObjectField(env, this, psi_addressID, iaObj);
-
-    /* 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, psi_localportID, localport);
-    } else {
-        (*env)->SetIntField(env, this, psi_localportID, localport);
-    }
-}
-
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketListen
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketListen (JNIEnv *env, jobject this,
-                                            jint count)
-{
-    /* this FileDescriptor fd field */
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-    /* fdObj's int fd field */
-    int fd;
-
-    if (IS_NULL(fdObj)) {
-        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
-                        "Socket closed");
-        return;
-    } else {
-        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
-    }
-
-    /*
-     * Workaround for bugid 4101691 in Solaris 2.6. See 4106600.
-     * If listen backlog is Integer.MAX_VALUE then subtract 1.
-     */
-    if (count == 0x7fffffff)
-        count -= 1;
-
-    if (JVM_Listen(fd, count) == JVM_IO_ERR) {
-        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
-                       "Listen failed");
-    }
-}
-
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketAccept
- * Signature: (Ljava/net/SocketImpl;)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketAccept(JNIEnv *env, jobject this,
-                                           jobject socket)
-{
-    /* fields on this */
-    int port;
-    jint timeout = (*env)->GetIntField(env, this, psi_timeoutID);
-    jlong prevTime = 0;
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-
-    /* the FileDescriptor field on socket */
-    jobject socketFdObj;
-    /* the InetAddress field on socket */
-    jobject socketAddressObj;
-
-    /* the ServerSocket fd int field on fdObj */
-    jint fd;
-
-    /* accepted fd */
-    jint newfd;
-
-    SOCKADDR him;
-    int len;
-
-    len = SOCKADDR_LEN;
-
-    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(socket)) {
-        JNU_ThrowNullPointerException(env, "socket is null");
-        return;
-    }
-
-    /*
-     * accept connection but ignore ECONNABORTED indicating that
-     * connection was eagerly accepted by the OS but was reset
-     * before accept() was called.
-     *
-     * If accept timeout in place and timeout is adjusted with
-     * each ECONNABORTED or EWOULDBLOCK to ensure that semantics
-     * of timeout are preserved.
-     */
-    for (;;) {
-        int ret;
-
-        /* first usage pick up current time */
-        if (prevTime == 0 && timeout > 0) {
-            prevTime = JVM_CurrentTimeMillis(env, 0);
-        }
-
-        /* passing a timeout of 0 to poll will return immediately,
-           but in the case of ServerSocket 0 means infinite. */
-        if (timeout <= 0) {
-            ret = NET_Timeout(fd, -1);
-        } else {
-            ret = NET_Timeout(fd, timeout);
-        }
-
-        if (ret == 0) {
-            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
-                            "Accept timed out");
-            return;
-        } else if (ret == JVM_IO_ERR) {
-            if (errno == EBADF) {
-               JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
-            } else {
-               NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Accept failed");
-            }
-            return;
-        } else if (ret == JVM_IO_INTR) {
-            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
-                            "operation interrupted");
-            return;
-        }
-
-        newfd = NET_Accept(fd, (struct sockaddr *)&him, (jint*)&len);
-
-        /* connection accepted */
-        if (newfd >= 0) {
-            SET_BLOCKING(newfd);
-            break;
-        }
-
-        /* non (ECONNABORTED or EWOULDBLOCK) error */
-        if (!(errno == ECONNABORTED || errno == EWOULDBLOCK)) {
-            break;
-        }
-
-        /* ECONNABORTED or EWOULDBLOCK error so adjust timeout if there is one. */
-        if (timeout) {
-            jlong currTime = JVM_CurrentTimeMillis(env, 0);
-            timeout -= (currTime - prevTime);
-
-            if (timeout <= 0) {
-                JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
-                                "Accept timed out");
-                return;
-            }
-            prevTime = currTime;
-        }
-    }
-
-    if (newfd < 0) {
-        if (newfd == -2) {
-            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
-                            "operation interrupted");
-        } else {
-            if (errno == EINVAL) {
-                errno = EBADF;
-            }
-            if (errno == EBADF) {
-                JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
-            } else {
-                NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Accept failed");
-            }
-        }
-        return;
-    }
-
-    /*
-     * fill up the remote peer port and address in the new socket structure.
-     */
-    socketAddressObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &port);
-    if (socketAddressObj == NULL) {
-        /* should be pending exception */
-        untagSocket(env, fd);
-        close(newfd);
-        return;
-    }
-
-    /*
-     * Populate SocketImpl.fd.fd
-     */
-    socketFdObj = (*env)->GetObjectField(env, socket, psi_fdID);
-    (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, newfd);
-
-    (*env)->SetObjectField(env, socket, psi_addressID, socketAddressObj);
-    (*env)->SetIntField(env, socket, psi_portID, port);
-    /* also fill up the local port information */
-     port = (*env)->GetIntField(env, this, psi_localportID);
-    (*env)->SetIntField(env, socket, psi_localportID, port);
-}
-
-
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketAvailable
- * Signature: ()I
- */
-JNIEXPORT jint JNICALL
-PlainSocketImpl_socketAvailable(JNIEnv *env, jobject this) {
-
-    jint ret = -1;
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-    jint fd;
-
-    if (IS_NULL(fdObj)) {
-        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
-                        "Socket closed");
-        return -1;
-    } else {
-        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
-    }
-    /* JVM_SocketAvailable returns 0 for failure, 1 for success */
-    if (!JVM_SocketAvailable(fd, &ret)){
-        if (errno == ECONNRESET) {
-            JNU_ThrowByName(env, "sun/net/ConnectionResetException", "");
-        } else {
-            NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
-                                         "ioctl FIONREAD failed");
-        }
-    }
-    return ret;
-}
-
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketClose0
- * Signature: (Z)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketClose0(JNIEnv *env, jobject this,
-                                          jboolean useDeferredClose) {
-
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-    jint fd;
-
-    if (IS_NULL(fdObj)) {
-        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
-                        "socket already closed");
-        return;
-    } else {
-        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
-    }
-    if (fd != -1) {
-        int marker_fd = -1;
-        if (useDeferredClose) {
-            marker_fd = getMarkerFD();
-        }
-        if (useDeferredClose && marker_fd >= 0) {
-            NET_Dup2(marker_fd, fd);
-            NET_SocketClose(marker_fd);
-        } else {
-            (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
-            NET_SocketClose(fd);
-        }
-    }
-}
-
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketShutdown
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketShutdown(JNIEnv *env, jobject this,
-                                             jint howto)
-{
-
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-    jint fd;
-
-    /*
-     * WARNING: THIS NEEDS LOCKING. ALSO: SHOULD WE CHECK for fd being
-     * -1 already?
-     */
-    if (IS_NULL(fdObj)) {
-        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
-                        "socket already closed");
-        return;
-    } else {
-        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
-    }
-    JVM_SocketShutdown(fd, howto);
-}
-
-
-/*
- * Class:     java_net_PlainSocketImpl
  * Method:    socketSetOption0
  * Signature: (IZLjava/lang/Object;)V
  */
@@ -1067,56 +322,9 @@
 }
 
 
-/*
- * Class:     java_net_PlainSocketImpl
- * Method:    socketSendUrgentData
- * Signature: (B)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketSendUrgentData(JNIEnv *env, jobject this,
-                                             jint data) {
-    /* The fd field */
-    jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-    int n, fd;
-    unsigned char d = data & 0xFF;
-
-    if (IS_NULL(fdObj)) {
-        JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
-        return;
-    } else {
-        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
-        /* Bug 4086704 - If the Socket associated with this file descriptor
-         * was closed (sysCloseFD), the the file descriptor is set to -1.
-         */
-        if (fd == -1) {
-            JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
-            return;
-        }
-
-    }
-    n = JVM_Send(fd, (char *)&d, 1, MSG_OOB);
-    if (n == JVM_IO_ERR) {
-        NET_ThrowByNameWithLastError(env, "java/io/IOException", "Write failed");
-        return;
-    }
-    if (n == JVM_IO_INTR) {
-        JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
-        return;
-    }
-}
-
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(PlainSocketImpl, socketSendUrgentData, "(I)V"),
   NATIVE_METHOD(PlainSocketImpl, socketGetOption, "(ILjava/lang/Object;)I"),
   NATIVE_METHOD(PlainSocketImpl, socketSetOption0, "(IZLjava/lang/Object;)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketShutdown, "(I)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketClose0, "(Z)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketAccept, "(Ljava/net/SocketImpl;)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketAvailable, "()I"),
-  NATIVE_METHOD(PlainSocketImpl, socketListen, "(I)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketBind, "(Ljava/net/InetAddress;I)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketConnect, "(Ljava/net/InetAddress;II)V"),
-  NATIVE_METHOD(PlainSocketImpl, socketCreate, "(Z)V"),
 };
 
 void register_java_net_PlainSocketImpl(JNIEnv* env) {
diff --git a/ojluni/src/test/java/nio/file/attribute/AclFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/AclFileAttributeViewTest.java
deleted file mode 100644
index 2d9e32c..0000000
--- a/ojluni/src/test/java/nio/file/attribute/AclFileAttributeViewTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (c) 2008, 2011, 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.
- *
- * 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.
- */
-
-/* @test
- * @bug 4313887 6838333 6891404
- * @summary Unit test for java.nio.file.attribute.AclFileAttribueView
- * @library ../..
- */
-// Android-changed: Adapted from
-// jdk/test/java/nio/file/attribute/AclFileAttributeView/Basic.java
-// Android-changed: Added package & Test import
-package test.java.nio.file.attribute;
-import org.testng.annotations.Test;
-import test.java.nio.file.TestUtil;
-
-import java.nio.file.*;
-import java.nio.file.attribute.*;
-import java.io.IOException;
-import java.util.*;
-
-import static java.nio.file.attribute.AclEntryType.*;
-import static java.nio.file.attribute.AclEntryPermission.*;
-import static java.nio.file.attribute.AclEntryFlag.*;
-
-// Android-changed: Renamed from "Basic"
-public class AclFileAttributeViewTest {
-
-    static void printAcl(List<AclEntry> acl) {
-        for (AclEntry entry: acl) {
-            System.out.format("  %s%n", entry);
-        }
-    }
-
-    // sanity check read and writing ACL
-    static void testReadWrite(Path dir) throws IOException {
-        Path file = dir.resolve("foo");
-        if (Files.notExists(file))
-            Files.createFile(file);
-
-        AclFileAttributeView view =
-            Files.getFileAttributeView(file, AclFileAttributeView.class);
-
-        // print existing ACL
-        List<AclEntry> acl = view.getAcl();
-        System.out.println(" -- current ACL --");
-        printAcl(acl);
-
-        // insert entry to grant owner read access
-        UserPrincipal owner = view.getOwner();
-        AclEntry entry = AclEntry.newBuilder()
-            .setType(ALLOW)
-            .setPrincipal(owner)
-            .setPermissions(READ_DATA, READ_ATTRIBUTES)
-            .build();
-        System.out.println(" -- insert (entry 0) --");
-        System.out.format("  %s%n", entry);
-        acl.add(0, entry);
-        view.setAcl(acl);
-
-        // re-ACL and check entry
-        List<AclEntry> newacl = view.getAcl();
-        System.out.println(" -- current ACL --");
-        printAcl(acl);
-        if (!newacl.get(0).equals(entry)) {
-            throw new RuntimeException("Entry 0 is not expected");
-        }
-
-        // if PosixFileAttributeView then repeat test with OWNER@
-        if (Files.getFileStore(file).supportsFileAttributeView("posix")) {
-            owner = file.getFileSystem().getUserPrincipalLookupService()
-                .lookupPrincipalByName("OWNER@");
-            entry = AclEntry.newBuilder(entry).setPrincipal(owner).build();
-
-            System.out.println(" -- replace (entry 0) --");
-            System.out.format("  %s%n", entry);
-
-            acl.set(0, entry);
-            view.setAcl(acl);
-            newacl = view.getAcl();
-            System.out.println(" -- current ACL --");
-            printAcl(acl);
-            if (!newacl.get(0).equals(entry)) {
-                throw new RuntimeException("Entry 0 is not expected");
-            }
-        }
-    }
-
-    static FileAttribute<List<AclEntry>> asAclAttribute(final List<AclEntry> acl) {
-        return new FileAttribute<List<AclEntry>>() {
-            public String name() { return "acl:acl"; }
-            public List<AclEntry> value() { return acl; }
-        };
-    }
-
-    static void assertEquals(List<AclEntry> actual, List<AclEntry> expected) {
-        if (!actual.equals(expected)) {
-            System.err.format("Actual: %s\n", actual);
-            System.err.format("Expected: %s\n", expected);
-            throw new RuntimeException("ACL not expected");
-        }
-    }
-
-    // sanity check create a file or directory with initial ACL
-    static void testCreateFile(Path dir) throws IOException {
-        UserPrincipal user = Files.getOwner(dir);
-        AclFileAttributeView view;
-
-        // create file with initial ACL
-        System.out.println("-- create file with initial ACL --");
-        Path file = dir.resolve("gus");
-        List<AclEntry> fileAcl = Arrays.asList(
-            AclEntry.newBuilder()
-                .setType(AclEntryType.ALLOW)
-                .setPrincipal(user)
-                .setPermissions(SYNCHRONIZE, READ_DATA, WRITE_DATA,
-                    READ_ATTRIBUTES, READ_ACL, WRITE_ATTRIBUTES, DELETE)
-                .build());
-        Files.createFile(file, asAclAttribute(fileAcl));
-        view = Files.getFileAttributeView(file, AclFileAttributeView.class);
-        assertEquals(view.getAcl(), fileAcl);
-
-        // create directory with initial ACL
-        System.out.println("-- create directory with initial ACL --");
-        Path subdir = dir.resolve("stuff");
-        List<AclEntry> dirAcl = Arrays.asList(
-            AclEntry.newBuilder()
-                .setType(AclEntryType.ALLOW)
-                .setPrincipal(user)
-                .setPermissions(SYNCHRONIZE, ADD_FILE, DELETE)
-                .build(),
-            AclEntry.newBuilder(fileAcl.get(0))
-                .setFlags(FILE_INHERIT)
-                .build());
-        Files.createDirectory(subdir, asAclAttribute(dirAcl));
-        view = Files.getFileAttributeView(subdir, AclFileAttributeView.class);
-        assertEquals(view.getAcl(), dirAcl);
-    }
-
-    // Android-changed: Removed args & added @Test
-    @Test
-    public static void main() throws IOException {
-        // use work directory rather than system temporary directory to
-        // improve chances that ACLs are supported
-        // Android-changed: Switched to temp dir due to permissions
-        // Path dir = Paths.get("./work" + new Random().nextInt());
-        // Files.createTempDirectory(dir);
-        Path dir = Files.createTempDirectory("acl");
-        try {
-            if (!Files.getFileStore(dir).supportsFileAttributeView("acl")) {
-                System.out.println("ACLs not supported - test skipped!");
-                return;
-            }
-            testReadWrite(dir);
-
-            // only currently feasible on Windows
-            if (System.getProperty("os.name").startsWith("Windows"))
-                testCreateFile(dir);
-
-        } finally {
-            TestUtil.removeAll(dir);
-        }
-    }
-}
diff --git a/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java b/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java
index b393981..c2a30bb 100644
--- a/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java
+++ b/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java
@@ -80,15 +80,17 @@
         boolean supportsCreationTimeRead = false;
         boolean supportsCreationTimeWrite = false;
         String os = System.getProperty("os.name");
-        if (os.contains("OS X") && Files.getFileStore(file).type().equals("hfs")) {
-            supportsCreationTimeRead = true;
-        } else if (os.startsWith("Windows")) {
-            String type = Files.getFileStore(file).type();
-            if (type.equals("NTFS") || type.equals("FAT")) {
-                supportsCreationTimeRead = true;
-                supportsCreationTimeWrite = true;
-            }
-        }
+        // Android-changed: This test is never run on Mac OS or windows hosts.
+        //
+        // if (os.contains("OS X") && Files.getFileStore(file).type().equals("hfs")) {
+        //     supportsCreationTimeRead = true;
+        // } else if (os.startsWith("Windows")) {
+        //     String type = Files.getFileStore(file).type();
+        //     if (type.equals("NTFS") || type.equals("FAT")) {
+        //         supportsCreationTimeRead = true;
+        //         supportsCreationTimeWrite = true;
+        //     }
+        // }
 
         /**
          * If the creation-time attribute is supported then change the file's
diff --git a/ojluni/src/test/java/nio/file/attribute/DosFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/DosFileAttributeViewTest.java
deleted file mode 100644
index 8386a31..0000000
--- a/ojluni/src/test/java/nio/file/attribute/DosFileAttributeViewTest.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (c) 2008, 2011, 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.
- *
- * 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.
- */
-
-/* @test
- * @bug 4313887 6838333
- * @summary Unit test for java.nio.file.attribute.DosFileAttributeView
- * @library ../..
- */
-// Android-changed: Adapted from
-// jdk/test/java/nio/file/attribute/DosFileAttributeView/Basic.java
-// Android-changed: Added package & Test import
-package test.java.nio.file.attribute;
-import org.testng.annotations.Test;
-import test.java.nio.file.TestUtil;
-
-import java.nio.file.*;
-import static java.nio.file.LinkOption.*;
-import java.nio.file.attribute.*;
-import java.util.*;
-import java.io.IOException;
-
-// Android-changed: Renamed from "Basic"
-public class DosFileAttributeViewTest {
-
-    static void check(boolean okay) {
-        if (!okay)
-            throw new RuntimeException("Test failed");
-    }
-
-    // exercise each setter/getter method, leaving all attributes unset
-    static void testAttributes(DosFileAttributeView view) throws IOException {
-        view.setReadOnly(true);
-        check(view.readAttributes().isReadOnly());
-        view.setReadOnly(false);
-        check(!view.readAttributes().isReadOnly());
-        view.setHidden(true);
-        check(view.readAttributes().isHidden());
-        view.setHidden(false);
-        check(!view.readAttributes().isHidden());
-        view.setArchive(true);
-        check(view.readAttributes().isArchive());
-        view.setArchive(false);
-        check(!view.readAttributes().isArchive());
-        view.setSystem(true);
-        check(view.readAttributes().isSystem());
-        view.setSystem(false);
-        check(!view.readAttributes().isSystem());
-    }
-
-    // set the value of all attributes
-    static void setAll(DosFileAttributeView view, boolean value)
-        throws IOException
-    {
-        view.setReadOnly(value);
-        view.setHidden(value);
-        view.setArchive(value);
-        view.setSystem(value);
-    }
-
-    // read and write FAT attributes
-    static void readWriteTests(Path dir) throws IOException {
-
-        // create "foo" and test that we can read/write each FAT attribute
-        Path file = Files.createFile(dir.resolve("foo"));
-        try {
-            testAttributes(Files.getFileAttributeView(file, DosFileAttributeView.class));
-
-            // Following tests use a symbolic link so skip if not supported
-            if (!TestUtil.supportsLinks(dir))
-                return;
-
-            Path link = dir.resolve("link");
-            Files.createSymbolicLink(link, file);
-
-            // test following links
-            testAttributes(Files.getFileAttributeView(link, DosFileAttributeView.class));
-
-            // test not following links
-            try {
-                try {
-                    testAttributes(Files
-                        .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS));
-                } catch (IOException x) {
-                    // access to link attributes not supported
-                    return;
-                }
-
-                // set all attributes on link
-                // run test on target of link (which leaves them all un-set)
-                // check that attributes of link remain all set
-                setAll(Files
-                    .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS), true);
-                testAttributes(Files
-                    .getFileAttributeView(link, DosFileAttributeView.class));
-                DosFileAttributes attrs =
-                    Files.getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS)
-                         .readAttributes();
-                check(attrs.isReadOnly());
-                check(attrs.isHidden());
-                check(attrs.isArchive());
-                check(attrs.isSystem());
-                setAll(Files
-                    .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS), false);
-
-                // set all attributes on target
-                // run test on link (which leaves them all un-set)
-                // check that attributes of target remain all set
-                setAll(Files.getFileAttributeView(link, DosFileAttributeView.class), true);
-                testAttributes(Files
-                    .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS));
-                attrs = Files.getFileAttributeView(link, DosFileAttributeView.class).readAttributes();
-                check(attrs.isReadOnly());
-                check(attrs.isHidden());
-                check(attrs.isArchive());
-                check(attrs.isSystem());
-                setAll(Files.getFileAttributeView(link, DosFileAttributeView.class), false);
-            } finally {
-                TestUtil.deleteUnchecked(link);
-            }
-        } finally {
-            TestUtil.deleteUnchecked(file);
-        }
-    }
-
-    // Android-changed: Removed args & added @Test
-    @Test
-    public static void main() throws IOException {
-        // create temporary directory to run tests
-        Path dir = TestUtil.createTemporaryDirectory();
-
-        try {
-            // skip test if DOS file attributes not supported
-            if (!Files.getFileStore(dir).supportsFileAttributeView("dos")) {
-                System.out.println("DOS file attribute not supported.");
-                return;
-            }
-            readWriteTests(dir);
-        } finally {
-            TestUtil.removeAll(dir);
-        }
-    }
-}
diff --git a/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java
index 7825837..4355b31 100644
--- a/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java
+++ b/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java
@@ -388,10 +388,13 @@
     public static void main() throws IOException {
         Path dir = TestUtil.createTemporaryDirectory();
         try {
-            if (!Files.getFileStore(dir).supportsFileAttributeView("posix")) {
-                System.out.println("PosixFileAttributeView not supported");
-                return;
-            }
+            // Android-changed: PosixFileAttributeViews are unconditionally supported in
+            // all writeable partitions.
+            //
+            // if (!Files.getFileStore(dir).supportsFileAttributeView("posix")) {
+            //     System.out.println("PosixFileAttributeView not supported");
+            //     return;
+            // }
 
             permissionTests(dir);
             createTests(dir);
diff --git a/ojluni/src/test/java/nio/file/attribute/UserDefinedFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/UserDefinedFileAttributeViewTest.java
deleted file mode 100644
index 6fb3c78..0000000
--- a/ojluni/src/test/java/nio/file/attribute/UserDefinedFileAttributeViewTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (c) 2008, 2011, 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.
- *
- * 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.
- */
-
-/* @test
- * @bug 4313887 6838333
- * @summary Unit test for java.nio.file.attribute.UserDefinedFileAttributeView
- * @library ../..
- */
-// Android-changed: Adapted from
-// jdk/test/java/nio/file/attribute/UserDefinedFileAttributeView/Basic.java
-// Android-changed: Added package & Test import
-package test.java.nio.file.attribute;
-import org.testng.annotations.Test;
-import test.java.nio.file.TestUtil;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.file.*;
-import static java.nio.file.LinkOption.*;
-import java.nio.file.attribute.*;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Random;
-import java.io.IOException;
-
-// Android-changed: Renamed from "Basic"
-public class UserDefinedFileAttributeViewTest {
-
-    private static Random rand = new Random();
-
-    private static final String ATTR_NAME = "mime_type";
-    private static final String ATTR_VALUE = "text/plain";
-    private static final String ATTR_VALUE2 = "text/html";
-
-    static interface Task {
-        void run() throws Exception;
-    }
-
-    static void tryCatch(Class<? extends Throwable> ex, Task task) {
-        boolean caught = false;
-        try {
-            task.run();
-        } catch (Throwable x) {
-            if (ex.isAssignableFrom(x.getClass())) {
-                caught = true;
-            } else {
-                throw new RuntimeException(x);
-            }
-        }
-        if (!caught)
-            throw new RuntimeException(ex.getName() + " expected");
-    }
-
-    static void expectNullPointerException(Task task) {
-        tryCatch(NullPointerException.class, task);
-    }
-
-    static boolean hasAttribute(UserDefinedFileAttributeView view, String attr)
-        throws IOException
-    {
-        for (String name: view.list()) {
-            if (name.equals(ATTR_NAME))
-                return true;
-        }
-        return false;
-    }
-
-    static void test(Path file, LinkOption... options) throws IOException {
-        final UserDefinedFileAttributeView view =
-            Files.getFileAttributeView(file, UserDefinedFileAttributeView.class, options);
-        ByteBuffer buf = rand.nextBoolean() ?
-            ByteBuffer.allocate(100) : ByteBuffer.allocateDirect(100);
-
-        // Test: write
-        buf.put(ATTR_VALUE.getBytes()).flip();
-        int size = buf.remaining();
-        int nwrote = view.write(ATTR_NAME, buf);
-        if (nwrote != size)
-            throw new RuntimeException("Unexpected number of bytes written");
-
-        // Test: size
-        if (view.size(ATTR_NAME) != size)
-            throw new RuntimeException("Unexpected size");
-
-        // Test: read
-        buf.clear();
-        int nread = view.read(ATTR_NAME, buf);
-        if (nread != size)
-            throw new RuntimeException("Unexpected number of bytes read");
-        buf.flip();
-        String value = Charset.defaultCharset().decode(buf).toString();
-        if (!value.equals(ATTR_VALUE))
-            throw new RuntimeException("Unexpected attribute value");
-
-        // Test: read with insufficient space
-        tryCatch(IOException.class, new Task() {
-            public void run() throws IOException {
-                view.read(ATTR_NAME, ByteBuffer.allocateDirect(1));
-            }});
-
-        // Test: replace value
-        buf.clear();
-        buf.put(ATTR_VALUE2.getBytes()).flip();
-        size = buf.remaining();
-        view.write(ATTR_NAME, buf);
-        if (view.size(ATTR_NAME) != size)
-            throw new RuntimeException("Unexpected size");
-
-        // Test: list
-        if (!hasAttribute(view, ATTR_NAME))
-            throw new RuntimeException("Attribute name not in list");
-
-        // Test: delete
-        view.delete(ATTR_NAME);
-        if (hasAttribute(view, ATTR_NAME))
-            throw new RuntimeException("Attribute name in list");
-
-        // Test: dynamic access
-        String name = "user:" + ATTR_NAME;
-        byte[] valueAsBytes = ATTR_VALUE.getBytes();
-        Files.setAttribute(file, name, valueAsBytes);
-        byte[] actualAsBytes = (byte[])Files.getAttribute(file, name);
-        if (!Arrays.equals(valueAsBytes, actualAsBytes))
-            throw new RuntimeException("Unexpected attribute value");
-        Map<String,?> map = Files.readAttributes(file, name);
-        if (!Arrays.equals(valueAsBytes, (byte[])map.get(ATTR_NAME)))
-            throw new RuntimeException("Unexpected attribute value");
-        map = Files.readAttributes(file, "user:*");
-        if (!Arrays.equals(valueAsBytes, (byte[])map.get(ATTR_NAME)))
-            throw new RuntimeException("Unexpected attribute value");
-    }
-
-    static void miscTests(final Path file) throws IOException {
-        final UserDefinedFileAttributeView view =
-            Files.getFileAttributeView(file, UserDefinedFileAttributeView.class);
-        view.write(ATTR_NAME, ByteBuffer.wrap(ATTR_VALUE.getBytes()));
-
-        // NullPointerException
-        final ByteBuffer buf = ByteBuffer.allocate(100);
-
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                view.read(null, buf);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                view.read(ATTR_NAME, null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                view.write(null, buf);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-               view.write(ATTR_NAME, null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                view.size(null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                view.delete(null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.getAttribute(file, null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.getAttribute(file, "user:" + ATTR_NAME, (LinkOption[])null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.setAttribute(file, "user:" + ATTR_NAME, null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.setAttribute(file, null, new byte[0]);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.setAttribute(file, "user: " + ATTR_NAME, new byte[0], (LinkOption[])null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.readAttributes(file, (String)null);
-            }});
-        expectNullPointerException(new Task() {
-            public void run() throws IOException {
-                Files.readAttributes(file, "*", (LinkOption[])null);
-            }});
-
-        // Read-only buffer
-        tryCatch(IllegalArgumentException.class, new Task() {
-            public void run() throws IOException {
-                ByteBuffer buf = ByteBuffer.wrap(ATTR_VALUE.getBytes()).asReadOnlyBuffer();
-                view.write(ATTR_NAME, buf);
-                buf.flip();
-                view.read(ATTR_NAME, buf);
-            }});
-
-        // Zero bytes remaining
-        tryCatch(IOException.class, new Task() {
-            public void run() throws IOException {
-                ByteBuffer buf = buf = ByteBuffer.allocateDirect(100);
-                buf.position(buf.capacity());
-                view.read(ATTR_NAME, buf);
-            }});
-    }
-
-    // Android-changed: Removed args & added @Test
-    @Test
-    public static void main() throws IOException {
-        // create temporary directory to run tests
-        Path dir = TestUtil.createTemporaryDirectory();
-        try {
-            if (!Files.getFileStore(dir).supportsFileAttributeView("user")) {
-                System.out.println("UserDefinedFileAttributeView not supported - skip test");
-                return;
-            }
-
-            // test access to user defined attributes of regular file
-            Path file = dir.resolve("foo.html");
-            Files.createFile(file);
-            try {
-                test(file);
-            } finally {
-                Files.delete(file);
-            }
-
-            // test access to user defined attributes of directory
-            Path subdir = dir.resolve("foo");
-            Files.createDirectory(subdir);
-            try {
-                test(subdir);
-            } finally {
-                Files.delete(subdir);
-            }
-
-            // test access to user defined attributes of sym link
-            if (TestUtil.supportsLinks(dir)) {
-                Path target = dir.resolve("doesnotexist");
-                Path link = dir.resolve("link");
-                Files.createSymbolicLink(link, target);
-                try {
-                    test(link, NOFOLLOW_LINKS);
-                } catch (IOException x) {
-                    // access to attributes of sym link may not be supported
-                } finally {
-                    Files.delete(link);
-                }
-            }
-
-            // misc. tests
-            try {
-                file = dir.resolve("foo.txt");
-                Files.createFile(file);
-                miscTests(dir);
-            } finally {
-                Files.delete(file);
-            }
-
-        } finally {
-            TestUtil.removeAll(dir);
-        }
-    }
- }
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index 4285911..3b38617 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -1610,6 +1610,8 @@
     ojluni/src/main/java/sun/security/provider/certpath/X509CertificatePair.java \
     ojluni/src/main/java/sun/security/provider/X509Factory.java \
     ojluni/src/main/java/sun/security/timestamp/TimestampToken.java \
+    ojluni/src/main/java/sun/security/util/AnchorCertificates.java \
+    ojluni/src/main/java/sun/security/util/CertConstraintParameters.java \
     ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java \
     ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java \
     ojluni/src/main/java/sun/security/util/BitArray.java \
diff --git a/tools/upstream/oj_upstream_comparison.py b/tools/upstream/oj_upstream_comparison.py
index 312483c..deab5fb 100755
--- a/tools/upstream/oj_upstream_comparison.py
+++ b/tools/upstream/oj_upstream_comparison.py
@@ -46,10 +46,12 @@
 """
 
 import argparse
+import csv
 import filecmp
 import os
 import re
 import shutil
+import sys
 
 def rel_paths_from_makefile(build_top):
     """Returns the list of relative paths to .java files parsed from openjdk_java_files.mk"""
@@ -83,28 +85,75 @@
             return result
     return None
 
-def compare_to_upstreams(build_top, upstream_root, upstreams, rel_paths):
+
+# For files with N and M lines, respectively, this runs in time
+# O(N+M) if the files are identical or O(N*M) if not. This could
+# be improved to O(D*(N+M)) for files with at most D lines
+# difference by only considering array elements within D cells
+# from the diagonal.
+def edit_distance_lines(file_a, file_b):
     """
-    Returns a dict from rel_path to lists of length len(upstreams)
-    Each list entry specifies whether the file at a particular
-    rel_path is missing from, identical to, or different from
-    a particular upstream.
+    Computes the line-based edit distance between two text files, i.e.
+    the smallest number of line deletions, additions or replacements
+    that would transform the content of one file into that of the other.
     """
-    result = {}
+    if filecmp.cmp(file_a, file_b, shallow=False):
+        return 0 # files identical
+    with open(file_a) as f:
+        lines_a = f.readlines()
+    with open(file_b) as f:
+        lines_b = f.readlines()
+    prev_cost = range(0, len(lines_b) + 1)
+    for end_a in range(1, len(lines_a) + 1):
+        # For each valid index i, prev_cost[i] is the edit distance between
+        # lines_a[:end_a-1] and lines_b[:i].
+        # We now calculate cur_cost[end_b] as the edit distance between
+        # line_a[:end_a] and lines_b[:end_b]
+        cur_cost = [end_a]
+        for end_b in range(1, len(lines_b) + 1):
+            c = min(
+                cur_cost[-1] + 1, # append line from b
+                prev_cost[end_b] + 1, # append line from a
+                # match or replace line
+                prev_cost[end_b - 1] + (0 if lines_a[end_a - 1] == lines_b[end_b - 1] else 1)
+                )
+            cur_cost.append(c)
+        prev_cost = cur_cost
+    return prev_cost[-1]
+
+def compare_to_upstreams_and_save(out_file, build_top, upstream_root, upstreams, rel_paths, best_only=False):
+    """
+    Prints tab-separated values comparing ojluni files vs. each
+    upstream, for each of the rel_paths, suitable for human
+    analysis in a spreadsheet.
+    This includes whether the corresponding upstream file is
+    missing, identical, or by how many lines it differs, and
+    a guess as to the correct upstream based on minimal line
+    difference (ties broken in favor of upstreams that occur
+    earlier in the list).
+    """
+    writer = csv.writer(out_file, delimiter='\t')
+    writer.writerow(["rel_path", "guessed_upstream"] + upstreams)
     for rel_path in rel_paths:
         ojluni_file = ojluni_path(build_top, rel_path)
-        status = []
+        upstream_comparisons = []
+        best_distance = sys.maxint
+        guessed_upstream = ""
         for upstream in upstreams:
             upstream_file = upstream_path(upstream_root, upstream, rel_path)
             if upstream_file is None:
-                upstream_status = "missing"
-            elif filecmp.cmp(upstream_file, ojluni_file, shallow=False):
-                upstream_status = "identical"
+                upstream_comparison = "missing"
             else:
-                upstream_status = "different"
-            status.append(upstream_status)
-        result[rel_path] = status
-    return result
+                edit_distance = edit_distance_lines(upstream_file, ojluni_file)
+                if edit_distance == 0:
+                    upstream_comparison = "identical"
+                else:
+                    upstream_comparison = "different (%d lines)" % (edit_distance)
+                if edit_distance < best_distance:
+                    best_distance = edit_distance
+                    guessed_upstream = upstream
+            upstream_comparisons.append(upstream_comparison)
+        writer.writerow([rel_path, guessed_upstream ] + upstream_comparisons)
 
 def copy_files(rel_paths, upstream_root, upstream, output_dir):
     """Copies files at the given rel_paths from upstream to output_dir"""
@@ -148,13 +197,12 @@
             raise Exception("Upstream not found: " + upstream_path)
 
     rel_paths = rel_paths_from_makefile(args.build_top)
-    upstream_infos = compare_to_upstreams(args.build_top, args.upstream_root, upstreams, rel_paths)
+
+    compare_to_upstreams_and_save(
+        sys.stdout, args.build_top, args.upstream_root, upstreams, rel_paths)
 
     if args.output_dir is not None:
         copy_files(rel_paths, args.upstream_root, default_upstream, args.output_dir)
 
-    for rel_path in rel_paths:
-        print(rel_path + "\t" +  "\t".join(upstream_infos[rel_path]))
-
 if __name__ == '__main__':
     main()
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
index 3902d36..80f37bc 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
@@ -152,6 +152,15 @@
     }
 
     @Override
+    public int hashCode() {
+        int result = formatMajorVersion;
+        result = 31 * result + formatMinorVersion;
+        result = 31 * result + rulesVersion.hashCode();
+        result = 31 * result + revision;
+        return result;
+    }
+
+    @Override
     public String toString() {
         return "DistroVersion{" +
                 "formatMajorVersion=" + formatMajorVersion +
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
index a170a9e..39626ac 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
@@ -17,6 +17,8 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -173,4 +175,14 @@
             return toReturn;
         }
     }
+
+    /**
+     * Creates an empty file.
+     *
+     * @param file the file to create
+     * @throws IOException if the file cannot be created
+     */
+    public static void createEmptyFile(File file) throws IOException {
+        new FileOutputStream(file, false /* append */).close();
+    }
 }
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java
new file mode 100644
index 0000000..0492e12
--- /dev/null
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.shared2;
+
+/**
+ * Information about a staged time zone distro operation.
+ */
+public class StagedDistroOperation {
+
+    private static final StagedDistroOperation UNINSTALL_STAGED =
+            new StagedDistroOperation(true /* isUninstall */, null /* stagedVersion */);
+
+    public final boolean isUninstall;
+    public final DistroVersion distroVersion;
+
+    private StagedDistroOperation(boolean isUninstall, DistroVersion distroVersion) {
+        this.isUninstall = isUninstall;
+        this.distroVersion = distroVersion;
+    }
+
+    public static StagedDistroOperation install(DistroVersion distroVersion) {
+        if (distroVersion == null) {
+            throw new NullPointerException("distroVersion==null");
+        }
+        return new StagedDistroOperation(false /* isUninstall */, distroVersion);
+    }
+
+    public static StagedDistroOperation uninstall() {
+        return UNINSTALL_STAGED;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        StagedDistroOperation that = (StagedDistroOperation) o;
+
+        if (isUninstall != that.isUninstall) {
+            return false;
+        }
+        return distroVersion != null ? distroVersion.equals(that.distroVersion)
+                : that.distroVersion == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (isUninstall ? 1 : 0);
+        result = 31 * result + (distroVersion != null ? distroVersion.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "StagedDistroOperation{" +
+                "isUninstall=" + isUninstall +
+                ", distroVersion=" + distroVersion +
+                '}';
+    }
+}
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java
index dd01fb0..9358c70 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java
@@ -37,6 +37,9 @@
     /** The name of the file inside the distro containing ICU TZ data. */
     public static final String ICU_DATA_FILE_NAME = "icu/icu_tzdata.dat";
 
+    /** The name of the file inside the distro containing time zone lookup data. */
+    public static final String TZLOOKUP_FILE_NAME = "tzlookup.xml";
+
     /**
      * The name of the file inside the distro containing the distro version information.
      * The content is ASCII bytes representing a set of version numbers. See {@link DistroVersion}.
diff --git a/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java b/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
index 3b9ec5b..87e6e6e 100644
--- a/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
+++ b/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
@@ -253,6 +253,43 @@
         assertTrue(Arrays.equals(contents, exhaustedFileRead));
     }
 
+    public void testCreateEmptyFile() throws Exception {
+        File dir = createTempDir();
+        File file = new File(dir, "one");
+        assertFalse(file.exists());
+        FileUtils.createEmptyFile(file);
+        assertTrue(file.exists());
+        assertEquals(0, file.length());
+    }
+
+    public void testCreateEmptyFile_isDir() throws Exception {
+        File dir = createTempDir();
+        assertTrue(dir.exists());
+        assertTrue(dir.isDirectory());
+
+        try {
+            FileUtils.createEmptyFile(dir);
+        } catch (FileNotFoundException expected) {
+        }
+        assertTrue(dir.exists());
+        assertTrue(dir.isDirectory());
+    }
+
+    public void testCreateEmptyFile_truncatesExisting() throws Exception {
+        File dir = createTempDir();
+        File file = new File(dir, "one");
+
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(new byte[1000]);
+        }
+        assertTrue(file.exists());
+        assertEquals(1000, file.length());
+
+        FileUtils.createEmptyFile(file);
+        assertTrue(file.exists());
+        assertEquals(0, file.length());
+    }
+
     private File createFile(byte[] contents) throws IOException {
         File file = File.createTempFile(getClass().getSimpleName(), ".txt");
         try (FileOutputStream fos = new FileOutputStream(file)) {
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java
index 4b70152..7fabf7a 100644
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java
@@ -30,7 +30,7 @@
  * A command-line tool for creating a timezone update distro.
  *
  * Args:
- * tzdata.properties file - the file describing the distro (see template file in tzdata/tools)
+ * tzdata.properties file - the file describing the distro (see template file in tzdata/tools2)
  * output file - the name of the file to be generated
  */
 public class CreateTimeZoneDistro {
@@ -56,8 +56,9 @@
                 Integer.parseInt(getMandatoryProperty(p, "revision")));
         TimeZoneDistroBuilder builder = new TimeZoneDistroBuilder()
                 .setDistroVersion(distroVersion)
-                .setTzData(getMandatoryPropertyFile(p, "bionic.file"))
-                .setIcuData(getMandatoryPropertyFile(p, "icu.file"));
+                .setTzDataFile(getMandatoryPropertyFile(p, "bionic.file"))
+                .setIcuDataFile(getMandatoryPropertyFile(p, "icu.file"))
+                .setTzLookupFile(getMandatoryPropertyFile(p, "tzlookup.file"));
 
         TimeZoneDistro distro = builder.build();
         File outputFile = new File(args[1]);
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java
index 4c12e96..9860489 100644
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java
@@ -19,6 +19,7 @@
 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.shared2.DistroException;
@@ -34,6 +35,7 @@
     private DistroVersion distroVersion;
     private byte[] tzData;
     private byte[] icuData;
+    private String tzLookupXml;
 
     public TimeZoneDistroBuilder setDistroVersion(DistroVersion distroVersion) {
         this.distroVersion = distroVersion;
@@ -56,11 +58,11 @@
         return this;
     }
 
-    public TimeZoneDistroBuilder setTzData(File tzDataFile) throws IOException {
-        return setTzData(readFileAsByteArray(tzDataFile));
+    public TimeZoneDistroBuilder setTzDataFile(File tzDataFile) throws IOException {
+        return setTzDataFile(readFileAsByteArray(tzDataFile));
     }
 
-    public TimeZoneDistroBuilder setTzData(byte[] tzData) {
+    public TimeZoneDistroBuilder setTzDataFile(byte[] tzData) {
         this.tzData = tzData;
         return this;
     }
@@ -71,15 +73,24 @@
         return this;
     }
 
-    public TimeZoneDistroBuilder setIcuData(File icuDataFile) throws IOException {
-        return setIcuData(readFileAsByteArray(icuDataFile));
+    public TimeZoneDistroBuilder setIcuDataFile(File icuDataFile) throws IOException {
+        return setIcuDataFile(readFileAsByteArray(icuDataFile));
     }
 
-    public TimeZoneDistroBuilder setIcuData(byte[] icuData) {
+    public TimeZoneDistroBuilder setIcuDataFile(byte[] icuData) {
         this.icuData = icuData;
         return this;
     }
 
+    public TimeZoneDistroBuilder setTzLookupFile(File tzLookupFile) throws IOException {
+        return setTzLookupXml(readFileAsUtf8(tzLookupFile));
+    }
+
+    public TimeZoneDistroBuilder setTzLookupXml(String tzlookupXml) {
+        this.tzLookupXml = tzlookupXml;
+        return this;
+    }
+
     // For use in tests.
     public TimeZoneDistroBuilder clearIcuDataForTests() {
         this.icuData = null;
@@ -102,6 +113,10 @@
             if (icuData != null) {
                 addZipEntry(zos, TimeZoneDistro.ICU_DATA_FILE_NAME, icuData);
             }
+            if (tzLookupXml != null) {
+                addZipEntry(zos, TimeZoneDistro.TZLOOKUP_FILE_NAME,
+                        tzLookupXml.getBytes(StandardCharsets.UTF_8));
+            }
         } catch (IOException e) {
             throw new DistroException("Unable to create zip file", e);
         }
@@ -140,7 +155,7 @@
     /**
      * Returns the contents of 'path' as a byte array.
      */
-    public static byte[] readFileAsByteArray(File file) throws IOException {
+    private static byte[] readFileAsByteArray(File file) throws IOException {
         byte[] buffer = new byte[8192];
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try (FileInputStream  fis = new FileInputStream(file)) {
@@ -151,5 +166,12 @@
         }
         return baos.toByteArray();
     }
+
+    /**
+     * Returns the contents of 'path' as a String, having interpreted the file as UTF-8.
+     */
+    private String readFileAsUtf8(File file) throws IOException {
+        return new String(readFileAsByteArray(file), StandardCharsets.UTF_8);
+    }
 }
 
diff --git a/tzdata/tools2/testing/prepareTzDataUpdates.sh b/tzdata/tools2/testing/prepareTzDataUpdates.sh
index 5b9b2ab..9a41942 100755
--- a/tzdata/tools2/testing/prepareTzDataUpdates.sh
+++ b/tzdata/tools2/testing/prepareTzDataUpdates.sh
@@ -26,6 +26,7 @@
 
 # Get the current tzdata version and find both the previous and new versions.
 TZDATA=libc/zoneinfo/tzdata
+TZLOOKUP=libc/zoneinfo/tzlookup.xml
 
 TZHEADER=$(head -n1 bionic/$TZDATA | cut -c1-11)
 
@@ -88,6 +89,7 @@
 rules.version=${TZ_PREVIOUS}
 bionic.file=${TMP_PREVIOUS}/tzdata
 icu.file=${TMP_PREVIOUS}/icu_tzdata.dat
+tzlookup.file=${ANDROID_BUILD_TOP}/bionic/${TZLOOKUP}
 EOF
 
 TZ_PREVIOUS_UPDATE_ZIP=update_${TZ_PREVIOUS}_test.zip
@@ -117,6 +119,7 @@
 rules.version=${TZ_CURRENT}
 bionic.file=${TMP_CURRENT}/tzdata
 icu.file=${TMP_CURRENT}/icu_tzdata.dat
+tzlookup.file=${ANDROID_BUILD_TOP}/bionic/${TZLOOKUP}
 EOF
 
 TZ_CURRENT_UPDATE_ZIP=update_${TZ_CURRENT}_test.zip
@@ -146,6 +149,7 @@
 rules.version=${TZ_NEXT}
 bionic.file=${TMP_NEXT}/tzdata
 icu.file=${TMP_NEXT}/icu_tzdata.dat
+tzlookup.file=${ANDROID_BUILD_TOP}/bionic/${TZLOOKUP}
 EOF
 
 TZ_NEXT_UPDATE_ZIP=update_${TZ_NEXT}_test.zip
diff --git a/tzdata/tools2/tzupdate.properties b/tzdata/tools2/tzupdate.properties
index 82fa2c4..264960b 100644
--- a/tzdata/tools2/tzupdate.properties
+++ b/tzdata/tools2/tzupdate.properties
@@ -8,4 +8,4 @@
 
 bionic.file=
 icu.file=
-
+tzlookup.file=
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
index bb49fe0..baadc85 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
@@ -23,7 +23,9 @@
 import libcore.tzdata.shared2.DistroException;
 import libcore.tzdata.shared2.DistroVersion;
 import libcore.tzdata.shared2.FileUtils;
+import libcore.tzdata.shared2.StagedDistroOperation;
 import libcore.tzdata.shared2.TimeZoneDistro;
+import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
 /**
@@ -31,38 +33,55 @@
  * testing. This class is not thread-safe: callers are expected to handle mutual exclusion.
  */
 public class TimeZoneDistroInstaller {
-    /** {@link #installWithErrorCode(byte[])} result code: Success. */
+    /** {@link #stageInstallWithErrorCode(byte[])} result code: Success. */
     public final static int INSTALL_SUCCESS = 0;
-    /** {@link #installWithErrorCode(byte[])} result code: Distro corrupt. */
+    /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro corrupt. */
     public final static int INSTALL_FAIL_BAD_DISTRO_STRUCTURE = 1;
-    /** {@link #installWithErrorCode(byte[])} result code: Distro version incompatible. */
+    /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro version incompatible. */
     public final static int INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION = 2;
-    /** {@link #installWithErrorCode(byte[])} result code: Distro rules too old for device. */
+    /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro rules too old for device. */
     public final static int INSTALL_FAIL_RULES_TOO_OLD = 3;
-    /** {@link #installWithErrorCode(byte[])} result code: Distro content failed validation. */
+    /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro content failed validation. */
     public final static int INSTALL_FAIL_VALIDATION_ERROR = 4;
 
+    // This constant must match one in system/core/tzdatacheck.cpp.
+    private static final String STAGED_TZ_DATA_DIR_NAME = "staged";
+    // This constant must match one in system/core/tzdatacheck.cpp.
     private static final String CURRENT_TZ_DATA_DIR_NAME = "current";
     private static final String WORKING_DIR_NAME = "working";
     private static final String OLD_TZ_DATA_DIR_NAME = "old";
 
+    /**
+     * The name of the file in the staged directory used to indicate a staged uninstallation.
+     */
+    // This constant must match one in system/core/tzdatacheck.cpp.
+    // VisibleForTesting.
+    public static final String UNINSTALL_TOMBSTONE_FILE_NAME = "STAGED_UNINSTALL_TOMBSTONE";
+
     private final String logTag;
     private final File systemTzDataFile;
-    private final File oldTzDataDir;
+    private final File oldStagedDataDir;
+    private final File stagedTzDataDir;
     private final File currentTzDataDir;
     private final File workingDir;
 
     public TimeZoneDistroInstaller(String logTag, File systemTzDataFile, File installDir) {
         this.logTag = logTag;
         this.systemTzDataFile = systemTzDataFile;
-        oldTzDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
+        oldStagedDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
+        stagedTzDataDir = new File(installDir, STAGED_TZ_DATA_DIR_NAME);
         currentTzDataDir = new File(installDir, CURRENT_TZ_DATA_DIR_NAME);
         workingDir = new File(installDir, WORKING_DIR_NAME);
     }
 
     // VisibleForTesting
-    File getOldTzDataDir() {
-        return oldTzDataDir;
+    File getOldStagedDataDir() {
+        return oldStagedDataDir;
+    }
+
+    // VisibleForTesting
+    File getStagedTzDataDir() {
+        return stagedTzDataDir;
     }
 
     // VisibleForTesting
@@ -76,34 +95,35 @@
     }
 
     /**
-     * Install the supplied content.
+     * Stage an install of the supplied content, to be installed the next time the device boots.
      *
-     * <p>Errors during unpacking or installation will throw an {@link IOException}.
+     * <p>Errors during unpacking or staging will throw an {@link IOException}.
      * If the distro 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 {
-        int result = installWithErrorCode(content);
+        int result = stageInstallWithErrorCode(content);
         return result == INSTALL_SUCCESS;
     }
 
     /**
-     * Install the supplied time zone distro.
+     * Stage an install of the supplied content, to be installed the next time the device boots.
      *
-     * <p>Errors during unpacking or installation will throw an {@link IOException}.
+     * <p>Errors during unpacking or staging will throw an {@link IOException}.
      * Returns {@link #INSTALL_SUCCESS} or an error code.
      */
-    public int installWithErrorCode(byte[] content) throws IOException {
-        if (oldTzDataDir.exists()) {
-            FileUtils.deleteRecursive(oldTzDataDir);
+    public int stageInstallWithErrorCode(byte[] content) throws IOException {
+        if (oldStagedDataDir.exists()) {
+            FileUtils.deleteRecursive(oldStagedDataDir);
         }
         if (workingDir.exists()) {
             FileUtils.deleteRecursive(workingDir);
         }
 
         Slog.i(logTag, "Unpacking / verifying time zone update");
-        unpackDistro(content, workingDir);
         try {
+            unpackDistro(content, workingDir);
+
             DistroVersion distroVersion;
             try {
                 distroVersion = readDistroVersion(workingDir);
@@ -131,6 +151,7 @@
                 return INSTALL_FAIL_RULES_TOO_OLD;
             }
 
+            // Validate the tzdata file.
             File zoneInfoFile = new File(workingDir, TimeZoneDistro.TZDATA_FILE_NAME);
             ZoneInfoDB.TzData tzData = ZoneInfoDB.TzData.loadTzData(zoneInfoFile.getPath());
             if (tzData == null) {
@@ -145,58 +166,100 @@
             } finally {
                 tzData.close();
             }
-            // TODO(nfuller): Add deeper validity checks / canarying before applying.
+
+            // Validate the tzlookup.xml file.
+            File tzLookupFile = new File(workingDir, TimeZoneDistro.TZLOOKUP_FILE_NAME);
+            if (!tzLookupFile.exists()) {
+                Slog.i(logTag, "Update not applied: " + tzLookupFile + " does not exist");
+                return INSTALL_FAIL_BAD_DISTRO_STRUCTURE;
+            }
+            try {
+                TimeZoneFinder timeZoneFinder =
+                        TimeZoneFinder.createInstance(tzLookupFile.getPath());
+                timeZoneFinder.validate();
+            } catch (IOException e) {
+                Slog.i(logTag, "Update not applied: " + tzLookupFile + " failed validation", e);
+                return INSTALL_FAIL_VALIDATION_ERROR;
+            }
+
+            // TODO(nfuller): Add validity checks for ICU data / canarying before applying.
             // http://b/31008728
 
             Slog.i(logTag, "Applying time zone update");
             FileUtils.makeDirectoryWorldAccessible(workingDir);
 
-            if (currentTzDataDir.exists()) {
-                Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
-                FileUtils.rename(currentTzDataDir, oldTzDataDir);
+            // Check if there is already a staged install or uninstall and remove it if there is.
+            if (!stagedTzDataDir.exists()) {
+                Slog.i(logTag, "Nothing to unstage at " + stagedTzDataDir);
+            } else {
+                Slog.i(logTag, "Moving " + stagedTzDataDir + " to " + oldStagedDataDir);
+                // Move stagedTzDataDir out of the way in one operation so we can't partially delete
+                // the contents.
+                FileUtils.rename(stagedTzDataDir, oldStagedDataDir);
             }
-            Slog.i(logTag, "Moving " + workingDir + " to " + currentTzDataDir);
-            FileUtils.rename(workingDir, currentTzDataDir);
-            Slog.i(logTag, "Update applied: " + currentTzDataDir + " successfully created");
+
+            // Move the workingDir to be the new staged directory.
+            Slog.i(logTag, "Moving " + workingDir + " to " + stagedTzDataDir);
+            FileUtils.rename(workingDir, stagedTzDataDir);
+            Slog.i(logTag, "Install staged: " + stagedTzDataDir + " successfully created");
             return INSTALL_SUCCESS;
         } finally {
-            deleteBestEffort(oldTzDataDir);
+            deleteBestEffort(oldStagedDataDir);
             deleteBestEffort(workingDir);
         }
     }
 
     /**
-     * Uninstall the current timezone update in /data, returning the device to using data from
-     * /system. Returns {@code true} if uninstallation was successful, {@code false} if there was
-     * nothing installed in /data to uninstall.
+     * Stage an uninstall of the current timezone update in /data which, on reboot, will return the
+     * device to using data from /system. Returns {@code true} if staging the uninstallation was
+     * successful, {@code false} if there was nothing installed in /data to uninstall. If there was
+     * something else staged it will be replaced by this call.
      *
      * <p>Errors encountered during uninstallation will throw an {@link IOException}.
      */
-    public boolean uninstall() throws IOException {
+    public boolean stageUninstall() throws IOException {
         Slog.i(logTag, "Uninstalling time zone update");
 
-        // Make sure we don't have a dir where we're going to move the currently installed data to.
-        if (oldTzDataDir.exists()) {
+        if (oldStagedDataDir.exists()) {
             // If we can't remove this, an exception is thrown and we don't continue.
-            FileUtils.deleteRecursive(oldTzDataDir);
+            FileUtils.deleteRecursive(oldStagedDataDir);
+        }
+        if (workingDir.exists()) {
+            FileUtils.deleteRecursive(workingDir);
         }
 
-        if (!currentTzDataDir.exists()) {
-            Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
-            return false;
+        try {
+            // Check if there is already an install or uninstall staged and remove it.
+            if (!stagedTzDataDir.exists()) {
+                Slog.i(logTag, "Nothing to unstage at " + stagedTzDataDir);
+            } else {
+                Slog.i(logTag, "Moving " + stagedTzDataDir + " to " + oldStagedDataDir);
+                // Move stagedTzDataDir out of the way in one operation so we can't partially delete
+                // the contents.
+                FileUtils.rename(stagedTzDataDir, oldStagedDataDir);
+            }
+
+            // If there's nothing actually installed, there's nothing to uninstall so no need to
+            // stage anything.
+            if (!currentTzDataDir.exists()) {
+                Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
+                return false;
+            }
+
+            // Stage an uninstall in workingDir.
+            FileUtils.ensureDirectoriesExist(workingDir, true /* makeWorldReadable */);
+            FileUtils.createEmptyFile(new File(workingDir, UNINSTALL_TOMBSTONE_FILE_NAME));
+
+            // Move the workingDir to be the new staged directory.
+            Slog.i(logTag, "Moving " + workingDir + " to " + stagedTzDataDir);
+            FileUtils.rename(workingDir, stagedTzDataDir);
+            Slog.i(logTag, "Uninstall staged: " + stagedTzDataDir + " successfully created");
+
+            return true;
+        } finally {
+            deleteBestEffort(oldStagedDataDir);
+            deleteBestEffort(workingDir);
         }
-
-        Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
-        // Move currentTzDataDir out of the way in one operation so we can't partially delete
-        // the contents, which would leave a partial install.
-        FileUtils.rename(currentTzDataDir, oldTzDataDir);
-
-        // Do our best to delete the now uninstalled timezone data.
-        deleteBestEffort(oldTzDataDir);
-
-        Slog.i(logTag, "Time zone update uninstalled.");
-
-        return true;
     }
 
     /**
@@ -214,6 +277,24 @@
     }
 
     /**
+     * Reads information about any currently staged distro operation. Returns {@code null} if there
+     * is no distro operation staged.
+     *
+     * @throws IOException if there was a problem reading data from /data
+     * @throws DistroException if there was a problem with the staged distro format/structure
+     */
+    public StagedDistroOperation getStagedDistroOperation() throws DistroException, IOException {
+        if (!stagedTzDataDir.exists()) {
+            return null;
+        }
+        if (new File(stagedTzDataDir, UNINSTALL_TOMBSTONE_FILE_NAME).exists()) {
+            return StagedDistroOperation.uninstall();
+        } else {
+            return StagedDistroOperation.install(readDistroVersion(stagedTzDataDir));
+        }
+    }
+
+    /**
      * Reads the timezone rules version present in /system. i.e. the version that would be present
      * after a factory reset.
      *
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
index 325b605..3af9f15 100644
--- a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
@@ -24,7 +24,6 @@
 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;
@@ -32,10 +31,13 @@
 import libcore.io.Streams;
 import libcore.tzdata.shared2.DistroVersion;
 import libcore.tzdata.shared2.FileUtils;
+import libcore.tzdata.shared2.StagedDistroOperation;
 import libcore.tzdata.shared2.TimeZoneDistro;
 import libcore.tzdata.testing.ZoneInfoTestHelper;
 import libcore.tzdata.update2.tools.TimeZoneDistroBuilder;
 
+import static org.junit.Assert.assertArrayEquals;
+
 /**
  * Tests for {@link TimeZoneDistroInstaller}.
  */
@@ -90,100 +92,110 @@
     }
 
     /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
-    public void testInstall_badSystemFile() throws Exception {
+    public void testStageInstallWithErrorCode_badSystemFile() throws Exception {
         File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
         TimeZoneDistroInstaller brokenSystemInstaller = new TimeZoneDistroInstaller(
                 "TimeZoneDistroInstallerTest", doesNotExist, testInstallDir);
         TimeZoneDistro tzData = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
 
         try {
-            brokenSystemInstaller.installWithErrorCode(tzData.getBytes());
+            brokenSystemInstaller.stageInstallWithErrorCode(tzData.getBytes());
             fail();
         } catch (IOException expected) {}
 
-        assertNoContentInstalled();
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
     /** Tests the first successful update on a device */
-    public void testInstall_successfulFirstUpdate() throws Exception {
+    public void testStageInstallWithErrorCode_successfulFirstUpdate() throws Exception {
         TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
 
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertDistroInstalled(distro);
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertInstallDistroStaged(distro);
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests we can install an update the same version as is in /system.
      */
-    public void testInstall_successfulFirstUpdate_sameVersionAsSystem() throws Exception {
+    public void testStageInstallWithErrorCode_successfulFirstUpdate_sameVersionAsSystem()
+            throws Exception {
         TimeZoneDistro distro = createValidTimeZoneDistro(SYSTEM_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertDistroInstalled(distro);
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertInstallDistroStaged(distro);
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests we cannot install an update older than the version in /system.
      */
-    public void testInstall_unsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
+    public void testStageInstallWithErrorCode_unsuccessfulFirstUpdate_olderVersionThanSystem()
+            throws Exception {
         TimeZoneDistro distro = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertNoContentInstalled();
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
     /**
-     * Tests an update on a device when there is a prior update already applied.
+     * Tests an update on a device when there is a prior update already staged.
      */
-    public void testInstall_successfulFollowOnUpdate_newerVersion() throws Exception {
+    public void testStageInstallWithErrorCode_successfulFollowOnUpdate_newerVersion()
+            throws Exception {
         TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro1.getBytes()));
-        assertDistroInstalled(distro1);
+                installer.stageInstallWithErrorCode(distro1.getBytes()));
+        assertInstallDistroStaged(distro1);
 
         TimeZoneDistro distro2 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro2.getBytes()));
-        assertDistroInstalled(distro2);
+                installer.stageInstallWithErrorCode(distro2.getBytes()));
+        assertInstallDistroStaged(distro2);
 
         TimeZoneDistro distro3 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro3.getBytes()));
-        assertDistroInstalled(distro3);
+                installer.stageInstallWithErrorCode(distro3.getBytes()));
+        assertInstallDistroStaged(distro3);
+        assertNoInstalledDistro();
     }
 
     /**
      * 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 {
+    public void testStageInstallWithErrorCode_unsuccessfulFollowOnUpdate_olderVersion()
+            throws Exception {
         TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro1.getBytes()));
-        assertDistroInstalled(distro1);
+                installer.stageInstallWithErrorCode(distro1.getBytes()));
+        assertInstallDistroStaged(distro1);
 
         TimeZoneDistro distro2 = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
-                installer.installWithErrorCode(distro2.getBytes()));
-        assertDistroInstalled(distro1);
+                installer.stageInstallWithErrorCode(distro2.getBytes()));
+        assertInstallDistroStaged(distro1);
+        assertNoInstalledDistro();
     }
 
-    /** Tests that a distro with a missing file will not update the content. */
-    public void testInstall_missingTzDataFile() throws Exception {
-        TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+    /** Tests that a distro with a missing tzdata file will not update the content. */
+    public void testStageInstallWithErrorCode_missingTzDataFile() throws Exception {
+        TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(installedDistro.getBytes()));
-        assertDistroInstalled(installedDistro);
+                installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
 
         TimeZoneDistro incompleteDistro =
                 createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
@@ -191,17 +203,18 @@
                         .buildUnvalidated();
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
-                installer.installWithErrorCode(incompleteDistro.getBytes()));
-        assertDistroInstalled(installedDistro);
+                installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
+        assertNoInstalledDistro();
     }
 
-    /** Tests that a distro with a missing file will not update the content. */
-    public void testInstall_missingIcuFile() throws Exception {
-        TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+    /** Tests that a distro with a missing ICU file will not update the content. */
+    public void testStageInstallWithErrorCode_missingIcuFile() throws Exception {
+        TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(installedDistro.getBytes()));
-        assertDistroInstalled(installedDistro);
+                installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
 
         TimeZoneDistro incompleteDistro =
                 createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
@@ -209,14 +222,53 @@
                         .buildUnvalidated();
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
-                installer.installWithErrorCode(incompleteDistro.getBytes()));
-        assertDistroInstalled(installedDistro);
+                installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
+        assertNoInstalledDistro();
+    }
+
+    /** Tests that a distro with a missing tzlookup file will not update the content. */
+    public void testStageInstallWithErrorCode_missingTzLookupFile() throws Exception {
+        TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+        assertEquals(
+                TimeZoneDistroInstaller.INSTALL_SUCCESS,
+                installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
+
+        TimeZoneDistro incompleteDistro =
+                createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
+                        .setTzLookupXml(null)
+                        .buildUnvalidated();
+        assertEquals(
+                TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
+                installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
+        assertNoInstalledDistro();
+    }
+
+    /** Tests that a distro with a bad tzlookup file will not update the content. */
+    public void testStageInstallWithErrorCode_badTzLookupFile() throws Exception {
+        TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+        assertEquals(
+                TimeZoneDistroInstaller.INSTALL_SUCCESS,
+                installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
+
+        TimeZoneDistro incompleteDistro =
+                createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
+                        .setTzLookupXml("<foo />")
+                        .buildUnvalidated();
+        assertEquals(
+                TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR,
+                installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+        assertInstallDistroStaged(stagedDistro);
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests that an update will be unpacked even if there is a partial update from a previous run.
      */
-    public void testInstall_withWorkingDir() throws Exception {
+    public void testStageInstallWithErrorCode_withWorkingDir() throws Exception {
         File workingDir = installer.getWorkingDir();
         assertTrue(workingDir.mkdir());
         createFile(new File(workingDir, "myFile"), new byte[] { 'a' });
@@ -224,42 +276,45 @@
         TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertDistroInstalled(distro);
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertInstallDistroStaged(distro);
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests that a distro without a distro version file will be rejected.
      */
-    public void testInstall_withMissingDistroVersionFile() throws Exception {
+    public void testStageInstallWithErrorCode_withMissingDistroVersionFile() throws Exception {
         // Create a distro without a version file.
         TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
                 .clearVersionForTests()
                 .buildUnvalidated();
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertNoContentInstalled();
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests that a distro with an newer distro version will be rejected.
      */
-    public void testInstall_withNewerDistroVersion() throws Exception {
+    public void testStageInstallWithErrorCode_withNewerDistroVersion() throws Exception {
         // Create a distro that will appear to be newer than the one currently supported.
         TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
                 .replaceFormatVersionForTests(2, 1)
                 .buildUnvalidated();
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertNoContentInstalled();
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests that a distro with a badly formed distro version will be rejected.
      */
-    public void testInstall_withBadlyFormedDistroVersion() throws Exception {
+    public void testStageInstallWithErrorCode_withBadlyFormedDistroVersion() throws Exception {
         // Create a distro that has an invalid major distro version. It should be 3 numeric
         // characters, "." and 3 more numeric characters.
         DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
@@ -269,14 +324,15 @@
         TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertNoContentInstalled();
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests that a distro with a badly formed revision will be rejected.
      */
-    public void testInstall_withBadlyFormedRevision() throws Exception {
+    public void testStageInstallWithErrorCode_withBadlyFormedRevision() throws Exception {
         // Create a distro that has an invalid revision. It should be 3 numeric characters.
         DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
         byte[] invalidRevisionBytes = validDistroVersion.toBytes();
@@ -285,14 +341,15 @@
         TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertNoContentInstalled();
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
     /**
      * Tests that a distro with a badly formed rules version will be rejected.
      */
-    public void testInstall_withBadlyFormedRulesVersion() throws Exception {
+    public void testStageInstallWithErrorCode_withBadlyFormedRulesVersion() throws Exception {
         // Create a distro that has an invalid rules version. It should be in the form "2016c".
         DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
         byte[] invalidRulesVersionBytes = validDistroVersion.toBytes();
@@ -301,38 +358,124 @@
         TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes);
         assertEquals(
                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
-                installer.installWithErrorCode(distro.getBytes()));
-        assertNoContentInstalled();
+                installer.stageInstallWithErrorCode(distro.getBytes()));
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
-    public void testUninstall_noExistingDataDistro() throws Exception {
-        assertFalse(installer.uninstall());
-        assertNoContentInstalled();
+    public void testStageUninstall_noExistingDistro() throws Exception {
+        // To stage an uninstall, there would need to be installed rules.
+        assertFalse(installer.stageUninstall());
+
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
     }
 
-    public void testUninstall_existingDataDistro() throws Exception {
-        File currentDataDir = installer.getCurrentTzDataDir();
-        assertTrue(currentDataDir.mkdir());
+    public void testStageUninstall_existingStagedDataDistro() throws Exception {
+        // To stage an uninstall, we need to have some installed rules.
+        TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+        simulateInstalledDistro(installedDistro);
 
-        assertTrue(installer.uninstall());
-        assertNoContentInstalled();
+        File stagedDataDir = installer.getStagedTzDataDir();
+        assertTrue(stagedDataDir.mkdir());
+
+        assertTrue(installer.stageUninstall());
+        assertDistroUninstallStaged();
+        assertInstalledDistro(installedDistro);
     }
 
-    public void testUninstall_oldDirsAlreadyExists() throws Exception {
-        File oldTzDataDir = installer.getOldTzDataDir();
-        assertTrue(oldTzDataDir.mkdir());
+    public void testStageUninstall_oldDirsAlreadyExists() throws Exception {
+        // To stage an uninstall, we need to have some installed rules.
+        TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+        simulateInstalledDistro(installedDistro);
 
-        File currentDataDir = installer.getCurrentTzDataDir();
-        assertTrue(currentDataDir.mkdir());
+        File oldStagedDataDir = installer.getOldStagedDataDir();
+        assertTrue(oldStagedDataDir.mkdir());
 
-        assertTrue(installer.uninstall());
-        assertNoContentInstalled();
+        File workingDir = installer.getWorkingDir();
+        assertTrue(workingDir.mkdir());
+
+        assertTrue(installer.stageUninstall());
+
+        assertDistroUninstallStaged();
+        assertFalse(workingDir.exists());
+        assertFalse(oldStagedDataDir.exists());
+        assertInstalledDistro(installedDistro);
     }
 
     public void testGetSystemRulesVersion() throws Exception {
         assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion());
     }
 
+    public void testGetInstalledDistroVersion() throws Exception {
+        // Check result when nothing installed.
+        assertNull(installer.getInstalledDistroVersion());
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
+
+        // Now simulate there being an existing install active.
+        TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+        simulateInstalledDistro(distro);
+        assertInstalledDistro(distro);
+
+        // Check result when something installed.
+        assertEquals(distro.getDistroVersion(), installer.getInstalledDistroVersion());
+        assertNoDistroOperationStaged();
+        assertInstalledDistro(distro);
+    }
+
+    public void testGetStagedDistroOperation() throws Exception {
+        TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+        TimeZoneDistro distro2 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1);
+
+        // Check result when nothing staged.
+        assertNull(installer.getStagedDistroOperation());
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
+
+        // Check result after unsuccessfully staging an uninstall.
+        // Can't stage an uninstall without an installed distro.
+        assertFalse(installer.stageUninstall());
+        assertNull(installer.getStagedDistroOperation());
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
+
+        // Check result after staging an install.
+        assertTrue(installer.install(distro1.getBytes()));
+        StagedDistroOperation expectedStagedInstall =
+                StagedDistroOperation.install(distro1.getDistroVersion());
+        assertEquals(expectedStagedInstall, installer.getStagedDistroOperation());
+        assertInstallDistroStaged(distro1);
+        assertNoInstalledDistro();
+
+        // Check result after unsuccessfully staging an uninstall (but after removing a staged
+        // install). Can't stage an uninstall without an installed distro.
+        assertFalse(installer.stageUninstall());
+        assertNull(installer.getStagedDistroOperation());
+        assertNoDistroOperationStaged();
+        assertNoInstalledDistro();
+
+        // Now simulate there being an existing install active.
+        simulateInstalledDistro(distro1);
+        assertInstalledDistro(distro1);
+
+        // Check state after successfully staging an uninstall.
+        assertTrue(installer.stageUninstall());
+        StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall();
+        assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation());
+        assertDistroUninstallStaged();
+        assertInstalledDistro(distro1);
+
+        // Check state after successfully staging an install.
+        assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS,
+                installer.stageInstallWithErrorCode(distro2.getBytes()));
+        StagedDistroOperation expectedStagedInstall2 =
+                StagedDistroOperation.install(distro2.getDistroVersion());
+        assertEquals(expectedStagedInstall2, installer.getStagedDistroOperation());
+        assertInstallDistroStaged(distro2);
+        assertInstalledDistro(distro1);
+    }
+
     private static TimeZoneDistro createValidTimeZoneDistro(
             String rulesVersion, int revision) throws Exception {
         return createValidTimeZoneDistroBuilder(rulesVersion, revision).build();
@@ -343,6 +486,17 @@
 
         byte[] bionicTzData = createTzData(rulesVersion);
         byte[] icuData = new byte[] { 'a' };
+        String tzlookupXml = "<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"us\">\n"
+                + "      <id>America/New_York\"</id>\n"
+                + "      <id>America/Los_Angeles</id>\n"
+                + "    </country>\n"
+                + "    <country code=\"gb\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n";
         DistroVersion distroVersion = new DistroVersion(
                 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
                 DistroVersion.CURRENT_FORMAT_MINOR_VERSION,
@@ -350,28 +504,35 @@
                 revision);
         return new TimeZoneDistroBuilder()
                 .setDistroVersion(distroVersion)
-                .setTzData(bionicTzData)
-                .setIcuData(icuData);
+                .setTzDataFile(bionicTzData)
+                .setIcuDataFile(icuData)
+                .setTzLookupXml(tzlookupXml);
     }
 
-    private void assertDistroInstalled(TimeZoneDistro expectedDistro) throws Exception {
+    private void assertInstallDistroStaged(TimeZoneDistro expectedDistro) throws Exception {
         assertTrue(testInstallDir.exists());
 
-        File currentTzDataDir = installer.getCurrentTzDataDir();
-        assertTrue(currentTzDataDir.exists());
+        File stagedTzDataDir = installer.getStagedTzDataDir();
+        assertTrue(stagedTzDataDir.exists());
 
         File distroVersionFile =
-                new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
+                new File(stagedTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
         assertTrue(distroVersionFile.exists());
 
-        File bionicFile = new File(currentTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
+        File bionicFile = new File(stagedTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
         assertTrue(bionicFile.exists());
 
-        File icuFile = new File(currentTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
+        File icuFile = new File(stagedTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
         assertTrue(icuFile.exists());
 
-        // Assert getInstalledDistroVersion() is reporting correctly.
-        assertEquals(expectedDistro.getDistroVersion(), installer.getInstalledDistroVersion());
+        File tzLookupFile = new File(stagedTzDataDir, TimeZoneDistro.TZLOOKUP_FILE_NAME);
+        assertTrue(tzLookupFile.exists());
+
+        // Assert getStagedDistroState() is reporting correctly.
+        StagedDistroOperation stagedDistroOperation = installer.getStagedDistroOperation();
+        assertNotNull(stagedDistroOperation);
+        assertFalse(stagedDistroOperation.isUninstall);
+        assertEquals(expectedDistro.getDistroVersion(), stagedDistroOperation.distroVersion);
 
         try (ZipInputStream zis = new ZipInputStream(
                 new ByteArrayInputStream(expectedDistro.getBytes()))) {
@@ -385,6 +546,8 @@
                     actualFile = icuFile;
                 } else if (entryName.endsWith(TimeZoneDistro.TZDATA_FILE_NAME)) {
                     actualFile = bionicFile;
+                } else if (entryName.endsWith(TimeZoneDistro.TZLOOKUP_FILE_NAME)) {
+                    actualFile = tzLookupFile;
                 } else {
                     throw new AssertionFailedError("Unknown file found");
                 }
@@ -401,23 +564,64 @@
             throws Exception {
         byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath());
         byte[] expectedBytes = Streams.readFullyNoClose(expected);
-        assertTrue(Arrays.equals(expectedBytes, actualBytes));
+        assertArrayEquals(expectedBytes, actualBytes);
     }
 
-    private void assertNoContentInstalled() throws Exception {
-        assertNull(installer.getInstalledDistroVersion());
+    private void assertNoDistroOperationStaged() throws Exception {
+        assertNull(installer.getStagedDistroOperation());
 
-        File currentTzDataDir = installer.getCurrentTzDataDir();
-        assertFalse(currentTzDataDir.exists());
+        File stagedTzDataDir = installer.getStagedTzDataDir();
+        assertFalse(stagedTzDataDir.exists());
 
         // Also check no working directories are left lying around.
         File workingDir = installer.getWorkingDir();
         assertFalse(workingDir.exists());
 
-        File oldDataDir = installer.getOldTzDataDir();
+        File oldDataDir = installer.getOldStagedDataDir();
         assertFalse(oldDataDir.exists());
     }
 
+    private void assertDistroUninstallStaged() throws Exception {
+        assertEquals(StagedDistroOperation.uninstall(), installer.getStagedDistroOperation());
+
+        File stagedTzDataDir = installer.getStagedTzDataDir();
+        assertTrue(stagedTzDataDir.exists());
+        assertTrue(stagedTzDataDir.isDirectory());
+
+        File uninstallTombstone =
+                new File(stagedTzDataDir, TimeZoneDistroInstaller.UNINSTALL_TOMBSTONE_FILE_NAME);
+        assertTrue(uninstallTombstone.exists());
+        assertTrue(uninstallTombstone.isFile());
+
+        // Also check no working directories are left lying around.
+        File workingDir = installer.getWorkingDir();
+        assertFalse(workingDir.exists());
+
+        File oldDataDir = installer.getOldStagedDataDir();
+        assertFalse(oldDataDir.exists());
+    }
+
+    private void simulateInstalledDistro(TimeZoneDistro timeZoneDistro) throws Exception {
+        File currentTzDataDir = installer.getCurrentTzDataDir();
+        assertFalse(currentTzDataDir.exists());
+        assertTrue(currentTzDataDir.mkdir());
+        timeZoneDistro.extractTo(currentTzDataDir);
+    }
+
+    private void assertNoInstalledDistro() {
+        assertFalse(installer.getCurrentTzDataDir().exists());
+    }
+
+    private void assertInstalledDistro(TimeZoneDistro timeZoneDistro) throws Exception {
+        File currentTzDataDir = installer.getCurrentTzDataDir();
+        assertTrue(currentTzDataDir.exists());
+        File versionFile = new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
+        assertTrue(versionFile.exists());
+        byte[] expectedVersionBytes = timeZoneDistro.getDistroVersion().toBytes();
+        byte[] actualVersionBytes = FileUtils.readBytes(versionFile, expectedVersionBytes.length);
+        assertArrayEquals(expectedVersionBytes, actualVersionBytes);
+    }
+
     private static byte[] createTzData(String rulesVersion) {
         return new ZoneInfoTestHelper.TzDataBuilder()
                 .initializeToValid()