Merge "Migrate libcore code to new classes libcore.util.Mutable{Int,Long}."
diff --git a/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java b/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java
index 5a84c8e..87c3096 100644
--- a/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java
+++ b/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java
@@ -87,7 +87,7 @@
         // Hardcode MessagePattern apostrophe mode to be default. b/27265238
         { "android.icu.text.MessagePattern.ApostropheMode", null },
 
-        // Hardcode "sun.io.useCanonCaches" to use the default (on). b/28174137
+        // Hardcode "sun.io.useCanonCaches" to use the default (off). b/28174137, b/62301183
         { "sun.io.useCanonCaches", null },
         { "sun.io.useCanonPrefixCache", null },
 
@@ -111,4 +111,3 @@
         { "java.util.logging.manager", null },
     };
 }
-
diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java
index 7d18098..154ea68 100644
--- a/luni/src/main/java/libcore/icu/LocaleData.java
+++ b/luni/src/main/java/libcore/icu/LocaleData.java
@@ -95,7 +95,7 @@
     public char groupingSeparator;
     public char patternSeparator;
     public String percent;
-    public char perMill;
+    public String perMill;
     public char monetarySeparator;
     public String minusSign;
     public String exponentSeparator;
diff --git a/luni/src/main/java/libcore/icu/TimeZoneNames.java b/luni/src/main/java/libcore/icu/TimeZoneNames.java
index 68e6e30..aea4483 100644
--- a/luni/src/main/java/libcore/icu/TimeZoneNames.java
+++ b/luni/src/main/java/libcore/icu/TimeZoneNames.java
@@ -175,7 +175,5 @@
         return ids.toArray(new String[ids.size()]);
     }
 
-    public static native String getExemplarLocation(String locale, String tz);
-
     private static native void fillZoneStrings(String locale, String[][] result);
 }
diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java
index c6053dc..f6d2456 100644
--- a/luni/src/main/java/libcore/io/BlockGuardOs.java
+++ b/luni/src/main/java/libcore/io/BlockGuardOs.java
@@ -52,14 +52,6 @@
         }
     }
 
-    private void untagSocket(FileDescriptor fd) throws ErrnoException {
-        try {
-            SocketTagger.get().untag(fd);
-        } catch (SocketException e) {
-            throw new ErrnoException("socket", EINVAL, e);
-        }
-    }
-
     @Override public FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException {
         BlockGuard.getThreadPolicy().onNetwork();
         final FileDescriptor acceptFd = os.accept(fd, peerAddress);
@@ -96,9 +88,6 @@
                     // connections in methods like onDestroy which will run on the UI thread.
                     BlockGuard.getThreadPolicy().onNetwork();
                 }
-                if (isInetSocket(fd)) {
-                    untagSocket(fd);
-                }
             }
         } catch (ErrnoException ignored) {
             // We're called via Socket.close (which doesn't ask for us to be called), so we
diff --git a/luni/src/main/java/libcore/util/TimeZoneFinder.java b/luni/src/main/java/libcore/util/TimeZoneFinder.java
index 37e3b4a..fa15086 100644
--- a/luni/src/main/java/libcore/util/TimeZoneFinder.java
+++ b/luni/src/main/java/libcore/util/TimeZoneFinder.java
@@ -36,6 +36,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 /**
@@ -45,18 +46,19 @@
 
     private static final String TZLOOKUP_FILE_NAME = "tzlookup.xml";
     private static final String TIMEZONES_ELEMENT = "timezones";
+    private static final String IANA_VERSION_ATTRIBUTE = "ianaversion";
     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 DEFAULT_TIME_ZONE_ID_ATTRIBUTE = "default";
     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;
+    // Cached field for the last country looked up.
+    private CountryTimeZones lastCountryTimeZones;
 
     private TimeZoneFinder(ReaderSupplier xmlSource) {
         this.xmlSource = xmlSource;
@@ -131,6 +133,33 @@
     }
 
     /**
+     * Returns the IANA rules version associated with the data. If there is no version information
+     * or there is a problem reading the file then {@code null} is returned.
+     */
+    public String getIanaVersion() {
+        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 ianaversion="xxxxx">
+             *     ....
+             * </timezones>
+             */
+
+            findRequiredStartTag(parser, TIMEZONES_ELEMENT);
+
+            return parser.getAttributeValue(null /* namespace */, IANA_VERSION_ATTRIBUTE);
+        } catch (XmlPullParserException | IOException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns a frozen ICU time zone that has / would have had the specified offset and DST value
      * at the specified moment in the specified country.
      *
@@ -143,14 +172,14 @@
     public TimeZone lookupTimeZoneByCountryAndOffset(
             String countryIso, int offsetSeconds, boolean isDst, long whenMillis, TimeZone bias) {
 
+        countryIso = normalizeCountryIso(countryIso);
         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);
+        for (TimeZone match : candidates) {
             if (!offsetMatchesAtTime(match, offsetSeconds, isDst, whenMillis)) {
                 continue;
             }
@@ -193,45 +222,88 @@
     }
 
     /**
+     * Returns a "default" time zone ID known to be used in the specified country. This is
+     * the time zone ID that can be used if only the country code is known and can be presumed to be
+     * the "best" choice in the absence of other information. For countries with more than one zone
+     * the time zone will not be correct for everybody.
+     *
+     * <p>If the country code is not recognized or there is an error during lookup this can return
+     * null.
+     */
+    public String lookupDefaultTimeZoneIdByCountry(String countryIso) {
+        countryIso = normalizeCountryIso(countryIso);
+        CountryTimeZones countryTimeZones = findCountryTimeZones(countryIso);
+        return countryTimeZones == null ? null : countryTimeZones.getDefaultTimeZoneId();
+    }
+
+    /**
      * Returns an immutable list of frozen ICU 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
+     * can return an empty list in a case when the underlying data files reference only unknown
      * zone IDs.
      */
     public List<TimeZone> lookupTimeZonesByCountry(String countryIso) {
-        synchronized(this) {
-            if (countryIso.equals(lastCountryIso)) {
+        countryIso = normalizeCountryIso(countryIso);
+        CountryTimeZones countryTimeZones = findCountryTimeZones(countryIso);
+        return countryTimeZones == null ? null : countryTimeZones.getTimeZones();
+    }
+
+    /**
+     * Returns an immutable list of time zone IDs 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 IDs returned will all be valid for use with
+     * {@link java.util.TimeZone#getTimeZone(String)} and
+     * {@link android.icu.util.TimeZone#getTimeZone(String)}. This method can return an empty list
+     * in a case when the underlying data files reference only unknown zone IDs.
+     */
+    public List<String> lookupTimeZoneIdsByCountry(String countryIso) {
+        countryIso = normalizeCountryIso(countryIso);
+        CountryTimeZones countryTimeZones = findCountryTimeZones(countryIso);
+        return countryTimeZones == null ? null : countryTimeZones.getTimeZoneIds();
+    }
+
+    /**
+     * Returns a {@link CountryTimeZones} object associated with the specified country code.
+     * Caching is handled as needed. If the country code is not recognized or there is an error
+     * during lookup this can return null.
+     */
+    private CountryTimeZones findCountryTimeZones(String countryIso) {
+        synchronized (this) {
+            if (lastCountryTimeZones != null
+                    && lastCountryTimeZones.getCountryIso().equals(countryIso)) {
                 return lastCountryTimeZones;
             }
         }
 
-        CountryZonesExtractor extractor = new CountryZonesExtractor(countryIso);
-        List<TimeZone> countryTimeZones = null;
+        SelectiveCountryTimeZonesExtractor extractor =
+                new SelectiveCountryTimeZonesExtractor(countryIso);
         try {
             processXml(extractor);
-            countryTimeZones = extractor.getMatchedZones();
-        } catch (IOException e) {
+
+            CountryTimeZones countryTimeZones = extractor.getValidatedCountryTimeZones();
+            if (countryTimeZones == null) {
+                // None matched. Return the null but don't change the cached value.
+                return null;
+            }
+
+            // Update the cached value.
+            synchronized (this) {
+                lastCountryTimeZones = countryTimeZones;
+            }
+            return countryTimeZones;
+        } catch (XmlPullParserException | 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.
+            // Error - don't change the cached value.
+            return null;
         }
-
-        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#process(String, String, List, String)} returns
      * {@link CountryZonesProcessor#HALT} or it throws an exception.
      */
     private void processXml(CountryZonesProcessor processor)
@@ -245,14 +317,14 @@
 
             /*
              * The expected XML structure is:
-             * <timezones>
+             * <timezones ianaversion="2017b">
              *   <countryzones>
-             *     <country code="us">
+             *     <country code="us" default="America/New_York">
              *       <id>America/New_York"</id>
              *       ...
              *       <id>America/Los_Angeles</id>
              *     </country>
-             *     <country code="gb">
+             *     <country code="gb" default="Europe/London">
              *       <id>Europe/London</id>
              *     </country>
              *   </countryzones>
@@ -261,6 +333,9 @@
 
             findRequiredStartTag(parser, TIMEZONES_ELEMENT);
 
+            // We do not require the ianaversion attribute be present. It is metadata that helps
+            // with versioning but is not required.
+
             // There is only one expected sub-element <countryzones> in the format currently, skip
             // over anything before it.
             findRequiredStartTag(parser, COUNTRY_ZONES_ELEMENT);
@@ -298,10 +373,16 @@
                     throw new XmlPullParserException(
                             "Unable to find country code: " + parser.getPositionDescription());
                 }
+                String defaultTimeZoneId = parser.getAttributeValue(
+                        null /* namespace */, DEFAULT_TIME_ZONE_ID_ATTRIBUTE);
+                if (defaultTimeZoneId == null || defaultTimeZoneId.isEmpty()) {
+                    throw new XmlPullParserException("Unable to find default time zone ID: "
+                            + parser.getPositionDescription());
+                }
 
                 String debugInfo = parser.getPositionDescription();
                 List<String> timeZoneIds = parseZoneIds(parser);
-                if (processor.process(code, timeZoneIds, debugInfo)
+                if (processor.process(code, defaultTimeZoneId, timeZoneIds, debugInfo)
                         == CountryZonesProcessor.HALT) {
                     return CountryZonesProcessor.HALT;
                 }
@@ -311,7 +392,7 @@
             checkOnEndTag(parser, COUNTRY_ELEMENT);
         }
 
-        return CountryZonesExtractor.CONTINUE;
+        return CountryZonesProcessor.CONTINUE;
     }
 
     private static List<String> parseZoneIds(XmlPullParser parser)
@@ -479,79 +560,80 @@
          * 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;
+        boolean process(String countryIso, String defaultTimeZoneId, 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.
+     * Validates &lt;countryzones&gt; elements. Intended to be used before a proposed installation
+     * of new data. To be valid the country ISO code must be normalized, unique, the default time
+     * zone ID must be one of the time zones IDs and the time zone IDs list must not be empty. The
+     * IDs themselves are not checked against other data to see if they are recognized because other
+     * classes will not have been updated with the associated new time zone data yet and so will not
+     * be aware of newly added IDs.
      */
     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
+        public boolean process(String countryIso, String defaultTimeZoneId,
+                List<String> timeZoneIds, String debugInfo) throws XmlPullParserException {
+            if (!normalizeCountryIso(countryIso).equals(countryIso)) {
+                throw new XmlPullParserException("Country code: " + countryIso
+                        + " is not normalized at " + debugInfo);
+            }
+            if (knownCountryCodes.contains(countryIso)) {
+                throw new XmlPullParserException("Second entry for country code: " + countryIso
                         + " at " + debugInfo);
             }
             if (timeZoneIds.isEmpty()) {
-                throw new XmlPullParserException("No time zone IDs for country code: " + countryCode
+                throw new XmlPullParserException("No time zone IDs for country code: " + countryIso
                         + " 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);
+            if (!timeZoneIds.contains(defaultTimeZoneId)) {
+                throw new XmlPullParserException("defaultTimeZoneId for country code: "
+                        + countryIso + " is not one of the zones " + timeZoneIds + " at "
+                        + debugInfo);
+            }
+            knownCountryCodes.add(countryIso);
 
             return CONTINUE;
         }
     }
 
     /**
-     * Extracts the zones associated with a country code, halting when the country code is matched
-     * and making them available via {@link #getMatchedZones()}.
+     * Extracts <em>validated</em> time zones information associated with a specific country code.
+     * Processing is halted when the country code is matched and the validated result is also made
+     * available via {@link #getValidatedCountryTimeZones()}.
      */
-    private static class CountryZonesExtractor implements CountryZonesProcessor {
+    private static class SelectiveCountryTimeZonesExtractor implements CountryZonesProcessor {
 
         private final String countryCodeToMatch;
-        private List<TimeZone> matchedZones;
+        private CountryTimeZones validatedCountryTimeZones;
 
-        private CountryZonesExtractor(String countryCodeToMatch) {
+        private SelectiveCountryTimeZonesExtractor(String countryCodeToMatch) {
             this.countryCodeToMatch = countryCodeToMatch;
         }
 
         @Override
-        public boolean process(String countryCode, List<String> timeZoneIds, String debugInfo) {
-            if (!countryCodeToMatch.equals(countryCode)) {
+        public boolean process(String countryIso, String defaultTimeZoneId,
+                List<String> countryTimeZoneIds, String debugInfo) {
+            countryIso = normalizeCountryIso(countryIso);
+            if (!countryCodeToMatch.equals(countryIso)) {
                 return CONTINUE;
             }
+            validatedCountryTimeZones = createValidatedCountryTimeZones(countryIso,
+                    defaultTimeZoneId, countryTimeZoneIds, debugInfo);
 
-            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.
+         * Returns the CountryTimeZones that matched, or {@code null} if there were no matches.
          */
-        List<TimeZone> getMatchedZones() {
-            return matchedZones;
+        CountryTimeZones getValidatedCountryTimeZones() {
+            return validatedCountryTimeZones;
         }
     }
 
@@ -577,4 +659,114 @@
             return () -> new StringReader(xml);
         }
     }
+
+    /**
+     * Information about a country's time zones.
+     */
+    // VisibleForTesting
+    public static class CountryTimeZones {
+        private final String countryIso;
+        private final String defaultTimeZoneId;
+        private final List<String> timeZoneIds;
+
+        // Memoized frozen ICU TimeZone objects for the timeZoneIds.
+        private List<TimeZone> timeZones;
+
+        public CountryTimeZones(String countryIso, String defaultTimeZoneId,
+                List<String> timeZoneIds) {
+            this.countryIso = countryIso;
+            this.defaultTimeZoneId = defaultTimeZoneId;
+            // Create a defensive copy of the IDs list.
+            this.timeZoneIds = Collections.unmodifiableList(new ArrayList<>(timeZoneIds));
+        }
+
+        public String getCountryIso() {
+            return countryIso;
+        }
+
+        /**
+         * Returns the default time zone ID for a country. Can return null in extreme cases when
+         * invalid data is found.
+         */
+        public String getDefaultTimeZoneId() {
+            return defaultTimeZoneId;
+        }
+
+        /**
+         * Returns an ordered list of time zone IDs for a country in an undefined but "priority"
+         * order for a country. The list can be empty if there were no zones configured or the
+         * configured zone IDs were not recognized.
+         */
+        public List<String> getTimeZoneIds() {
+            return timeZoneIds;
+        }
+
+        /**
+         * Returns an ordered list of time zones for a country in an undefined but "priority"
+         * order for a country. The list can be empty if there were no zones configured or the
+         * configured zone IDs were not recognized.
+         */
+        public synchronized List<TimeZone> getTimeZones() {
+            if (timeZones == null) {
+                ArrayList<TimeZone> mutableList = new ArrayList<>(timeZoneIds.size());
+                for (String timeZoneId : timeZoneIds) {
+                    TimeZone timeZone = getValidFrozenTimeZoneOrNull(timeZoneId);
+                    // This shouldn't happen given the validation that takes place in
+                    // createValidatedCountryTimeZones().
+                    if (timeZone == null) {
+                        System.logW("Skipping invalid zone: " + timeZoneId);
+                        continue;
+                    }
+                    mutableList.add(timeZone);
+                }
+                timeZones = Collections.unmodifiableList(mutableList);
+            }
+            return timeZones;
+        }
+
+        private static TimeZone getValidFrozenTimeZoneOrNull(String timeZoneId) {
+            TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId);
+            if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
+                return null;
+            }
+            return timeZone;
+        }
+    }
+
+    private static String normalizeCountryIso(String countryIso) {
+        // Lowercase ASCII is normalized for the purposes of the input files and the code in this
+        // class.
+        return countryIso.toLowerCase(Locale.US);
+    }
+
+    // VisibleForTesting
+    public static CountryTimeZones createValidatedCountryTimeZones(String countryIso,
+            String defaultTimeZoneId, List<String> countryTimeZoneIds, String debugInfo) {
+
+        // We rely on ZoneInfoDB to tell us what the known valid time zone IDs are. ICU may
+        // recognize more but we want to be sure that zone IDs can be used with java.util as well as
+        // android.icu and ICU is expected to have a superset.
+        String[] validTimeZoneIdsArray = ZoneInfoDB.getInstance().getAvailableIDs();
+        HashSet<String> validTimeZoneIdsSet = new HashSet<>(Arrays.asList(validTimeZoneIdsArray));
+        List<String> validCountryTimeZoneIds = new ArrayList<>();
+        for (String countryTimeZoneId : countryTimeZoneIds) {
+            if (!validTimeZoneIdsSet.contains(countryTimeZoneId)) {
+                System.logW("Skipping invalid zone: " + countryTimeZoneId + " at " + debugInfo);
+            } else {
+                validCountryTimeZoneIds.add(countryTimeZoneId);
+            }
+        }
+
+        // We don't get too strict at runtime about whether the defaultTimeZoneId must be
+        // one of the country's time zones because this is the data we have to use (we also
+        // assume the data was validated by earlier steps). The default time zone ID must just
+        // be a recognized zone ID: if it's not valid we leave it null.
+        if (!validTimeZoneIdsSet.contains(defaultTimeZoneId)) {
+            System.logW("Invalid default time zone ID: " + defaultTimeZoneId
+                    + " at " + debugInfo);
+            defaultTimeZoneId = null;
+        }
+
+        return new CountryTimeZones(countryIso, defaultTimeZoneId, validCountryTimeZoneIds);
+    }
 }
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index c392553..bd8c116 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -21,6 +21,7 @@
 #include "log/log.h"
 
 #include <nativehelper/JniConstants.h>
+#include "nativehelper/JniConstants-priv.h"
 #include <nativehelper/ScopedLocalFrame.h>
 
 // DalvikVM calls this on startup, so we can statically register all our native methods.
@@ -30,6 +31,7 @@
         ALOGE("JavaVM::GetEnv() failed");
         abort();
     }
+    JniConstants::init(env);
 
     ScopedLocalFrame localFrame(env);
 
@@ -71,4 +73,8 @@
 #define UNREGISTER(FN) extern void FN(JNIEnv*); FN(env)
     UNREGISTER(unregister_libcore_icu_ICU);
 #undef UNREGISTER
+
+    // Ensure that libnativehelper caching is invalidated, in case a new runtime is to be brought
+    // up later.
+    android::ClearJniConstantsCache();
 }
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index d836164..48f1ba2 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -423,7 +423,7 @@
     setCharField(env, obj, "groupingSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol));
     setCharField(env, obj, "patternSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kPatternSeparatorSymbol));
     setStringField(env, obj, "percent", dfs.getSymbol(icu::DecimalFormatSymbols::kPercentSymbol));
-    setCharField(env, obj, "perMill", dfs.getSymbol(icu::DecimalFormatSymbols::kPerMillSymbol));
+    setStringField(env, obj, "perMill", dfs.getSymbol(icu::DecimalFormatSymbols::kPerMillSymbol));
     setCharField(env, obj, "monetarySeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kMonetarySeparatorSymbol));
     setStringField(env, obj, "minusSign", dfs.getSymbol(icu::DecimalFormatSymbols:: kMinusSignSymbol));
     setStringField(env, obj, "exponentSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kExponentialSymbol));
diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
index 29c07e2..ada6700 100644
--- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
@@ -115,32 +115,8 @@
   }
 }
 
-static jstring TimeZoneNames_getExemplarLocation(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaTz) {
-  ScopedIcuLocale icuLocale(env, javaLocaleName);
-  if (!icuLocale.valid()) {
-    return NULL;
-  }
-
-  UErrorCode status = U_ZERO_ERROR;
-  std::unique_ptr<icu::TimeZoneNames> names(icu::TimeZoneNames::createInstance(icuLocale.locale(), status));
-  if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) {
-    return NULL;
-  }
-
-  ScopedJavaUnicodeString tz(env, javaTz);
-  if (!tz.valid()) {
-    return NULL;
-  }
-
-  icu::UnicodeString s;
-  const UDate now(icu::Calendar::getNow());
-  names->getDisplayName(tz.unicodeString(), UTZNM_EXEMPLAR_LOCATION, now, s);
-  return jniCreateString(env, s.getBuffer(), s.length());
-}
-
 static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(TimeZoneNames, fillZoneStrings, "(Ljava/lang/String;[[Ljava/lang/String;)V"),
-  NATIVE_METHOD(TimeZoneNames, getExemplarLocation, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
 };
 void register_libcore_icu_TimeZoneNames(JNIEnv* env) {
   jniRegisterNativeMethods(env, "libcore/icu/TimeZoneNames", gMethods, NELEM(gMethods));
diff --git a/luni/src/module/java/module-info.java b/luni/src/module/java/module-info.java
index acb4dc3..7f11b17 100644
--- a/luni/src/module/java/module-info.java
+++ b/luni/src/module/java/module-info.java
@@ -114,6 +114,8 @@
     exports javax.xml.parsers;
     exports libcore.io;
     exports libcore.icu;
+    exports libcore.net;
+    exports libcore.net.event;
     exports libcore.net.http;
     exports libcore.reflect;
     exports libcore.util;
diff --git a/luni/src/test/java/libcore/dalvik/system/SocketTaggingTest.java b/luni/src/test/java/libcore/dalvik/system/SocketTaggingTest.java
deleted file mode 100644
index cc30534..0000000
--- a/luni/src/test/java/libcore/dalvik/system/SocketTaggingTest.java
+++ /dev/null
@@ -1,155 +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.dalvik.system;
-
-import android.system.ErrnoException;
-import android.system.StructStat;
-import dalvik.system.SocketTagger;
-import junit.framework.TestCase;
-
-import java.io.FileDescriptor;
-import java.net.DatagramSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.nio.channels.DatagramChannel;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-public class SocketTaggingTest extends TestCase {
-    static final class StatAndDescriptor {
-        final int fd;
-        final StructStat stat;
-
-        StatAndDescriptor(FileDescriptor fd) {
-            this.fd = fd.getInt$();
-            this.stat = fstat(fd);
-        }
-
-        @Override
-        public String toString() {
-            return "[fd=" + fd + ", stat=" + stat + "]";
-        }
-    }
-
-    static class RecordingSocketTagger extends SocketTagger {
-        private final Map<Integer, StatAndDescriptor> liveDescriptors = new HashMap<>();
-
-        @Override
-        public void tag(FileDescriptor socketDescriptor) throws SocketException {
-            liveDescriptors.put(socketDescriptor.getInt$(),
-                    new StatAndDescriptor(socketDescriptor));
-        }
-
-        @Override
-        public void untag(FileDescriptor socketDescriptor) throws SocketException {
-            StatAndDescriptor existing = liveDescriptors.remove(socketDescriptor.getInt$());
-
-            // We compare the current description of the descriptor with the description
-            // we used to tag with and make sure they describe the same file. This helps test
-            // whether we untag the socket at the "right" time.
-            StructStat current = fstat(socketDescriptor);
-            assertEquals(existing.stat.st_dev, current.st_dev);
-            assertEquals(existing.stat.st_ino, current.st_ino);
-        }
-
-        public Map<Integer, StatAndDescriptor> getLiveDescriptors() {
-            return liveDescriptors;
-        }
-    }
-
-    private RecordingSocketTagger tagger;
-    private SocketTagger original;
-
-    private ServerSocketChannel server;
-
-    @Override
-    public void setUp() throws Exception {
-        server = ServerSocketChannel.open();
-        server.configureBlocking(false);
-        server.bind(null);
-
-        original = SocketTagger.get();
-        tagger = new RecordingSocketTagger();
-        SocketTagger.set(tagger);
-    }
-
-    @Override
-    public void tearDown() {
-        SocketTagger.set(original);
-    }
-
-    public void testSocketChannel() throws Exception {
-        SocketChannel sc = SocketChannel.open();
-        sc.connect(server.getLocalAddress());
-        assertEquals(1, tagger.getLiveDescriptors().size());
-
-        sc.close();
-
-        assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
-    }
-
-    public void testServerSocketChannel() throws Exception {
-        ServerSocketChannel ssc = ServerSocketChannel.open();
-        ssc.bind(null);
-        assertEquals(1, tagger.getLiveDescriptors().size());
-
-        ssc.close();
-
-        assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
-    }
-
-    public void testDatagramChannel() throws Exception {
-        DatagramChannel dc = DatagramChannel.open();
-        dc.connect(server.getLocalAddress());
-        assertEquals(1, tagger.getLiveDescriptors().size());
-
-        dc.close();
-
-        assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
-    }
-
-    public void testSocket() throws Exception {
-        Socket s = new Socket();
-        s.connect(server.getLocalAddress());
-        assertEquals(1, tagger.getLiveDescriptors().size());
-
-        s.close();
-
-        assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
-    }
-
-    public void testDatagramSocket() throws Exception {
-        DatagramSocket d = new DatagramSocket();
-        d.connect(server.getLocalAddress());
-        assertEquals(1, tagger.getLiveDescriptors().size());
-
-        d.close();
-
-        assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
-    }
-
-    private static StructStat fstat(FileDescriptor fd) {
-        try {
-            return android.system.Os.fstat(fd);
-        } catch (ErrnoException e) {
-            throw new AssertionError(e);
-        }
-    }
-}
diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java
index fdc7225..a8e4d59 100644
--- a/luni/src/test/java/libcore/icu/ICUTest.java
+++ b/luni/src/test/java/libcore/icu/ICUTest.java
@@ -16,10 +16,16 @@
 
 package libcore.icu;
 
+import android.icu.util.TimeZone;
+
 import java.text.BreakIterator;
 import java.text.Collator;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
+import java.util.Set;
+import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
 public class ICUTest extends junit.framework.TestCase {
@@ -258,10 +264,65 @@
   /** Confirms that ICU agrees with the rest of libcore about the version of the TZ data in use. */
   public void testTimeZoneDataVersion() {
     String icu4cTzVersion = ICU.getTZDataVersion();
+
     String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion();
     assertEquals(icu4cTzVersion, zoneInfoTzVersion);
 
     String icu4jTzVersion = android.icu.util.TimeZone.getTZDataVersion();
     assertEquals(icu4jTzVersion, zoneInfoTzVersion);
+
+    String tzLookupTzVersion = TimeZoneFinder.getInstance().getIanaVersion();
+    assertEquals(icu4jTzVersion, tzLookupTzVersion);
+  }
+
+  /**
+   * Confirms that ICU can recognize all the time zone IDs used by the ZoneInfoDB data.
+   * ICU's IDs may be a superset.
+   */
+  public void testTimeZoneIdLookup() {
+    String[] zoneInfoDbAvailableIds = ZoneInfoDB.getInstance().getAvailableIDs();
+
+    // ICU has a known set of IDs. We want ANY because we don't want to filter to ICU's canonical
+    // IDs only.
+    Set<String> icuAvailableIds = android.icu.util.TimeZone.getAvailableIDs(
+            TimeZone.SystemTimeZoneType.ANY, null /* region */, null /* rawOffset */);
+
+    List<String> nonIcuAvailableIds = new ArrayList<>();
+    List<String> creationFailureIds = new ArrayList<>();
+    List<String> noCanonicalLookupIds = new ArrayList<>();
+    List<String> nonSystemIds = new ArrayList<>();
+    for (String zoneInfoDbId : zoneInfoDbAvailableIds) {
+
+      if (zoneInfoDbId.equals("Asia/Hanoi")) {
+        // Known ICU lookup issue: http://b/30277331
+        continue;
+      }
+
+      if (!icuAvailableIds.contains(zoneInfoDbId)) {
+        nonIcuAvailableIds.add(zoneInfoDbId);
+      }
+
+      boolean[] isSystemId = new boolean[1];
+      String canonicalId = android.icu.util.TimeZone.getCanonicalID(zoneInfoDbId, isSystemId);
+      if (canonicalId == null) {
+        noCanonicalLookupIds.add(zoneInfoDbId);
+      }
+      if (!isSystemId[0]) {
+        nonSystemIds.add(zoneInfoDbId);
+      }
+
+      android.icu.util.TimeZone icuTimeZone = android.icu.util.TimeZone.getTimeZone(zoneInfoDbId);
+      if (icuTimeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
+        creationFailureIds.add(zoneInfoDbId);
+      }
+    }
+    assertTrue("Non-ICU available IDs: " + nonIcuAvailableIds
+                    + ", creation failed IDs: " + creationFailureIds
+                    + ", non-system IDs: " + nonSystemIds
+                    + ", ids without canonical IDs: " + noCanonicalLookupIds,
+            nonIcuAvailableIds.isEmpty()
+                    && creationFailureIds.isEmpty()
+                    && nonSystemIds.isEmpty()
+                    && noCanonicalLookupIds.isEmpty());
   }
 }
diff --git a/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java b/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
index 57943b0..c2f8312 100644
--- a/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
+++ b/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
@@ -59,11 +59,4 @@
       assertTrue(TimeZoneNames.forLocale(l) != null);
     }
   }
-
-  public void test_getExemplarLocation() throws Exception {
-    assertEquals("Moscow", TimeZoneNames.getExemplarLocation("en_US", "Europe/Moscow"));
-    assertEquals("Moskau", TimeZoneNames.getExemplarLocation("de_DE", "Europe/Moscow"));
-    assertEquals("Seoul", TimeZoneNames.getExemplarLocation("en_US", "Asia/Seoul"));
-    assertEquals("서울", TimeZoneNames.getExemplarLocation("ko_KR", "Asia/Seoul"));
-  }
 }
diff --git a/luni/src/test/java/libcore/java/io/FileTest.java b/luni/src/test/java/libcore/java/io/FileTest.java
index 9226e02..3705f2b 100644
--- a/luni/src/test/java/libcore/java/io/FileTest.java
+++ b/luni/src/test/java/libcore/java/io/FileTest.java
@@ -393,4 +393,25 @@
             fail();
         } catch (InvalidPathException expected) {}
     }
+
+    // http://b/62301183
+    public void test_canonicalCachesAreOff() throws Exception {
+        File tempDir = createTemporaryDirectory();
+        File f1 = new File(tempDir, "testCannonCachesOff1");
+        f1.createNewFile();
+        File f2  = new File(tempDir, "testCannonCachesOff2");
+        f2.createNewFile();
+        File symlinkFile = new File(tempDir, "symlink");
+
+        // Create a symlink from symlink to f1 and populate canonical path cache
+        assertEquals(0, Runtime.getRuntime().exec("ln -s " + f1.getAbsolutePath() + " " + symlinkFile.getAbsolutePath()).waitFor());
+        assertEquals(symlinkFile.getCanonicalPath(), f1.getCanonicalPath());
+
+        // Remove it and replace it with a symlink to f2 (using java File/Files would flush caches).
+        assertEquals(0, Runtime.getRuntime().exec("rm " + symlinkFile.getAbsolutePath()).waitFor());
+        assertEquals(0, Runtime.getRuntime().exec("ln -s " + f2.getAbsolutePath() + " " + symlinkFile.getAbsolutePath()).waitFor());
+
+        // Did we cache canonical path results? hope not!
+        assertEquals(symlinkFile.getCanonicalPath(), f2.getCanonicalPath());
+    }
 }
diff --git a/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java b/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java
index 2842a0a..74948f6 100644
--- a/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java
@@ -44,15 +44,11 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Random;
-import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
- import sun.net.ftp.FtpLoginException;
+import sun.net.ftp.FtpLoginException;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -253,6 +249,34 @@
         }
     }
 
+    // http://b/35784677
+    public void testCRLFInUserinfo() throws Exception {
+        int serverPort = fakeFtpServer.getServerControlPort();
+        List<String> encodedUserInfos = Arrays.asList(
+                // '\r\n' in the username with password
+                "user%0D%0Acommand:password",
+                // '\r\n' in the password
+                "user:password%0D%0Acommand",
+                // just '\n' in the password
+                "user:password%0Acommand",
+                // just '\n' in the username
+                "user%0Acommand:password"
+        );
+        for (String encodedUserInfo : encodedUserInfos) {
+            String urlString = String.format(Locale.US, "ftp://%s@%s:%s/%s",
+                    encodedUserInfo, SERVER_HOSTNAME, serverPort, FILE_PATH);
+            try {
+                new URL(urlString).openConnection().connect();
+                fail("Connection shouldn't have succeeded: " + urlString);
+            } catch (FtpLoginException expected) {
+                // The original message "Illegal carriage return" gets lost
+                // where FtpURLConnection.connect() translates the
+                // original FtpProtocolException into FtpLoginException.
+                assertEquals("Invalid username/password", expected.getMessage());
+            }
+        }
+    }
+
     private InputStream openFileSystemContents(String fileName) throws IOException {
         String fullFileName = VALID_USER_HOME_DIR + "/" + fileName;
         FileEntry entry = (FileEntry) fileSystem.getEntry(fullFileName);
@@ -279,49 +303,6 @@
         }
     }
 
-    // http://b/35784677
-    public void testCRLFInUserinfo() throws Exception {
-        List<String> encodedUserInfos = Arrays.asList(
-                // '\r\n' in the username with password
-                "user%0D%0Acommand:password",
-                // '\r\n' in the password
-                "user:password%0D%0Acommand",
-                // just '\n' in the password
-                "user:password%0Acommand",
-                // just '\n' in the username
-                "user%0Acommand:password"
-        );
-        for (String encodedUserInfo : encodedUserInfos) {
-            ExecutorService executor = Executors.newSingleThreadExecutor();
-            ServerSocket mockFtpServerSocket = new ServerSocket(0);
-            Future<Void> future = executor.submit(new Callable<Void>() {
-                @Override public Void call() throws Exception {
-                    Socket clientSocket = mockFtpServerSocket.accept();
-                    clientSocket.getOutputStream().write("220 o/".getBytes());
-                    clientSocket.close();
-                    return null;
-                }
-              });
-            executor.shutdown();
-
-            String urlString = String.format(Locale.US, "ftp://%s@%s:%s/%s",
-                    encodedUserInfo, SERVER_HOSTNAME, mockFtpServerSocket.getLocalPort(), FILE_PATH);
-            try {
-                new URL(urlString).openConnection().connect();
-                fail("Connection shouldn't have succeeded: " + urlString);
-            } catch (FtpLoginException expected) {
-                // The original message "Illegal carriage return" gets lost
-                // where FtpURLConnection.connect() translates the
-                // original FtpProtocolException into FtpLoginException.
-                assertEquals("Invalid username/password", expected.getMessage());
-            }
-
-            // Cleanup
-            future.get();
-            mockFtpServerSocket.close();
-        }
-    }
-
     private URL getFileUrlWithCredentials(String user, String password, String filePath) {
         Objects.requireNonNull(user);
         Objects.requireNonNull(filePath);
diff --git a/luni/src/test/java/libcore/java/text/DecimalFormatTest.java b/luni/src/test/java/libcore/java/text/DecimalFormatTest.java
index 60fafac..766a8d3 100644
--- a/luni/src/test/java/libcore/java/text/DecimalFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/DecimalFormatTest.java
@@ -434,7 +434,7 @@
         // Confirm ICU and java.text disagree.
         // java.text doesn't localize PerMill and fallback to default char U+2030
         // when PerMill in that locale has more than one visible characters.
-        locale = new Locale("en_US_POSIX");
+        locale = Locale.forLanguageTag("en-US-POSIX");
         {
             android.icu.text.DecimalFormat df = new android.icu.text.DecimalFormat(pattern,
                     new android.icu.text.DecimalFormatSymbols(locale));
diff --git a/luni/src/test/java/libcore/util/TimeZoneFinderTest.java b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
index 0b31c9a..f675150 100644
--- a/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
+++ b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
@@ -30,6 +30,8 @@
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -39,6 +41,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 public class TimeZoneFinderTest {
@@ -96,16 +99,16 @@
 
     @Test
     public void createInstanceWithFallback() throws Exception {
-        String validXml1 = "<timezones>\n"
+        String validXml1 = "<timezones ianaversion=\"2017c\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n";
-        String validXml2 = "<timezones>\n"
+        String validXml2 = "<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/Paris</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
@@ -121,26 +124,36 @@
 
         TimeZoneFinder file1ThenFile2 =
                 TimeZoneFinder.createInstanceWithFallback(validFile1, validFile2);
+        assertEquals("2017c", file1ThenFile2.getIanaVersion());
+        assertEquals(list("Europe/London"), file1ThenFile2.lookupTimeZoneIdsByCountry("gb"));
         assertZonesEqual(zones("Europe/London"), file1ThenFile2.lookupTimeZonesByCountry("gb"));
 
         TimeZoneFinder missingFileThenFile1 =
                 TimeZoneFinder.createInstanceWithFallback(missingFile, validFile1);
+        assertEquals("2017c", missingFileThenFile1.getIanaVersion());
+        assertEquals(list("Europe/London"), missingFileThenFile1.lookupTimeZoneIdsByCountry("gb"));
         assertZonesEqual(zones("Europe/London"),
                 missingFileThenFile1.lookupTimeZonesByCountry("gb"));
 
         TimeZoneFinder file2ThenFile1 =
                 TimeZoneFinder.createInstanceWithFallback(validFile2, validFile1);
+        assertEquals("2017b", file2ThenFile1.getIanaVersion());
+        assertEquals(list("Europe/Paris"), file2ThenFile1.lookupTimeZoneIdsByCountry("gb"));
         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.getIanaVersion());
+        assertNull(invalidThenValid.lookupTimeZoneIdsByCountry("gb"));
         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.getIanaVersion());
+        assertNull(missingFiles.lookupTimeZoneIdsByCountry("gb"));
         assertNull(missingFiles.lookupTimeZonesByCountry("gb"));
     }
 
@@ -156,12 +169,12 @@
 
     @Test
     public void xmlParsing_missingCountryZones() throws Exception {
-        checkValidateThrowsParserException("<timezones></timezones>\n");
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\"></timezones>\n");
     }
 
     @Test
     public void xmlParsing_noCountriesOk() throws Exception {
-        validate("<timezones>\n"
+        validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
@@ -169,167 +182,177 @@
 
     @Test
     public void xmlParsing_unexpectedComments() throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <!-- This is a comment -->"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
         // This is a crazy comment, but also helps prove that TEXT nodes are coalesced by the
         // parser.
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/<!-- Don't freak out! -->London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
     }
 
     @Test
     public void xmlParsing_unexpectedElementsIgnored() throws Exception {
         String unexpectedElement = "<unexpected-element>\n<a /></unexpected-element>\n";
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  " + unexpectedElement
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
                 + "    " + unexpectedElement
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      " + unexpectedElement
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\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"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London", "Europe/Paris"),
+                finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "    " + unexpectedElement
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
         // This test is important because it ensures we can extend the format in future with
         // more information.
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "  " + unexpectedElement
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
     }
 
     @Test
     public void xmlParsing_unexpectedTextIgnored() throws Exception {
         String unexpectedText = "unexpected-text";
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  " + unexpectedText
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
                 + "    " + unexpectedText
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      " + unexpectedText
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
-        assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
 
-        finder = validate("<timezones>\n"
+        finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\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"));
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+        assertEquals(list("Europe/London", "Europe/Paris"),
+                finder.lookupTimeZoneIdsByCountry("gb"));
     }
 
     @Test
     public void xmlParsing_truncatedInput() throws Exception {
-        checkValidateThrowsParserException("<timezones>\n");
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n");
 
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n");
 
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n");
+                + "    <country code=\"gb\" default=\"Europe/London\">\n");
 
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n");
 
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n");
 
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n");
@@ -337,9 +360,9 @@
 
     @Test
     public void xmlParsing_unexpectedChildInTimeZoneIdThrows() throws Exception {
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id><unexpected-element /></id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
@@ -348,22 +371,34 @@
 
     @Test
     public void xmlParsing_unknownTimeZoneIdIgnored() throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Unknown_Id</id>\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
         assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
     }
 
     @Test
     public void xmlParsing_missingCountryCode() throws Exception {
-        checkValidateThrowsParserException("<timezones>\n"
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country>\n"
+                + "    <country default=\"Europe/London\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+    }
+
+    @Test
+    public void xmlParsing_missingDefault() throws Exception {
+        checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
@@ -372,18 +407,19 @@
 
     @Test
     public void xmlParsing_unknownCountryReturnsNull() throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
                 + "  </countryzones>\n"
                 + "</timezones>\n");
+        assertNull(finder.lookupTimeZoneIdsByCountry("gb"));
         assertNull(finder.lookupTimeZonesByCountry("gb"));
     }
 
     @Test
     public void lookupTimeZonesByCountry_structuresAreImmutable() throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"gb\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
@@ -394,14 +430,91 @@
         assertImmutableList(gbList);
         assertImmutableTimeZone(gbList.get(0));
 
+        // Check country code normalization works too.
+        assertEquals(1, finder.lookupTimeZonesByCountry("GB").size());
+
         assertNull(finder.lookupTimeZonesByCountry("unknown"));
     }
 
     @Test
-    public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+    public void lookupTimeZoneIdsByCountry_structuresAreImmutable() throws Exception {
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"xx\">\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        List<String> gbList = finder.lookupTimeZoneIdsByCountry("gb");
+        assertEquals(1, gbList.size());
+        assertImmutableList(gbList);
+
+        // Check country code normalization works too.
+        assertEquals(1, finder.lookupTimeZoneIdsByCountry("GB").size());
+
+        assertNull(finder.lookupTimeZoneIdsByCountry("unknown"));
+    }
+
+    @Test
+    public void lookupDefaultTimeZoneIdByCountry() throws Exception {
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+
+        // Check country code normalization works too.
+        assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("GB"));
+    }
+
+    /**
+     * At runtime we don't validate too much since there's nothing we can do if the data is
+     * incorrect.
+     */
+    @Test
+    public void lookupDefaultTimeZoneIdByCountry_notCountryTimeZoneButValid() throws Exception {
+        String xml = "<timezones ianaversion=\"2017b\">\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\" default=\"America/New_York\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n";
+        // validate() should fail because America/New_York is not one of the "gb" zones listed.
+        checkValidateThrowsParserException(xml);
+
+        // But it should still work at runtime.
+        TimeZoneFinder finder = TimeZoneFinder.createInstanceForTests(xml);
+        assertEquals("America/New_York", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+    }
+
+    @Test
+    public void lookupDefaultTimeZoneIdByCountry_invalidDefault() throws Exception {
+        String xml = "<timezones ianaversion=\"2017b\">\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\" default=\"Moon/Tranquility_Base\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "      <id>Moon/Tranquility_Base</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n";
+        // validate() should pass because the IDs all match.
+        TimeZoneFinder finder = validate(xml);
+
+        // But "Moon/Tranquility_Base" is not a valid time zone ID so should not be used.
+        assertNull(finder.lookupDefaultTimeZoneIdByCountry("gb"));
+    }
+
+    @Test
+    public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"xx\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
@@ -412,6 +525,11 @@
                 finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
                         true /* isDst */, WHEN_DST, null /* bias */));
 
+        // Check country code normalization works too.
+        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,
@@ -423,9 +541,9 @@
 
     @Test
     public void lookupTimeZoneByCountryAndOffset_oneCandidate() throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"xx\">\n"
+                + "    <country code=\"xx\" default=\"Europe/London\">\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
                 + "  </countryzones>\n"
@@ -492,9 +610,9 @@
     @Test
     public void lookupTimeZoneByCountryAndOffset_multipleNonOverlappingCandidates()
             throws Exception {
-        TimeZoneFinder finder = validate("<timezones>\n"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"xx\">\n"
+                + "    <country code=\"xx\" default=\"Europe/London\">\n"
                 + "      <id>America/New_York</id>\n"
                 + "      <id>Europe/London</id>\n"
                 + "    </country>\n"
@@ -563,9 +681,9 @@
     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"
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
                 + "  <countryzones>\n"
-                + "    <country code=\"xx\">\n"
+                + "    <country code=\"xx\" default=\"Europe/London\">\n"
                 + "      <id>Atlantic/Reykjavik</id>\n"
                 + "      <id>Europe/London</id>\n"
                 + "      <id>Etc/UTC</id>\n"
@@ -659,18 +777,93 @@
             // Android uses lower case, IANA uses upper.
             countryCode = countryCode.toLowerCase();
 
-            List<String> ianaZoneIds = countryEntry.getValue().stream().sorted()
-                    .collect(Collectors.toList());
+            List<String> androidZoneIds = timeZoneFinder.lookupTimeZoneIdsByCountry(countryCode);
             List<TimeZone> androidZones = timeZoneFinder.lookupTimeZonesByCountry(countryCode);
-            List<String> androidZoneIds =
-                    androidZones.stream().map(TimeZone::getID).sorted()
-                            .collect(Collectors.toList());
+            List<String> androidIdsFromZones =
+                    androidZones.stream().map(TimeZone::getID).collect(Collectors.toList());
+            assertEquals("lookupTimeZonesByCountry and lookupTimeZoneIdsByCountry differ for "
+                            + countryCode, androidZoneIds, androidIdsFromZones);
 
+            Collection<String> ianaZoneIds = countryEntry.getValue();
             assertEquals("Android zones for " + countryCode + " do not match IANA data",
-                    ianaZoneIds, androidZoneIds);
+                    sort(ianaZoneIds), sort(androidZoneIds));
         }
     }
 
+    @Test
+    public void xmlParsing_missingIanaVersionAttribute() throws Exception {
+        // The <timezones> element will typically have an ianaversion attribute, but it's not
+        // required for parsing.
+        TimeZoneFinder finder = validate("<timezones>\n"
+                + "  <countryzones>\n"
+                + "    <country code=\"gb\" default=\"Europe/London\">\n"
+                + "      <id>Europe/London</id>\n"
+                + "    </country>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
+
+        assertNull(finder.getIanaVersion());
+    }
+
+    @Test
+    public void getIanaVersion() throws Exception {
+        final String expectedIanaVersion = "2017b";
+
+        TimeZoneFinder finder = validate("<timezones ianaversion=\"" + expectedIanaVersion + "\">\n"
+                + "  <countryzones>\n"
+                + "  </countryzones>\n"
+                + "</timezones>\n");
+        assertEquals(expectedIanaVersion, finder.getIanaVersion());
+    }
+
+    @Test
+    public void createValidatedCountryTimeZones_filtersBadIds() throws Exception {
+        String countryIso = "iso";
+        String knownTimeZoneId1 = "Europe/London";
+        String knownTimeZoneId2 = "America/Los_Angeles";
+        String knownTimeZoneId3 = "America/New_York";
+        String unknownTimeZoneId = "Moon/Tranquility_Base";
+
+        List<String> countryZoneIds = list(
+                knownTimeZoneId1, knownTimeZoneId2, unknownTimeZoneId, knownTimeZoneId3);
+        TimeZoneFinder.CountryTimeZones countryTimeZones =
+                TimeZoneFinder.createValidatedCountryTimeZones(countryIso, knownTimeZoneId1,
+                        countryZoneIds, "debugInfoIgnored");
+
+        assertEquals(countryIso, countryTimeZones.getCountryIso());
+
+        assertEquals(knownTimeZoneId1, countryTimeZones.getDefaultTimeZoneId());
+        assertEquals(knownTimeZoneId1, countryTimeZones.getDefaultTimeZoneId());
+
+        // Validation should have filtered the unknown ID.
+        String[] expectedTimeZoneIds = { knownTimeZoneId1, knownTimeZoneId2, knownTimeZoneId3 };
+        assertEquals(list(expectedTimeZoneIds), countryTimeZones.getTimeZoneIds());
+        List<TimeZone> timeZones = countryTimeZones.getTimeZones();
+        for (int i = 0; i < timeZones.size(); i++) {
+            TimeZone timeZone = timeZones.get(i);
+            assertEquals(expectedTimeZoneIds[i], timeZone.getID());
+            assertTrue(timeZone.isFrozen());
+        }
+    }
+
+    @Test
+    public void createValidatedCountryTimeZones_filtersBadDefaultId() throws Exception {
+        String countryIso = "iso";
+        String unknownTimeZoneId = "Moon/Tranquility_Base";
+
+        List<String> countryZoneIds = list(unknownTimeZoneId);
+        TimeZoneFinder.CountryTimeZones countryTimeZones =
+                TimeZoneFinder.createValidatedCountryTimeZones(countryIso, unknownTimeZoneId,
+                        countryZoneIds, "debugInfoIgnored");
+
+        assertEquals(countryIso, countryTimeZones.getCountryIso());
+
+        assertNull(countryTimeZones.getDefaultTimeZoneId());
+        assertEquals(Collections.emptyList(), countryTimeZones.getTimeZoneIds());
+        assertEquals(Collections.emptyList(), countryTimeZones.getTimeZones());
+    }
+
     private void assertImmutableTimeZone(TimeZone timeZone) {
         try {
             timeZone.setRawOffset(1000);
@@ -679,9 +872,9 @@
         }
     }
 
-    private static void assertImmutableList(List<TimeZone> timeZones) {
+    private static <X> void assertImmutableList(List<X> list) {
         try {
-            timeZones.add(null);
+            list.add(null);
             fail();
         } catch (UnsupportedOperationException expected) {
         }
@@ -711,6 +904,15 @@
         return timeZoneFinder;
     }
 
+    private static <X> List<X> list(X... values) {
+        return Arrays.asList(values);
+    }
+
+    private static <X> List<X> sort(Collection<X> value) {
+        return value.stream().sorted()
+                .collect(Collectors.toList());
+    }
+
     private static List<TimeZone> zones(String... ids) {
         return Arrays.stream(ids).map(TimeZone::getTimeZone).collect(Collectors.toList());
     }
diff --git a/ojluni/src/main/java/java/io/FileSystem.java b/ojluni/src/main/java/java/io/FileSystem.java
index 4b0260d..86d8fff 100644
--- a/ojluni/src/main/java/java/io/FileSystem.java
+++ b/ojluni/src/main/java/java/io/FileSystem.java
@@ -226,8 +226,11 @@
 
     // Flags for enabling/disabling performance optimizations for file
     // name canonicalization
-    static boolean useCanonCaches      = true;
-    static boolean useCanonPrefixCache = true;
+    // Android-changed: Disabled caches for security reasons (b/62301183)
+    //static boolean useCanonCaches      = true;
+    //static boolean useCanonPrefixCache = true;
+    static boolean useCanonCaches      = false;
+    static boolean useCanonPrefixCache = false;
 
     private static boolean getBooleanProperty(String prop, boolean defaultVal) {
         String val = System.getProperty(prop);
diff --git a/ojluni/src/main/java/java/io/RandomAccessFile.java b/ojluni/src/main/java/java/io/RandomAccessFile.java
index df607cb..985f8ed 100755
--- a/ojluni/src/main/java/java/io/RandomAccessFile.java
+++ b/ojluni/src/main/java/java/io/RandomAccessFile.java
@@ -28,6 +28,7 @@
 
 import java.nio.channels.FileChannel;
 import sun.nio.ch.FileChannelImpl;
+import android.system.Os;
 import android.system.ErrnoException;
 import dalvik.system.CloseGuard;
 import libcore.io.IoBridge;
@@ -68,6 +69,12 @@
     // BEGIN Android-added: CloseGuard and some helper fields for Android changes in this file.
     private final CloseGuard guard = CloseGuard.get();
     private final byte[] scratch = new byte[8];
+
+    private static final int FLUSH_NONE = 0;
+    private static final int FLUSH_FSYNC = 1;
+    private static final int FLUSH_FDATASYNC = 2;
+    private int flushAfterWrite = FLUSH_NONE;
+
     private int mode;
     // END Android-added: CloseGuard and some helper fields for Android changes in this file.
 
@@ -230,9 +237,17 @@
             rw = true;
             if (mode.length() > 2) {
                 if (mode.equals("rws")) {
-                    imode |= O_SYNC;
+                    // Android-changed: For performance reasons, use fsync after each write.
+                    // RandomAccessFile.write may result in multiple write syscalls,
+                    // O_SYNC/O_DSYNC flags will cause a blocking wait on each syscall. Replacing
+                    // them with single fsync/fdatasync call gives better performance with only
+                    // minor decrease in reliability.
+                    // imode |= O_SYNC;
+                    flushAfterWrite = FLUSH_FSYNC;
                 } else if (mode.equals("rwd")) {
-                    imode |= O_DSYNC;
+                    // Android-changed: For performance reasons, use fdatasync after each write.
+                    // imode |= O_DSYNC;
+                    flushAfterWrite = FLUSH_FDATASYNC;
                 } else {
                     imode = -1;
                 }
@@ -267,10 +282,29 @@
 
         // BEGIN Android-changed: Use IoBridge.open() instead of open.
         fd = IoBridge.open(name, imode);
+        maybeSync();
         guard.open("close");
         // END Android-changed: Use IoBridge.open() instead of open.
     }
 
+    // BEGIN Android-added: Sync after rws/rwd write
+    private void maybeSync() {
+        if (flushAfterWrite == FLUSH_FSYNC) {
+            try {
+                fd.sync();
+            } catch (IOException e) {
+                // Ignored
+            }
+        } else if (flushAfterWrite == FLUSH_FDATASYNC) {
+            try {
+                Os.fdatasync(fd);
+            } catch (ErrnoException e) {
+                // Ignored
+            }
+        }
+    }
+    // END Android-added: Sync after rws/rwd write
+
     /**
      * Returns the opaque file descriptor object associated with this
      * stream.
@@ -329,7 +363,7 @@
      *                          end-of-file has been reached.
      */
     public int read() throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // Android-changed: Implement on top of libcore os API.
         // return read0();
         return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
     }
@@ -342,7 +376,7 @@
      * @exception IOException If an I/O error has occurred.
      */
     private int readBytes(byte b[], int off, int len) throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // Android-changed: Implement on top of libcore os API.
         ioTracker.trackIo(len, IoTracker.Mode.READ);
         return IoBridge.read(fd, b, off, len);
     }
@@ -485,10 +519,11 @@
      * @exception  IOException  if an I/O error occurs.
      */
     public void write(int b) throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         // write0(b);
         scratch[0] = (byte) (b & 0xff);
         write(scratch, 0, 1);
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -500,9 +535,11 @@
      * @exception IOException If an I/O error has occurred.
      */
     private void writeBytes(byte b[], int off, int len) throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         ioTracker.trackIo(len, IoTracker.Mode.WRITE);
         IoBridge.write(fd, b, off, len);
+        maybeSync();
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -539,12 +576,13 @@
      * @exception  IOException  if an I/O error occurs.
      */
     public long getFilePointer() throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         try {
             return Libcore.os.lseek(fd, 0L, SEEK_CUR);
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
         }
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -567,7 +605,7 @@
             // throw new IOException("Negative seek offset");
             throw new IOException("offset < 0: " + pos);
         } else {
-            // Android-changed: Implement on top of low-level API, not directly natively.
+            // BEGIN Android-changed: Implement on top of libcore os API.
             // seek0(pos);
             try {
                 Libcore.os.lseek(fd, pos, SEEK_SET);
@@ -575,6 +613,7 @@
             } catch (ErrnoException errnoException) {
                 throw errnoException.rethrowAsIOException();
             }
+            // END Android-changed: Implement on top of libcore os API.
         }
     }
 
@@ -585,12 +624,13 @@
      * @exception  IOException  if an I/O error occurs.
      */
     public long length() throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         try {
             return Libcore.os.fstat(fd).st_size;
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
         }
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -613,7 +653,7 @@
      * @since      1.2
      */
     public void setLength(long newLength) throws IOException {
-        // BEGIN Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         if (newLength < 0) {
             throw new IllegalArgumentException("newLength < 0");
         }
@@ -627,7 +667,8 @@
         if (filePointer > newLength) {
             seek(newLength);
         }
-        // END Android-changed: Implement on top of low-level API, not directly natively.
+        maybeSync();
+        // END Android-changed: Implement on top of libcore os API.
     }
 
 
@@ -654,12 +695,12 @@
             closed = true;
         }
 
-        // BEGIN Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         if (channel != null && channel.isOpen()) {
             channel.close();
         }
         IoBridge.closeAndSignalBlockedThreads(fd);
-        // END Android-changed: Implement on top of low-level API, not directly natively.
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     //
diff --git a/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java b/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
index 85eac6f..e5b0301 100644
--- a/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
+++ b/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
@@ -557,7 +557,6 @@
                 // Also, close the CloseGuard when the #close is called.
                 if (!closePending) {
                     closePending = true;
-                    SocketTagger.get().untag(fd);
                     guard.close();
 
                     if (fdUseCount == 0) {
diff --git a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
index a9f11c8..59b8cdc 100644
--- a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
+++ b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
@@ -660,12 +660,12 @@
             values[0] = String.valueOf(localeData.decimalSeparator);
             values[1] = String.valueOf(localeData.groupingSeparator);
             values[2] = String.valueOf(localeData.patternSeparator);
-            values[3] = String.valueOf(localeData.percent);
+            values[3] = localeData.percent;
             values[4] = String.valueOf(localeData.zeroDigit);
             values[5] = "#";
             values[6] = localeData.minusSign;
             values[7] = localeData.exponentSeparator;
-            values[8] = String.valueOf(localeData.perMill);
+            values[8] = localeData.perMill;
             values[9] = localeData.infinity;
             values[10] = localeData.NaN;
             data[0] = values;
diff --git a/ojluni/src/main/java/javax/crypto/Cipher.java b/ojluni/src/main/java/javax/crypto/Cipher.java
index dfaf94d..010587d 100644
--- a/ojluni/src/main/java/javax/crypto/Cipher.java
+++ b/ojluni/src/main/java/javax/crypto/Cipher.java
@@ -692,6 +692,7 @@
 
     static final Cipher createCipher(String transformation, Provider provider)
         throws NoSuchAlgorithmException, NoSuchPaddingException {
+        Providers.checkBouncyCastleDeprecation(provider, "Cipher", transformation);
         String[] tokenizedTransformation = tokenizeTransformation(transformation);
 
         CipherSpiAndProvider cipherSpiAndProvider = null;
diff --git a/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java b/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java
index d6c3f76..93b5dc7 100644
--- a/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java
+++ b/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java
@@ -260,6 +260,8 @@
      * @throws NullPointerException if {@code s} is {@code null}
      *
      * @since 1.8
+     *
+     * @hide
      */
     public Socket createSocket(Socket s, InputStream consumed,
             boolean autoClose) throws IOException {
diff --git a/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java b/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java
index 9526d3b..0c117d4 100644
--- a/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java
+++ b/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2017, 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/sun/nio/ch/DatagramDispatcher.java b/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java
index 0c5ec0b..328e4ee 100644
--- a/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java
+++ b/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java
@@ -62,7 +62,7 @@
     }
 
     void preClose(FileDescriptor fd) throws IOException {
-        FileDispatcherImpl.preCloseImpl(fd);
+        FileDispatcherImpl.preClose0(fd);
     }
 
     static native int read0(FileDescriptor fd, long address, int len)
diff --git a/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java b/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java
index 75bfd13..bafb74e 100644
--- a/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java
+++ b/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java
@@ -128,11 +128,6 @@
         overhead to set up when the number of FDs being polled is small
         (which is the common case on Android).
 
-        We also need to make sure that all tagged sockets are untagged before
-        they're preclosed at the platform level. However, there's nothing we
-        can do about applications that abuse public api (android.net.TrafficStats).
-
-
         ALTERNATE APPROACHES :
         ----------------------
         For completeness, I'm listing a couple of other approaches that were
diff --git a/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java b/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java
index 96163c2..b530b3a 100644
--- a/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java
+++ b/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java
@@ -26,10 +26,8 @@
 package sun.nio.ch;
 
 import dalvik.system.BlockGuard;
-import dalvik.system.SocketTagger;
 
 import java.io.*;
-import java.net.SocketException;
 
 class FileDispatcherImpl extends FileDispatcher {
 
@@ -108,19 +106,6 @@
     }
 
     void preClose(FileDescriptor fd) throws IOException {
-        preCloseImpl(fd);
-    }
-
-    static void preCloseImpl(FileDescriptor fd) throws IOException {
-        if (fd.isSocket$()) {
-            // Always untag sockets before the preClose. The file descriptor will describe
-            // a different file (/dev/null) after preClose0.
-            try {
-                SocketTagger.get().untag(fd);
-            } catch (SocketException ignored) {
-            }
-        }
-
         preClose0(fd);
     }
 
@@ -174,7 +159,7 @@
 
     static native void close0(FileDescriptor fd) throws IOException;
 
-    private static native void preClose0(FileDescriptor fd) throws IOException;
+    static native void preClose0(FileDescriptor fd) throws IOException;
 
     static native void closeIntFD(int fd) throws IOException;
 
diff --git a/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java b/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java
index 4f4e469..8dde1f9 100644
--- a/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java
+++ b/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java
@@ -26,7 +26,6 @@
 package sun.nio.ch;
 
 import dalvik.system.BlockGuard;
-import dalvik.system.SocketTagger;
 
 import java.io.*;
 
@@ -63,6 +62,6 @@
     }
 
     void preClose(FileDescriptor fd) throws IOException {
-        FileDispatcherImpl.preCloseImpl(fd);
+        FileDispatcherImpl.preClose0(fd);
     }
 }
diff --git a/ojluni/src/main/java/sun/security/jca/Providers.java b/ojluni/src/main/java/sun/security/jca/Providers.java
index e50ce23..b0026b3 100644
--- a/ojluni/src/main/java/sun/security/jca/Providers.java
+++ b/ojluni/src/main/java/sun/security/jca/Providers.java
@@ -348,7 +348,8 @@
     }
 
     // The set of algorithms that are deprecated.  This list is created using
-    // libcore/tools/crypto/src/java/libcore/java/security/ProviderOverlap.java
+    // libcore/tools/crypto/src/java/libcore/java/security/ProviderOverlap.java, with
+    // additional Ciphers added manually (see comment below).
     private static final Set<String> DEPRECATED_ALGORITHMS = new HashSet<String>();
     static {
         DEPRECATED_ALGORITHMS.addAll(Arrays.asList(
@@ -367,16 +368,27 @@
                 "ALGORITHMPARAMETERS.TDEA",
                 "CERTIFICATEFACTORY.X.509",
                 "CERTIFICATEFACTORY.X509",
-                // TODO(flooey, b/67626877): Implement Cipher support
-                // "CIPHER.1.2.840.113549.3.4",
-                // "CIPHER.2.16.840.1.101.3.4.1.26",
-                // "CIPHER.2.16.840.1.101.3.4.1.46",
-                // "CIPHER.2.16.840.1.101.3.4.1.6",
-                // "CIPHER.AES/GCM/NOPADDING",
-                // "CIPHER.ARC4",
-                // "CIPHER.ARCFOUR",
-                // "CIPHER.OID.1.2.840.113549.3.4",
-                // "CIPHER.RC4",
+                // List of Ciphers produced by ProviderOverlap:
+                "CIPHER.1.2.840.113549.3.4",
+                "CIPHER.2.16.840.1.101.3.4.1.26",
+                "CIPHER.2.16.840.1.101.3.4.1.46",
+                "CIPHER.2.16.840.1.101.3.4.1.6",
+                "CIPHER.AES/GCM/NOPADDING",
+                "CIPHER.ARC4",
+                "CIPHER.ARCFOUR",
+                "CIPHER.OID.1.2.840.113549.3.4",
+                "CIPHER.RC4",
+                // End of Ciphers produced by ProviderOverlap
+                // Additional ciphers transformations that will resolve to the same things as
+                // the automatically-produced overlap due to the Cipher transformation rules.
+                // These have been added manually.
+                "CIPHER.ARC4/ECB/NOPADDING",
+                "CIPHER.ARC4/NONE/NOPADDING",
+                "CIPHER.ARCFOUR/ECB/NOPADDING",
+                "CIPHER.ARCFOUR/NONE/NOPADDING",
+                "CIPHER.RC4/ECB/NOPADDING",
+                "CIPHER.RC4/NONE/NOPADDING",
+                // End of additional Ciphers
                 "KEYAGREEMENT.ECDH",
                 "KEYFACTORY.1.2.840.10045.2.1",
                 "KEYFACTORY.1.2.840.113549.1.1.1",
diff --git a/ojluni/src/main/native/Net.c b/ojluni/src/main/native/Net.c
index 01ea3f2..550711e 100644
--- a/ojluni/src/main/native/Net.c
+++ b/ojluni/src/main/native/Net.c
@@ -255,7 +255,6 @@
             JNU_ThrowByNameWithLastError(env,
                                          JNU_JAVANETPKG "SocketException",
                                          "Unable to set IPV6_V6ONLY");
-            untagSocket(env, fd);
             close(fd);
             return -1;
         }
@@ -269,7 +268,6 @@
             JNU_ThrowByNameWithLastError(env,
                                          JNU_JAVANETPKG "SocketException",
                                          "Unable to set SO_REUSEADDR");
-            untagSocket(env, fd);
             close(fd);
             return -1;
         }
@@ -299,7 +297,6 @@
             JNU_ThrowByNameWithLastError(env,
                                          JNU_JAVANETPKG "SocketException",
                                          "Unable to set IPV6_MULTICAST_HOPS");
-            untagSocket(env, fd);
             close(fd);
             return -1;
         }
diff --git a/ojluni/src/main/native/net_util_md.c b/ojluni/src/main/native/net_util_md.c
index d15e29d..3fb3eb6 100644
--- a/ojluni/src/main/native/net_util_md.c
+++ b/ojluni/src/main/native/net_util_md.c
@@ -398,7 +398,6 @@
             /**
              * SIOCGLIFNUM failed - assume IPv6 not configured
              */
-            untagSocket(env, fd);
             close(fd);
             return JNI_FALSE;
         }
diff --git a/ojluni/src/main/native/net_util_md.h b/ojluni/src/main/native/net_util_md.h
index 5c6dd42..94a8877 100644
--- a/ojluni/src/main/native/net_util_md.h
+++ b/ojluni/src/main/native/net_util_md.h
@@ -60,7 +60,6 @@
 #endif
 
 extern int tagSocket(JNIEnv* env, int fd);
-extern void untagSocket(JNIEnv* env, int fd);
 
 #else
 
@@ -79,7 +78,6 @@
 #define NET_Poll        poll
 
 #define tagSocket(env,fd)    (void)0
-#define untagSocket(env,fd)  (void)0
 
 #endif
 
diff --git a/ojluni/src/main/native/socket_tagger_util.cpp b/ojluni/src/main/native/socket_tagger_util.cpp
index 611b92e..b62898f 100644
--- a/ojluni/src/main/native/socket_tagger_util.cpp
+++ b/ojluni/src/main/native/socket_tagger_util.cpp
@@ -37,17 +37,4 @@
     return fd;
 }
 
-void untagSocket(JNIEnv* env, int fd) {
-    if (env->ExceptionOccurred()) { return; }
-    jmethodID get = env->GetStaticMethodID(JniConstants::socketTaggerClass,
-                                           "get", "()Ldalvik/system/SocketTagger;");
-    jobject socketTagger =
-        env->CallStaticObjectMethod(JniConstants::socketTaggerClass, get);
-    jmethodID untag = env->GetMethodID(JniConstants::socketTaggerClass,
-                                       "untag", "(Ljava/io/FileDescriptor;)V");
-
-    jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
-    env->CallVoidMethod(socketTagger, untag, fileDescriptor);
-}
-
 }