Move libcore.timezone classes to I18n module

Note that this CL does not miminize @IntraCoreApi surface.

Bug: 141747409
Test: atest CtsLibcoreOjTestCases CtsLibcoreTestCases CtsIcuTestCases
Merged-In: I8da8fe64c3ecf94f5de3e597dd98cdbd5c1eecf5
Change-Id: I8da8fe64c3ecf94f5de3e597dd98cdbd5c1eecf5
diff --git a/android_icu4j/Android.bp b/android_icu4j/Android.bp
index 5f40abe..6749993 100644
--- a/android_icu4j/Android.bp
+++ b/android_icu4j/Android.bp
@@ -50,6 +50,18 @@
     path: "libcore_bridge/src/java",
 }
 
+// timezone-related source that is also used in host tests / tools and its
+// dependencies.
+filegroup {
+    name: "timezone_host_files",
+    srcs: [
+        "libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java",
+        "libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java",
+    ],
+    path: "libcore_bridge/src/java",
+    visibility: ["//libcore"],
+}
+
 // core-repackaged-icu4j contains only the repackaged ICU4J that does not
 // use any internal or android specific code. If it ever did then it could depend on
 // art-module-intra-core-api-stubs-system-modules (a superset) instead.
@@ -94,6 +106,7 @@
     permitted_packages: [
         "android.icu",
         "com.android.icu",
+        "com.android.i18n.timezone",
     ],
     installable: true,
     hostdex: false,
@@ -309,6 +322,7 @@
     static_libs: [
         "junit",
         "junit-params",
+        "tzdata-testing",
     ],
 
     patch_module: "java.base",
diff --git a/android_icu4j/api/intra/current.txt b/android_icu4j/api/intra/current.txt
index 850884b..7c43c6d 100644
--- a/android_icu4j/api/intra/current.txt
+++ b/android_icu4j/api/intra/current.txt
@@ -188,6 +188,44 @@
 
 }
 
+package com.android.i18n.timezone {
+
+  public final class TimeZoneDataFiles {
+    method public static String generateIcuDataPath();
+    method public static String getSystemTzFile(String);
+    method public static String[] getTimeZoneFilePaths(String);
+    method public static String getTimeZoneModuleTzFile(String);
+  }
+
+  public final class ZoneInfoData implements java.lang.Cloneable {
+    ctor public ZoneInfoData(com.android.i18n.timezone.ZoneInfoData);
+    method public static com.android.i18n.timezone.ZoneInfoData createFromSerializationFields(String, java.io.ObjectInputStream.GetField) throws java.io.IOException;
+    method public static com.android.i18n.timezone.ZoneInfoData createZoneInfo(String, long, java.nio.ByteBuffer) throws java.io.IOException;
+    method public int getDSTSavings();
+    method public String getID();
+    method public int getOffset(int, int, int, int, int, int);
+    method public int getOffset(long);
+    method public int getOffsetsByUtcTime(long, int[]);
+    method public int getRawOffset();
+    method public long[] getTransitionsForAppCompat();
+    method public boolean hasSameRules(com.android.i18n.timezone.ZoneInfoData);
+    method public boolean inDaylightTime(java.util.Date);
+    method public void setRawOffset(int);
+    method public boolean useDaylightTime();
+    method public void writeToSerializationFields(java.io.ObjectOutputStream.PutField);
+    field public static final java.io.ObjectStreamField[] ZONEINFO_SERIALIZED_FIELDS;
+  }
+
+  public final class ZoneInfoDb implements java.lang.AutoCloseable {
+    method public String[] getAvailableIDs();
+    method public String[] getAvailableIDs(int);
+    method public static com.android.i18n.timezone.ZoneInfoDb getInstance();
+    method public String getVersion();
+    method public com.android.i18n.timezone.ZoneInfoData makeZoneInfoData(String);
+  }
+
+}
+
 package com.android.icu.charset {
 
   public final class CharsetICU extends java.nio.charset.Charset {
diff --git a/android_icu4j/api/legacy_platform/current.txt b/android_icu4j/api/legacy_platform/current.txt
index 41e67f5..25f801e 100644
--- a/android_icu4j/api/legacy_platform/current.txt
+++ b/android_icu4j/api/legacy_platform/current.txt
@@ -50,6 +50,92 @@
 
 }
 
+package com.android.i18n.timezone {
+
+  public class DebugInfo {
+    ctor public DebugInfo();
+    method public com.android.i18n.timezone.DebugInfo addStringEntry(String, String);
+    method public com.android.i18n.timezone.DebugInfo addStringEntry(String, int);
+    method public java.util.List<com.android.i18n.timezone.DebugInfo.DebugEntry> getDebugEntries();
+  }
+
+  public static class DebugInfo.DebugEntry {
+    ctor public DebugInfo.DebugEntry(String, String);
+    method public String getKey();
+    method public String getStringValue();
+  }
+
+  public class I18nModuleDebug {
+    method public static com.android.i18n.timezone.DebugInfo getDebugInfo();
+  }
+
+  public final class TimeZoneDataFiles {
+    method public static String getDataTimeZoneFile(String);
+    method public static String getDataTimeZoneRootDir();
+    method public static String getTimeZoneModuleTzVersionFile();
+  }
+
+  public final class TzDataSetVersion {
+    ctor public TzDataSetVersion(int, int, String, int) throws com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
+    method public static int currentFormatMajorVersion();
+    method public static int currentFormatMinorVersion();
+    method public int getFormatMajorVersion();
+    method public int getFormatMinorVersion();
+    method public int getRevision();
+    method public String getRulesVersion();
+    method public static boolean isCompatibleWithThisDevice(com.android.i18n.timezone.TzDataSetVersion);
+    method public static com.android.i18n.timezone.TzDataSetVersion readFromFile(java.io.File) throws java.io.IOException, com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
+    method public static com.android.i18n.timezone.TzDataSetVersion readTimeZoneModuleVersion() throws java.io.IOException, com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
+    method public byte[] toBytes();
+    field public static final String DEFAULT_FILE_NAME = "tz_version";
+  }
+
+  public static class TzDataSetVersion.TzDataSetException extends java.lang.Exception {
+    ctor public TzDataSetVersion.TzDataSetException(String);
+    ctor public TzDataSetVersion.TzDataSetException(String, Throwable);
+  }
+
+  public final class ZoneInfoData implements java.lang.Cloneable {
+    method public String getID();
+  }
+
+  public static class ZoneInfoData.WallTime {
+    ctor public ZoneInfoData.WallTime();
+    method public int getGmtOffset();
+    method public int getHour();
+    method public int getIsDst();
+    method public int getMinute();
+    method public int getMonth();
+    method public int getMonthDay();
+    method public int getSecond();
+    method public int getWeekDay();
+    method public int getYear();
+    method public int getYearDay();
+    method public void localtime(int, com.android.i18n.timezone.ZoneInfoData);
+    method public int mktime(com.android.i18n.timezone.ZoneInfoData);
+    method public void setGmtOffset(int);
+    method public void setHour(int);
+    method public void setIsDst(int);
+    method public void setMinute(int);
+    method public void setMonth(int);
+    method public void setMonthDay(int);
+    method public void setSecond(int);
+    method public void setWeekDay(int);
+    method public void setYear(int);
+    method public void setYearDay(int);
+  }
+
+  public final class ZoneInfoDb implements java.lang.AutoCloseable {
+    method public static com.android.i18n.timezone.ZoneInfoDb getInstance();
+    method public String getVersion();
+    method public boolean hasTimeZone(String);
+    method public static com.android.i18n.timezone.ZoneInfoDb loadTzData(String);
+    method public com.android.i18n.timezone.ZoneInfoData makeZoneInfoData(String);
+    method public void validate() throws java.io.IOException;
+  }
+
+}
+
 package com.android.icu.text {
 
   public class DateSorterBridge {
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/DebugInfo.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/DebugInfo.java
index 78baa71..a9516f1 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/DebugInfo.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/DebugInfo.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.util;
+package com.android.i18n.timezone;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CoreLibraryDebug.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/I18nModuleDebug.java
similarity index 90%
rename from android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CoreLibraryDebug.java
rename to android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/I18nModuleDebug.java
index 0d6ee46..1dd8fe2 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/CoreLibraryDebug.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/I18nModuleDebug.java
@@ -14,30 +14,27 @@
  * limitations under the License.
  */
 
-package libcore.util;
+package com.android.i18n.timezone;
 
+import com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
+import com.android.i18n.util.Log;
 import com.android.icu.util.Icu4cMetadata;
 
-import libcore.timezone.TimeZoneDataFiles;
-import libcore.timezone.TzDataSetVersion;
-import libcore.timezone.TzDataSetVersion.TzDataSetException;
-import libcore.timezone.ZoneInfoDb;
-
 import java.io.File;
 import java.io.IOException;
 
 /**
- * Provides APIs for obtaining metadata for the managed core library and lower-level
- * components like bionic and the runtime.
+ * Provides APIs for obtaining metadata for the i18n library and lower-level
+ * components like timezone.
  *
  * @hide
  */
 @libcore.api.CorePlatformApi
-public class CoreLibraryDebug {
+public class I18nModuleDebug {
 
     private static final String CORE_LIBRARY_TIMEZONE_DEBUG_PREFIX = "core_library.timezone.";
 
-    private CoreLibraryDebug() {}
+    private I18nModuleDebug() {}
 
     /**
      * Returns information about the Core Library for debugging.
@@ -95,7 +92,7 @@
                 debugInfo.addStringEntry(statusKey, "ERROR");
                 debugInfo.addStringEntry(debugKeyPrefix + "exception_class", e.getClass().getName());
                 debugInfo.addStringEntry(debugKeyPrefix + "exception_msg", e.getMessage());
-                System.logE("Error reading " + file, e);
+                Log.e("Error reading " + file, e);
             }
         } else {
             debugInfo.addStringEntry(statusKey, "NOT_FOUND");
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
index b4c8976..1506b46 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.timezone;
+package com.android.i18n.timezone;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -25,6 +25,7 @@
  * @hide
  */
 @libcore.api.CorePlatformApi
+@libcore.api.IntraCoreApi
 public final class TimeZoneDataFiles {
     private static final String ANDROID_ROOT_ENV = "ANDROID_ROOT";
     private static final String ANDROID_I18N_ROOT_ENV = "ANDROID_I18N_ROOT";
@@ -42,6 +43,7 @@
      * </ul>
      */
     // VisibleForTesting
+    @libcore.api.IntraCoreApi
     public static String[] getTimeZoneFilePaths(String fileName) {
         return new String[] {
                 getDataTimeZoneFile(fileName),
@@ -61,6 +63,7 @@
         return getDataTimeZoneRootDir() + "current/" + fileName;
     }
 
+    @libcore.api.IntraCoreApi
     public static String getTimeZoneModuleTzFile(String fileName) {
         return getTimeZoneModuleFile("tz/" + fileName);
     }
@@ -88,6 +91,7 @@
         return System.getenv(ANDROID_I18N_ROOT_ENV) + "/etc/" + fileName;
     }
 
+    @libcore.api.IntraCoreApi
     public static String getSystemTzFile(String fileName) {
         return getEnvironmentPath(ANDROID_ROOT_ENV, "/usr/share/zoneinfo/" + fileName);
     }
@@ -96,6 +100,7 @@
         return getEnvironmentPath(ANDROID_ROOT_ENV, "/usr/icu/" + fileName);
     }
 
+    @libcore.api.IntraCoreApi
     public static String generateIcuDataPath() {
         List<String> paths = new ArrayList<>(3);
 
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
index 1a1d156..89ee560 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.timezone;
+package com.android.i18n.timezone;
 
 import java.io.File;
 import java.io.FileInputStream;
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java
index 85bf1bc..5612195 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoData.java
@@ -20,19 +20,22 @@
  * This file is in the public domain, so clarified as of
  * 1996-06-05 by Arthur David Olson.
  */
-package libcore.util;
+package com.android.i18n.timezone;
 
-import android.compat.annotation.UnsupportedAppUsage;
-
+import com.android.i18n.timezone.internal.BufferIterator;
+import com.android.i18n.timezone.internal.ByteBufferIterator;
 import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.io.ObjectInputStream.GetField;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.TimeZone;
-import libcore.io.BufferIterator;
-import libcore.timezone.ZoneInfoDb;
+import libcore.api.IntraCoreApi;
 
 /**
  * Our concrete TimeZone implementation, backed by zoneinfo data.
@@ -47,19 +50,17 @@
  * reading the index and creating a {@link BufferIterator} that provides access to an entry for a
  * specific file. This class is responsible for reading the data from that {@link BufferIterator}
  * and storing it a representation to support the {@link TimeZone} and {@link GregorianCalendar}
- * implementations. See {@link ZoneInfo#readTimeZone(String, BufferIterator, long)}.
+ * implementations. See {@link ZoneInfoData#readTimeZone(String, BufferIterator, long)}.
  *
  * <p>This class does not use all the information from the {@code tzfile}; it uses:
  * {@code tzh_timecnt} and the associated transition times and type information. For each type
  * (described by {@code struct ttinfo}) it uses {@code tt_gmtoff} and {@code tt_isdst}.
  *
- * <p>This class should be in libcore.timezone but this class is Serializable so cannot
- * be moved there without breaking apps that have (for some reason) serialized TimeZone objects.
- *
- * @hide - used to implement TimeZone
+ * @hide
  */
+@libcore.api.IntraCoreApi
 @libcore.api.CorePlatformApi
-public final class ZoneInfo extends TimeZone {
+public final class ZoneInfoData implements Cloneable {
     private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
     private static final long MILLISECONDS_PER_400_YEARS =
             MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3);
@@ -74,8 +75,22 @@
         0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335,
     };
 
-    // Proclaim serialization compatibility with pre-OpenJDK AOSP
-    static final long serialVersionUID = -4598738130123921552L;
+    /**
+     * The serialized fields in {@link libcore.util.ZoneInfo} kept for backward app compatibility.
+     */
+    @IntraCoreApi
+    public static final ObjectStreamField[] ZONEINFO_SERIALIZED_FIELDS = new ObjectStreamField[] {
+        new ObjectStreamField("mRawOffset", int.class),
+        new ObjectStreamField("mEarliestRawOffset", int.class),
+        new ObjectStreamField("mUseDst", boolean.class),
+        new ObjectStreamField("mDstSavings", int.class),
+        new ObjectStreamField("mTransitions", long[].class),
+        new ObjectStreamField("mTypes", byte[].class),
+        new ObjectStreamField("mOffsets", int[].class),
+        new ObjectStreamField("mIsDsts", byte[].class),
+    };
+
+    private final String mId;
 
     /**
      * The (best guess) non-DST offset used "today". It is stored in milliseconds.
@@ -109,8 +124,9 @@
      * Implements {@link #getDSTSavings()}
      *
      * <p>This should be final but is not because it may need to be fixed up by
-     * {@link #readObject(ObjectInputStream)} to correct an inconsistency in the previous version
-     * of the code whereby this was set to a non-zero value even though DST was not actually used.
+     * {@link #createFromSerializationFields(String, GetField)} to correct an inconsistency in
+     * the previous version of the code whereby this was set to a non-zero value even though DST was
+     * not actually used.
      *
      * @see #mUseDst
      */
@@ -121,13 +137,13 @@
      * in the offset from UTC or a change in the DST.
      *
      * <p>These times are pre-calculated externally from a set of rules (both historical and
-     * future) and stored in a file from which {@link ZoneInfo#readTimeZone(String, BufferIterator,
+     * future) and stored in a file from which {@link ZoneInfoData#readTimeZone(String, BufferIterator,
      * long)} reads the data. That is quite different to {@link java.util.SimpleTimeZone}, which has
      * essentially human readable rules (e.g. DST starts at 01:00 on the first Sunday in March and
      * ends at 01:00 on the last Sunday in October) that can be used to determine the DST transition
      * times across a number of years
      *
-     * <p>In terms of {@link ZoneInfo tzfile} structure this array is of length {@code tzh_timecnt}
+     * <p>In terms of {@link ZoneInfoData tzfile} structure this array is of length {@code tzh_timecnt}
      * and contains the times in seconds converted to long to make them safer to use.
      *
      * <p>They are stored in order from earliest (lowest) time to latest (highest). A transition is
@@ -137,7 +153,6 @@
      *
      * @see #mTypes
      */
-    @UnsupportedAppUsage
     private final long[] mTransitions;
 
     /**
@@ -148,7 +163,7 @@
      * index. The type is an index into the arrays {@link #mOffsets} and {@link #mIsDsts} that each
      * contain one part of the pair.
      *
-     * <p>In the {@link ZoneInfo tzfile} structure the type array only contains unique instances of
+     * <p>In the {@link ZoneInfoData tzfile} structure the type array only contains unique instances of
      * the {@code struct ttinfo} to save space and each type may be referenced by multiple
      * transitions. However, the type pairs stored in this class are not guaranteed unique because
      * they do not include the {@code tt_abbrind}, which is the abbreviated identifier to use for
@@ -185,7 +200,39 @@
      */
     private final byte[] mIsDsts;
 
-    public static ZoneInfo readTimeZone(String id, BufferIterator it, long currentTimeMillis)
+    private ZoneInfoData(String id, int rawOffset, int earliestRawOffset, boolean useDst,
+            int dstSavings, long[] transitions, byte[] types, int[] offsets, byte[] isDsts) {
+        mId = id;
+        mRawOffset = rawOffset;
+        mEarliestRawOffset = earliestRawOffset;
+        mUseDst = useDst;
+        mDstSavings = dstSavings;
+        mTransitions = transitions;
+        mTypes = types;
+        mOffsets = offsets;
+        mIsDsts = isDsts;
+    }
+
+    /**
+     * Copy constructor
+     */
+    @IntraCoreApi
+    public ZoneInfoData(ZoneInfoData that) {
+        if (that == null) {
+            throw new NullPointerException("ZoneInfoData can't be null");
+        }
+        mId = that.mId;
+        mRawOffset = that.mRawOffset;
+        mDstSavings = that.mDstSavings;
+        mEarliestRawOffset = that.mEarliestRawOffset;
+        mUseDst = that.mUseDst;
+        mTransitions = that.mTransitions == null ? null : that.mTransitions.clone();
+        mTypes = that.mTypes == null ? null : that.mTypes.clone();
+        mOffsets = that.mOffsets == null ? null : that.mOffsets.clone();
+        mIsDsts = that.mIsDsts == null ? null : that.mIsDsts.clone();
+
+    }
+    public static ZoneInfoData readTimeZone(String id, BufferIterator it, long currentTimeMillis)
             throws IOException {
 
         // Skip over the superseded 32-bit header and data.
@@ -251,7 +298,7 @@
      * Read the 64-bit header and data for {@code id} from the current position of {@code it} and
      * return a ZoneInfo.
      */
-    private static ZoneInfo read64BitData(String id, BufferIterator it, long currentTimeMillis)
+    private static ZoneInfoData read64BitData(String id, BufferIterator it, long currentTimeMillis)
             throws IOException {
         // Variable names beginning tzh_ correspond to those in "tzfile.h".
 
@@ -330,7 +377,7 @@
             // for any locale. (The RI doesn't do any better than us here either.)
             it.skip(1);
         }
-        return new ZoneInfo(id, transitions64, types, gmtOffsets, isDsts, currentTimeMillis);
+        return new ZoneInfoData(id, transitions64, types, gmtOffsets, isDsts, currentTimeMillis);
     }
 
     private static void checkTzifVersionAcceptable(String id, byte tzh_version) throws IOException {
@@ -345,7 +392,7 @@
         }
     }
 
-    private ZoneInfo(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts,
+    private ZoneInfoData(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts,
             long currentTimeMillis) {
         if (gmtOffsets.length == 0) {
             throw new IllegalArgumentException("ZoneInfo requires at least one offset "
@@ -354,7 +401,7 @@
         mTransitions = transitions;
         mTypes = types;
         mIsDsts = isDsts;
-        setID(name);
+        mId = name;
 
         // Find the latest daylight and standard offsets (if any).
         int lastStdTransitionIndex = -1;
@@ -456,17 +503,49 @@
     }
 
     /**
-     * Ensure that when deserializing an instance that {@link #mDstSavings} is always 0 when
-     * {@link #mUseDst} is false.
+     * Create an instance from the serialized fields from {@link libcore.util.ZoneInfo}
+     * for backward app compatibility.
      */
-    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-        in.defaultReadObject();
-        if (!mUseDst && mDstSavings != 0) {
-            mDstSavings = 0;
+    @libcore.api.IntraCoreApi
+    public static ZoneInfoData createFromSerializationFields(String id,
+            ObjectInputStream.GetField getField) throws IOException {
+        int rawOffset = getField.get("mRawOffset", 0);
+        int earliestRawOffset = getField.get("mEarliestRawOffset", 0);
+        boolean useDst = getField.get("mUseDst", false);
+        int dstSavings = getField.get("mDstSavings", 0);
+        long[] transitions = (long[]) getField.get("mTransitions", null);
+        byte[] types = (byte[]) getField.get("mTypes", null);
+        int[] offsets = (int[]) getField.get("mOffsets", null);
+        byte[] isDsts = (byte[]) getField.get("mIsDsts", null);
+        /** For pre-OpenJDK compatibility, ensure that when deserializing an instance that
+         * {@link #mDstSavings} is always 0 when {@link #mUseDst} is false
+         */
+        if (!useDst && dstSavings != 0) {
+            dstSavings = 0;
         }
+
+        return new ZoneInfoData(
+                id, rawOffset, earliestRawOffset,
+                useDst, dstSavings, transitions, types,
+            offsets, isDsts);
     }
 
-    @Override
+    /**
+     * Serialize {@link libcore.util.ZoneInfo} into backward app compatible form.
+     */
+    @libcore.api.IntraCoreApi
+    public void writeToSerializationFields(ObjectOutputStream.PutField putField) {
+        putField.put("mRawOffset", mRawOffset);
+        putField.put("mEarliestRawOffset", mEarliestRawOffset);
+        putField.put("mUseDst", mUseDst);
+        putField.put("mDstSavings", mDstSavings);
+        putField.put("mTransitions", mTransitions);
+        putField.put("mTypes", mTypes);
+        putField.put("mOffsets", mOffsets);
+        putField.put("mIsDsts", mIsDsts);
+    }
+
+    @libcore.api.IntraCoreApi
     public int getOffset(int era, int year, int month, int day, int dayOfWeek, int millis) {
         // XXX This assumes Gregorian always; Calendar switches from
         // Julian to Gregorian in 1582.  What calendar system are the
@@ -619,6 +698,7 @@
      * @param offsets the array whose length must be greater than or equal to 2.
      * @return the total offset which is the sum of the raw and DST offsets.
      */
+    @libcore.api.IntraCoreApi
     public int getOffsetsByUtcTime(long utcTimeInMillis, int[] offsets) {
         int transitionIndex = findTransitionIndex(roundDownMillisToSeconds(utcTimeInMillis));
         int totalOffset;
@@ -666,7 +746,7 @@
         return totalOffset;
     }
 
-    @Override
+    @libcore.api.IntraCoreApi
     public int getOffset(long when) {
         int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);
         if (offsetIndex == -1) {
@@ -678,7 +758,8 @@
         return mRawOffset + mOffsets[offsetIndex] * 1000;
     }
 
-    @Override public boolean inDaylightTime(Date time) {
+    @libcore.api.IntraCoreApi
+    public boolean inDaylightTime(Date time) {
         long when = time.getTime();
         int offsetIndex = findOffsetIndexForTimeInMilliseconds(when);
         if (offsetIndex == -1) {
@@ -691,27 +772,28 @@
         return mIsDsts[offsetIndex] == 1;
     }
 
-    @Override public int getRawOffset() {
+    @libcore.api.IntraCoreApi
+    public int getRawOffset() {
         return mRawOffset;
     }
 
-    @Override public void setRawOffset(int off) {
+    @libcore.api.IntraCoreApi
+    public void setRawOffset(int off) {
         mRawOffset = off;
     }
 
-    @Override public int getDSTSavings() {
+    @libcore.api.IntraCoreApi
+    public int getDSTSavings() {
         return mDstSavings;
     }
 
-    @Override public boolean useDaylightTime() {
+    @libcore.api.IntraCoreApi
+    public boolean useDaylightTime() {
         return mUseDst;
     }
 
-    @Override public boolean hasSameRules(TimeZone timeZone) {
-        if (!(timeZone instanceof ZoneInfo)) {
-            return false;
-        }
-        ZoneInfo other = (ZoneInfo) timeZone;
+    @libcore.api.IntraCoreApi
+    public boolean hasSameRules(ZoneInfoData other) {
         if (mUseDst != other.mUseDst) {
             return false;
         }
@@ -727,10 +809,10 @@
     }
 
     @Override public boolean equals(Object obj) {
-        if (!(obj instanceof ZoneInfo)) {
+        if (!(obj instanceof ZoneInfoData)) {
             return false;
         }
-        ZoneInfo other = (ZoneInfo) obj;
+        ZoneInfoData other = (ZoneInfoData) obj;
         return getID().equals(other.getID()) && hasSameRules(other);
     }
 
@@ -750,7 +832,7 @@
 
     @Override
     public String toString() {
-        return getClass().getName() + "[id=\"" + getID() + "\"" +
+        return "[id=\"" + getID() + "\"" +
             ",mRawOffset=" + mRawOffset +
             ",mEarliestRawOffset=" + mEarliestRawOffset +
             ",mUseDst=" + mUseDst +
@@ -759,13 +841,15 @@
             "]";
     }
 
-    @Override
-    public Object clone() {
-        // Overridden for documentation. The default clone() behavior is exactly what we want.
-        // Though mutable, the arrays of offset data are treated as immutable. Only ID and
-        // mRawOffset are mutable in this class, and those are an immutable object and a primitive
-        // respectively.
-        return super.clone();
+    @libcore.api.CorePlatformApi
+    @libcore.api.IntraCoreApi
+    public String getID() {
+        return mId;
+    }
+
+    @libcore.api.IntraCoreApi
+    public long[] getTransitionsForAppCompat() {
+        return mTransitions;
     }
 
     /**
@@ -826,7 +910,7 @@
          * is only one offset rule acting at any given instant. We do not consider leap seconds.
          */
         @libcore.api.CorePlatformApi
-        public void localtime(int timeSeconds, ZoneInfo zoneInfo) {
+        public void localtime(int timeSeconds, ZoneInfoData zoneInfo) {
             try {
                 int offsetSeconds = zoneInfo.mRawOffset / 1000;
 
@@ -887,7 +971,7 @@
          * occur for other reasons such as when a zone changes its raw offset.
          */
         @libcore.api.CorePlatformApi
-        public int mktime(ZoneInfo zoneInfo) {
+        public int mktime(ZoneInfoData zoneInfo) {
             // Normalize isDst to -1, 0 or 1 to simplify isDst equality checks below.
             this.isDst = this.isDst > 0 ? this.isDst = 1 : this.isDst < 0 ? this.isDst = -1 : 0;
 
@@ -978,7 +1062,7 @@
          * {@code targetInterval}, apply it, and see if we are still in {@code targetInterval}. If
          * we are, then we have found an adjustment.
          */
-        private Integer tryOffsetAdjustments(ZoneInfo zoneInfo, int oldWallTimeSeconds,
+        private Integer tryOffsetAdjustments(ZoneInfoData zoneInfo, int oldWallTimeSeconds,
                 OffsetInterval targetInterval, int transitionIndex, int isDstToFind)
                 throws CheckedArithmeticException {
 
@@ -1011,7 +1095,7 @@
          * The {@code startIndex} is used as a starting point so transitions nearest
          * to that index are returned first.
          */
-        private static int[] getOffsetsOfType(ZoneInfo zoneInfo, int startIndex, int isDst) {
+        private static int[] getOffsetsOfType(ZoneInfoData zoneInfo, int startIndex, int isDst) {
             // +1 to account for the synthetic transition we invent before the first recorded one.
             int[] offsets = new int[zoneInfo.mOffsets.length + 1];
             boolean[] seen = new boolean[zoneInfo.mOffsets.length];
@@ -1075,7 +1159,7 @@
          * in seconds if a match has been found and modifies fields, or it returns {@code null} and
          * leaves the field state unmodified.
          */
-        private Integer doWallTimeSearch(ZoneInfo zoneInfo, int initialTransitionIndex,
+        private Integer doWallTimeSearch(ZoneInfoData zoneInfo, int initialTransitionIndex,
                 int wallTimeSeconds, boolean mustMatchDst) throws CheckedArithmeticException {
 
             // The loop below starts at the initialTransitionIndex and radiates out from that point
@@ -1351,7 +1435,7 @@
          *   </li>
          * </ol>
          */
-        public static OffsetInterval create(ZoneInfo timeZone, int transitionIndex) {
+        public static OffsetInterval create(ZoneInfoData timeZone, int transitionIndex) {
             if (transitionIndex < -1 || transitionIndex >= timeZone.mTransitions.length) {
                 return null;
             }
@@ -1468,4 +1552,16 @@
         }
         return (int) result;
     }
+
+    /**
+     * IntraCoreApi made visible for testing in libcore
+     */
+    @libcore.api.IntraCoreApi
+    public static ZoneInfoData createZoneInfo(String name, long timeInMilli, ByteBuffer buf)
+        throws IOException {
+        ByteBufferIterator bufferIterator = new ByteBufferIterator(buf);
+        return ZoneInfoData.readTimeZone(
+            "TimeZone for '" + name + "'", bufferIterator, timeInMilli);
+    }
+
 }
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoDb.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoDb.java
index c2a028d..30eee3a 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoDb.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/ZoneInfoDb.java
@@ -14,20 +14,19 @@
  * limitations under the License.
  */
 
-package libcore.timezone;
+package com.android.i18n.timezone;
 
 import android.system.ErrnoException;
-import dalvik.annotation.optimization.ReachabilitySensitive;
+import com.android.i18n.timezone.internal.BasicLruCache;
+import com.android.i18n.timezone.internal.BufferIterator;
+import com.android.i18n.timezone.internal.MemoryMappedFile;
 
+import dalvik.annotation.optimization.ReachabilitySensitive;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import libcore.io.BufferIterator;
-import libcore.io.MemoryMappedFile;
-import libcore.util.BasicLruCache;
-import libcore.util.ZoneInfo;
 
 /**
  * A class used to initialize the time zone database. This implementation uses the
@@ -38,6 +37,7 @@
  * @hide - used to implement TimeZone
  */
 @libcore.api.CorePlatformApi
+@libcore.api.IntraCoreApi
 public final class ZoneInfoDb implements AutoCloseable {
 
   // VisibleForTesting
@@ -91,12 +91,12 @@
    * See http://b/8270865 for context.
    */
   private final static int CACHE_SIZE = 1;
-  private final BasicLruCache<String, ZoneInfo> cache =
-      new BasicLruCache<String, ZoneInfo>(CACHE_SIZE) {
+  private final BasicLruCache<String, ZoneInfoData> cache =
+          new BasicLruCache<String, ZoneInfoData>(CACHE_SIZE) {
     @Override
-    protected ZoneInfo create(String id) {
+    protected ZoneInfoData create(String id) {
       try {
-        return makeTimeZoneUncached(id);
+        return makeZoneInfoDataUncached(id);
       } catch (IOException e) {
         throw new IllegalStateException("Unable to load timezone for ID=" + id, e);
       }
@@ -107,6 +107,7 @@
    * Obtains the singleton instance.
    */
   @libcore.api.CorePlatformApi
+  @libcore.api.IntraCoreApi
   public static ZoneInfoDb getInstance() {
     return DATA;
   }
@@ -127,7 +128,7 @@
     // We didn't find any usable tzdata on disk, so let's just hard-code knowledge of "GMT".
     // This is actually implemented in TimeZone itself, so if this is the only time zone
     // we report, we won't be asked any more questions.
-    System.logE("Couldn't find any " + TZDATA_FILE_NAME + " file!");
+    // !! System.logE("Couldn't find any " + TZDATA_FILE_NAME + " file!");
     return ZoneInfoDb.createFallback();
   }
 
@@ -197,7 +198,7 @@
 
       // Something's wrong with the file.
       // Log the problem and return false so we try the next choice.
-      System.logE(TZDATA_FILE_NAME + " file \"" + path + "\" was present but invalid!", ex);
+      // !! System.logE(TZDATA_FILE_NAME + " file \"" + path + "\" was present but invalid!", ex);
       return false;
     }
   }
@@ -303,27 +304,30 @@
     checkNotClosed();
     // Validate the data in the tzdata file by loading each and every zone.
     for (String id : getAvailableIDs()) {
-      ZoneInfo zoneInfo = makeTimeZoneUncached(id);
-      if (zoneInfo == null) {
+      ZoneInfoData zoneInfoData = makeZoneInfoDataUncached(id);
+      if (zoneInfoData == null) {
         throw new IOException("Unable to find data for ID=" + id);
       }
     }
   }
 
-  ZoneInfo makeTimeZoneUncached(String id) throws IOException {
+  @libcore.api.IntraCoreApi
+  ZoneInfoData makeZoneInfoDataUncached(String id) throws IOException {
     BufferIterator it = getBufferIterator(id);
     if (it == null) {
       return null;
     }
 
-    return ZoneInfo.readTimeZone(id, it, System.currentTimeMillis());
+    return ZoneInfoData.readTimeZone(id, it, System.currentTimeMillis());
   }
 
+  @libcore.api.IntraCoreApi
   public String[] getAvailableIDs() {
     checkNotClosed();
     return ids.clone();
   }
 
+  @libcore.api.IntraCoreApi
   public String[] getAvailableIDs(int rawUtcOffset) {
     checkNotClosed();
     List<String> matches = new ArrayList<String>();
@@ -356,6 +360,7 @@
    * Returns the tzdb version in use.
    */
   @libcore.api.CorePlatformApi
+  @libcore.api.IntraCoreApi
   public String getVersion() {
     checkNotClosed();
     return version;
@@ -367,11 +372,12 @@
   }
 
   @libcore.api.CorePlatformApi
-  public ZoneInfo makeTimeZone(String id) {
+  @libcore.api.IntraCoreApi
+  public ZoneInfoData makeZoneInfoData(String id) {
     checkNotClosed();
-    ZoneInfo zoneInfo = cache.get(id);
+    ZoneInfoData zoneInfoData = cache.get(id);
     // The object from the cache is cloned because TimeZone / ZoneInfo are mutable.
-    return zoneInfo == null ? null : (ZoneInfo) zoneInfo.clone();
+    return zoneInfoData == null ? null : new ZoneInfoData(zoneInfoData);
   }
 
   @libcore.api.CorePlatformApi
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BasicLruCache.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BasicLruCache.java
index e3ddc64..a6d9a48 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BasicLruCache.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BasicLruCache.java
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package libcore.util;
+package com.android.i18n.timezone.internal;
 
-import android.compat.annotation.UnsupportedAppUsage;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -26,17 +25,28 @@
  * @hide
  */
 public class BasicLruCache<K, V> {
-    @UnsupportedAppUsage
-    private final LinkedHashMap<K, V> map;
-    private final int maxSize;
+    private static class CacheMap<K, V> extends LinkedHashMap<K, V> {
 
-    @UnsupportedAppUsage
+        private final int maxSize;
+
+        private CacheMap(int maxSize) {
+            super(0, 0.75f, true);
+            this.maxSize = maxSize;
+        }
+
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+            return this.size() > maxSize;
+        }
+    }
+
+    private final CacheMap<K, V> map;
+
     public BasicLruCache(int maxSize) {
         if (maxSize <= 0) {
             throw new IllegalArgumentException("maxSize <= 0");
         }
-        this.maxSize = maxSize;
-        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+        this.map = new CacheMap<K, V>(maxSize);
     }
 
     /**
@@ -45,7 +55,6 @@
      * head of the queue. This returns null if a value is not cached and cannot
      * be created.
      */
-    @UnsupportedAppUsage
     public final V get(K key) {
         if (key == null) {
             throw new NullPointerException("key == null");
@@ -70,7 +79,6 @@
             // isn't design for such usage anyway).
             if (result != null) {
                 map.put(key, result);
-                trimToSize(maxSize);
             }
         }
 
@@ -82,9 +90,8 @@
      * the queue.
      *
      * @return the previous value mapped by {@code key}. Although that entry is
-     *     no longer cached, it has not been passed to {@link #entryEvicted}.
+     *     no longer cached, it has not been evicted.
      */
-    @UnsupportedAppUsage
     public synchronized final V put(K key, V value) {
         if (key == null) {
             throw new NullPointerException("key == null");
@@ -93,28 +100,9 @@
         }
 
         V previous = map.put(key, value);
-        trimToSize(maxSize);
         return previous;
     }
 
-    private void trimToSize(int maxSize) {
-        while (map.size() > maxSize) {
-            Map.Entry<K, V> toEvict = map.eldest();
-
-            K key = toEvict.getKey();
-            V value = toEvict.getValue();
-            map.remove(key);
-
-            entryEvicted(key, value);
-        }
-    }
-
-    /**
-     * Called for entries that have reached the tail of the least recently used
-     * queue and are be removed. The default implementation does nothing.
-     */
-    protected void entryEvicted(K key, V value) {}
-
     /**
      * Called after a cache miss to compute a value for the corresponding key.
      * Returns the computed value or null if no value can be computed. The
@@ -125,18 +113,9 @@
     }
 
     /**
-     * Returns a copy of the current contents of the cache, ordered from least
-     * recently accessed to most recently accessed.
-     */
-    public synchronized final Map<K, V> snapshot() {
-        return new LinkedHashMap<K, V>(map);
-    }
-
-    /**
      * Clear the cache, calling {@link #entryEvicted} on each removed entry.
      */
-    @UnsupportedAppUsage
     public synchronized final void evictAll() {
-        trimToSize(0);
+        map.clear();
     }
 }
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BufferIterator.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BufferIterator.java
index fb97250..c12d3df 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BufferIterator.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/BufferIterator.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.io;
-
-import android.compat.annotation.UnsupportedAppUsage;
+package com.android.i18n.timezone.internal;
 
 /**
  * Iterates over big- or little-endian bytes. See {@link MemoryMappedFile#bigEndianIterator} and
@@ -29,13 +27,11 @@
      * Seeks to the absolute position {@code offset}, measured in bytes from the start of the
      * buffer.
      */
-    @UnsupportedAppUsage
     public abstract void seek(int offset);
 
     /**
      * Skips forwards or backwards {@code byteCount} bytes from the current position.
      */
-    @UnsupportedAppUsage
     public abstract void skip(int byteCount);
 
     /**
@@ -49,7 +45,6 @@
      *
      * @throws IndexOutOfBoundsException if the read / write would be outside of the buffer / array
      */
-    @UnsupportedAppUsage
     public abstract void readByteArray(byte[] bytes, int arrayOffset, int byteCount);
 
     /**
@@ -57,7 +52,6 @@
      *
      * @throws IndexOutOfBoundsException if the read would be outside of the buffer
      */
-    @UnsupportedAppUsage
     public abstract byte readByte();
 
     /**
@@ -65,7 +59,6 @@
      *
      * @throws IndexOutOfBoundsException if the read would be outside of the buffer
      */
-    @UnsupportedAppUsage
     public abstract int readInt();
 
     /**
@@ -74,7 +67,6 @@
      *
      * @throws IndexOutOfBoundsException if the read / write would be outside of the buffer / array
      */
-    @UnsupportedAppUsage
     public abstract void readIntArray(int[] ints, int arrayOffset, int intCount);
 
     /**
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/ByteBufferIterator.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/ByteBufferIterator.java
new file mode 100644
index 0000000..e3cf9e4
--- /dev/null
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/ByteBufferIterator.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.i18n.timezone.internal;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link BufferIterator} that wraps a {@link ByteBuffer}.
+ */
+public class ByteBufferIterator extends BufferIterator {
+
+    private final ByteBuffer buffer;
+
+    public ByteBufferIterator(ByteBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+    @Override
+    public void seek(int offset) {
+        buffer.position(offset);
+    }
+
+    @Override
+    public void skip(int byteCount) {
+        buffer.position(buffer.position() + byteCount);
+    }
+
+    @Override
+    public int pos() {
+        return buffer.position();
+    }
+
+    @Override
+    public void readByteArray(byte[] bytes, int arrayOffset, int byteCount) {
+        buffer.get(bytes, arrayOffset, byteCount);
+    }
+
+    @Override
+    public byte readByte() {
+        return buffer.get();
+    }
+
+    @Override
+    public int readInt() {
+        int value = buffer.asIntBuffer().get();
+        // Using a separate view does not update the position of this buffer so do it
+        // explicitly.
+        skip(Integer.BYTES);
+        return value;
+    }
+
+    @Override
+    public void readIntArray(int[] ints, int arrayOffset, int intCount) {
+        buffer.asIntBuffer().get(ints, arrayOffset, intCount);
+        // Using a separate view does not update the position of this buffer so do it
+        // explicitly.
+        skip(Integer.BYTES * intCount);
+    }
+
+    @Override
+    public void readLongArray(long[] longs, int arrayOffset, int longCount) {
+        buffer.asLongBuffer().get(longs, arrayOffset, longCount);
+        // Using a separate view does not update the position of this buffer so do it
+        // explicitly.
+        skip(Long.BYTES * longCount);
+    }
+
+    @Override
+    public short readShort() {
+        short value = buffer.asShortBuffer().get();
+        // Using a separate view does not update the position of this buffer so do it
+        // explicitly.
+        skip(Short.BYTES);
+        return value;
+    }
+}
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java
index 9e7f4ea..846044a 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/Memory.java
@@ -15,38 +15,19 @@
  *  limitations under the License.
  */
 
-package libcore.io;
-
-import android.compat.annotation.UnsupportedAppUsage;
-
-import java.nio.ByteOrder;
+package com.android.i18n.timezone.internal;
 
 import dalvik.annotation.optimization.FastNative;
+import java.nio.ByteOrder;
 
 /**
  * Unsafe access to memory.
  *
  * @hide
  */
-@libcore.api.CorePlatformApi
 public final class Memory {
     private Memory() { }
 
-    /**
-     * Used to optimize nio heap buffer bulk get operations. 'dst' must be a primitive array.
-     * 'dstOffset' is measured in units of 'sizeofElements' bytes.
-     */
-    public static native void unsafeBulkGet(Object dst, int dstOffset, int byteCount,
-            byte[] src, int srcOffset, int sizeofElements, boolean swap);
-
-    /**
-     * Used to optimize nio heap buffer bulk put operations. 'src' must be a primitive array.
-     * 'srcOffset' is measured in units of 'sizeofElements' bytes.
-     */
-    public static native void unsafeBulkPut(byte[] dst, int dstOffset, int byteCount,
-            Object src, int srcOffset, int sizeofElements, boolean swap);
-
-    @libcore.api.CorePlatformApi
     public static int peekInt(byte[] src, int offset, ByteOrder order) {
         if (order == ByteOrder.BIG_ENDIAN) {
             return (((src[offset++] & 0xff) << 24) |
@@ -85,7 +66,6 @@
         }
     }
 
-    @libcore.api.CorePlatformApi
     public static short peekShort(byte[] src, int offset, ByteOrder order) {
         if (order == ByteOrder.BIG_ENDIAN) {
             return (short) ((src[offset] << 8) | (src[offset + 1] & 0xff));
@@ -94,7 +74,7 @@
         }
     }
 
-    @libcore.api.CorePlatformApi
+
     public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) {
         if (order == ByteOrder.BIG_ENDIAN) {
             dst[offset++] = (byte) ((value >> 24) & 0xff);
@@ -108,8 +88,6 @@
             dst[offset  ] = (byte) ((value >> 24) & 0xff);
         }
     }
-
-    @libcore.api.CorePlatformApi
     public static void pokeLong(byte[] dst, int offset, long value, ByteOrder order) {
         if (order == ByteOrder.BIG_ENDIAN) {
             int i = (int) (value >> 32);
@@ -135,8 +113,6 @@
             dst[offset  ] = (byte) ((i >> 24) & 0xff);
         }
     }
-
-    @libcore.api.CorePlatformApi
     public static void pokeShort(byte[] dst, int offset, short value, ByteOrder order) {
         if (order == ByteOrder.BIG_ENDIAN) {
             dst[offset++] = (byte) ((value >> 8) & 0xff);
@@ -147,24 +123,8 @@
         }
     }
 
-    /**
-     * Copies 'byteCount' bytes from the source to the destination. The objects are either
-     * instances of DirectByteBuffer or byte[]. The offsets in the byte[] case must include
-     * the Buffer.arrayOffset if the array came from a Buffer.array call. We could make this
-     * private and provide the four type-safe variants, but then ByteBuffer.put(ByteBuffer)
-     * would need to work out which to call based on whether the source and destination buffers
-     * are direct or not.
-     *
-     * @hide make type-safe before making public?
-     */
-    @libcore.api.CorePlatformApi
-    public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount);
-
-    @UnsupportedAppUsage
     @FastNative
     public static native byte peekByte(long address);
-
-    @UnsupportedAppUsage
     public static int peekInt(long address, boolean swap) {
         int result = peekIntNative(address);
         if (swap) {
@@ -174,8 +134,6 @@
     }
     @FastNative
     private static native int peekIntNative(long address);
-
-    @UnsupportedAppUsage
     public static long peekLong(long address, boolean swap) {
         long result = peekLongNative(address);
         if (swap) {
@@ -195,8 +153,6 @@
     }
     @FastNative
     private static native short peekShortNative(long address);
-
-    @UnsupportedAppUsage
     public static native void peekByteArray(long address, byte[] dst, int dstOffset, int byteCount);
     public static native void peekCharArray(long address, char[] dst, int dstOffset, int charCount, boolean swap);
     public static native void peekDoubleArray(long address, double[] dst, int dstOffset, int doubleCount, boolean swap);
@@ -204,12 +160,8 @@
     public static native void peekIntArray(long address, int[] dst, int dstOffset, int intCount, boolean swap);
     public static native void peekLongArray(long address, long[] dst, int dstOffset, int longCount, boolean swap);
     public static native void peekShortArray(long address, short[] dst, int dstOffset, int shortCount, boolean swap);
-
-    @UnsupportedAppUsage
     @FastNative
     public static native void pokeByte(long address, byte value);
-
-    @UnsupportedAppUsage
     public static void pokeInt(long address, int value, boolean swap) {
         if (swap) {
             value = Integer.reverseBytes(value);
@@ -218,8 +170,6 @@
     }
     @FastNative
     private static native void pokeIntNative(long address, int value);
-
-    @UnsupportedAppUsage
     public static void pokeLong(long address, long value, boolean swap) {
         if (swap) {
             value = Long.reverseBytes(value);
@@ -237,8 +187,6 @@
     }
     @FastNative
     private static native void pokeShortNative(long address, short value);
-
-    @UnsupportedAppUsage
     public static native void pokeByteArray(long address, byte[] src, int offset, int count);
     public static native void pokeCharArray(long address, char[] src, int offset, int count, boolean swap);
     public static native void pokeDoubleArray(long address, double[] src, int offset, int count, boolean swap);
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/MemoryMappedFile.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/MemoryMappedFile.java
index 5e003ea..d2c2811 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/MemoryMappedFile.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/MemoryMappedFile.java
@@ -14,22 +14,19 @@
  * limitations under the License.
  */
 
-package libcore.io;
+package com.android.i18n.timezone.internal;
 
-import android.compat.annotation.UnsupportedAppUsage;
 import android.system.ErrnoException;
-
+import android.system.Os;
 import java.io.FileDescriptor;
 import java.nio.ByteOrder;
 
-import static android.system.OsConstants.MAP_SHARED;
-import static android.system.OsConstants.O_RDONLY;
-import static android.system.OsConstants.PROT_READ;
-
 /**
  * A memory-mapped file. Use {@link #mmapRO} to map a file, {@link #close} to unmap a file,
  * and either {@link #bigEndianIterator} or {@link #littleEndianIterator} to get a seekable
  * {@link BufferIterator} over the mapped data. This class is not thread safe.
+ *
+ * @hide
  */
 public final class MemoryMappedFile implements AutoCloseable {
     private boolean closed;
@@ -49,15 +46,14 @@
     /**
      * Use this to mmap the whole file read-only.
      */
-    @UnsupportedAppUsage
     public static MemoryMappedFile mmapRO(String path) throws ErrnoException {
-        FileDescriptor fd = Libcore.os.open(path, O_RDONLY, 0);
+        FileDescriptor fd = Os.open(path, 0 /* O_RDONLY */, 0);
         try {
-            long size = Libcore.os.fstat(fd).st_size;
-            long address = Libcore.os.mmap(0L, size, PROT_READ, MAP_SHARED, fd, 0);
+            long size = Os.fstat(fd).st_size;
+            long address = Os.mmap(0L, size, 0x1 /* PROT_READ */, 0x01 /* MAP_SHARED */, fd, 0);
             return new MemoryMappedFile(address, size);
         } finally {
-            Libcore.os.close(fd);
+            Os.close(fd);
         }
     }
 
@@ -72,7 +68,7 @@
     public void close() throws ErrnoException {
         if (!closed) {
             closed = true;
-            Libcore.os.munmap(address, size);
+            Os.munmap(address, size);
         }
     }
 
@@ -83,7 +79,6 @@
     /**
      * Returns a new iterator that treats the mapped data as big-endian.
      */
-    @UnsupportedAppUsage
     public BufferIterator bigEndianIterator() {
         return new NioBufferIterator(
                 this, address, size, ByteOrder.nativeOrder() != ByteOrder.BIG_ENDIAN);
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/NioBufferIterator.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/NioBufferIterator.java
index 1554519..83c0ec6 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/NioBufferIterator.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/internal/NioBufferIterator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package libcore.io;
+package com.android.i18n.timezone.internal;
 
 /**
  * Iterates over big- or little-endian bytes on the native heap.
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/util/Log.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/util/Log.java
new file mode 100644
index 0000000..6d960b9
--- /dev/null
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/util/Log.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 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 com.android.i18n.util;
+
+/**
+ * Log utility class only used by i18n module.
+ *
+ * @hide
+ */
+public class Log {
+    // liblog's log priorities from android_LogPriority in log.h.
+    private static int VERBOSE = 2;
+    private static int DEBUG = 3;
+    private static int INFO = 4;
+    private static int WARN = 5;
+    private static int ERROR = 6;
+    private static int FATAL = 7;
+
+    public static void e(String msg, Throwable e) {
+        log(ERROR, msg, e);
+    }
+
+    private static native void log(int priority, String msg, Throwable th);
+}
diff --git a/android_icu4j/libcore_bridge/src/native/Register.cpp b/android_icu4j/libcore_bridge/src/native/Register.cpp
index 7997077..4313345 100644
--- a/android_icu4j/libcore_bridge/src/native/Register.cpp
+++ b/android_icu4j/libcore_bridge/src/native/Register.cpp
@@ -35,6 +35,8 @@
 
 #define REGISTER(FN) extern void FN(JNIEnv*); FN(env)
     REGISTER(register_com_android_icu_text_TimeZoneNamesNative);
+    REGISTER(register_com_android_i18n_timezone_internal_Memory);
+    REGISTER(register_com_android_i18n_util_Log);
     REGISTER(register_com_android_icu_util_CaseMapperNative);
     REGISTER(register_com_android_icu_util_Icu4cMetadata);
     REGISTER(register_com_android_icu_util_LocaleNative);
diff --git a/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp b/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp
index a5b7b72..d38bcfb 100644
--- a/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp
+++ b/android_icu4j/libcore_bridge/src/native/com_android_i18n_timezone_internal_Memory.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "Memory"
 
+#include <byteswap.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
@@ -27,7 +28,6 @@
 #include <nativehelper/jni_macros.h>
 
 #include "JniConstants.h"
-#include "Portability.h"
 
 // Use packed structures for access to unaligned data on targets with alignment restrictions.
 // The compiler will generate appropriate code to access these structures without
@@ -92,18 +92,6 @@
     }
 }
 
-static void Memory_memmove(JNIEnv* env, jclass, jobject dstObject, jint dstOffset, jobject srcObject, jint srcOffset, jlong length) {
-    ScopedBytesRW dstBytes(env, dstObject);
-    if (dstBytes.get() == NULL) {
-        return;
-    }
-    ScopedBytesRO srcBytes(env, srcObject);
-    if (srcBytes.get() == NULL) {
-        return;
-    }
-    memmove(dstBytes.get() + dstOffset, srcBytes.get() + srcOffset, length);
-}
-
 static jbyte Memory_peekByte(JNIEnv*, jclass, jlong srcAddress) {
     return *cast<const jbyte*>(srcAddress);
 }
@@ -233,64 +221,7 @@
     put_unaligned<jlong>(cast<jlong*>(dstAddress), value);
 }
 
-static void unsafeBulkCopy(jbyte* dst, const jbyte* src, jint byteCount,
-        jint sizeofElement, jboolean swap) {
-    if (!swap) {
-        memcpy(dst, src, byteCount);
-        return;
-    }
-
-    if (sizeofElement == 2) {
-        jshort* dstShorts = reinterpret_cast<jshort*>(dst);
-        const jshort* srcShorts = reinterpret_cast<const jshort*>(src);
-        swapShorts(dstShorts, srcShorts, byteCount / 2);
-    } else if (sizeofElement == 4) {
-        jint* dstInts = reinterpret_cast<jint*>(dst);
-        const jint* srcInts = reinterpret_cast<const jint*>(src);
-        swapInts(dstInts, srcInts, byteCount / 4);
-    } else if (sizeofElement == 8) {
-        jlong* dstLongs = reinterpret_cast<jlong*>(dst);
-        const jlong* srcLongs = reinterpret_cast<const jlong*>(src);
-        swapLongs(dstLongs, srcLongs, byteCount / 8);
-    }
-}
-
-static void Memory_unsafeBulkGet(JNIEnv* env, jclass, jobject dstObject, jint dstOffset,
-        jint byteCount, jbyteArray srcArray, jint srcOffset, jint sizeofElement, jboolean swap) {
-    ScopedByteArrayRO srcBytes(env, srcArray);
-    if (srcBytes.get() == NULL) {
-        return;
-    }
-    jarray dstArray = reinterpret_cast<jarray>(dstObject);
-    jbyte* dstBytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(dstArray, NULL));
-    if (dstBytes == NULL) {
-        return;
-    }
-    jbyte* dst = dstBytes + dstOffset*sizeofElement;
-    const jbyte* src = srcBytes.get() + srcOffset;
-    unsafeBulkCopy(dst, src, byteCount, sizeofElement, swap);
-    env->ReleasePrimitiveArrayCritical(dstArray, dstBytes, 0);
-}
-
-static void Memory_unsafeBulkPut(JNIEnv* env, jclass, jbyteArray dstArray, jint dstOffset,
-        jint byteCount, jobject srcObject, jint srcOffset, jint sizeofElement, jboolean swap) {
-    ScopedByteArrayRW dstBytes(env, dstArray);
-    if (dstBytes.get() == NULL) {
-        return;
-    }
-    jarray srcArray = reinterpret_cast<jarray>(srcObject);
-    jbyte* srcBytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(srcArray, NULL));
-    if (srcBytes == NULL) {
-        return;
-    }
-    jbyte* dst = dstBytes.get() + dstOffset;
-    const jbyte* src = srcBytes + srcOffset*sizeofElement;
-    unsafeBulkCopy(dst, src, byteCount, sizeofElement, swap);
-    env->ReleasePrimitiveArrayCritical(srcArray, srcBytes, 0);
-}
-
 static JNINativeMethod gMethods[] = {
-    NATIVE_METHOD(Memory, memmove, "(Ljava/lang/Object;ILjava/lang/Object;IJ)V"),
     FAST_NATIVE_METHOD(Memory, peekByte, "(J)B"),
     NATIVE_METHOD(Memory, peekByteArray, "(J[BII)V"),
     NATIVE_METHOD(Memory, peekCharArray, "(J[CIIZ)V"),
@@ -313,9 +244,7 @@
     NATIVE_METHOD(Memory, pokeLongArray, "(J[JIIZ)V"),
     FAST_NATIVE_METHOD(Memory, pokeShortNative, "(JS)V"),
     NATIVE_METHOD(Memory, pokeShortArray, "(J[SIIZ)V"),
-    NATIVE_METHOD(Memory, unsafeBulkGet, "(Ljava/lang/Object;II[BIIZ)V"),
-    NATIVE_METHOD(Memory, unsafeBulkPut, "([BIILjava/lang/Object;IIZ)V"),
 };
-void register_libcore_io_Memory(JNIEnv* env) {
-    jniRegisterNativeMethods(env, "libcore/io/Memory", gMethods, NELEM(gMethods));
+void register_com_android_i18n_timezone_internal_Memory(JNIEnv* env) {
+    jniRegisterNativeMethods(env, "com/android/i18n/timezone/internal/Memory", gMethods, NELEM(gMethods));
 }
diff --git a/android_icu4j/libcore_bridge/src/native/com_android_i18n_util_Log.cpp b/android_icu4j/libcore_bridge/src/native/com_android_i18n_util_Log.cpp
new file mode 100644
index 0000000..a5a2d61
--- /dev/null
+++ b/android_icu4j/libcore_bridge/src/native/com_android_i18n_util_Log.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Log"
+
+#include <memory>
+
+#include "jni.h"
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/jni_macros.h>
+
+typedef std::unique_ptr<char const[], std::function<void(char const*)>> jni_string;
+
+static void Log_log(JNIEnv* env, jclass, jint priority, jstring msg, jthrowable throwable) {
+  if (msg == NULL) {
+    jniThrowNullPointerException(env, "Log needs a message");
+    return;
+  }
+
+  std::unique_ptr<char const[], std::function<void(char const*)>> message(
+      env->GetStringUTFChars(msg, 0),
+      [=](char const* p) mutable{ env->ReleaseStringUTFChars(msg, p); }
+  );
+
+  LOG_PRI(priority, "Log", "%s", message.get());
+
+  if (throwable != NULL) {
+      jniLogException(env, priority, "Log", throwable);
+  }
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(Log, log, "(ILjava/lang/String;Ljava/lang/Throwable;)V"),
+};
+void register_com_android_i18n_util_Log(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "com/android/i18n/util/Log", gMethods, NELEM(gMethods));
+}
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneDataFilesTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneDataFilesTest.java
index a74ba30..13884bf 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneDataFilesTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneDataFilesTest.java
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
-package libcore.libcore.timezone;
-
-import org.junit.Test;
-
-import libcore.timezone.TimeZoneDataFiles;
+package com.android.i18n.test.timezone;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.icu.testsharding.MainTestShard;
+import com.android.i18n.timezone.TimeZoneDataFiles;
+import org.junit.Test;
+
+@MainTestShard
 public class TimeZoneDataFilesTest {
 
     private static final String ANDROID_TZDATA_ROOT_ENV = "ANDROID_TZDATA_ROOT";
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneIntegrationTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneIntegrationTest.java
index f24d8b1..57f0c6b 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneIntegrationTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/TimeZoneIntegrationTest.java
@@ -14,8 +14,14 @@
  * limitations under the License.
  */
 
-package libcore.libcore.icu;
+package com.android.i18n.test.timezone;
 
+import android.icu.testsharding.MainTestShard;
+import com.android.i18n.timezone.I18nModuleDebug;
+import com.android.i18n.timezone.DebugInfo;
+import com.android.i18n.timezone.TimeZoneDataFiles;
+import com.android.i18n.timezone.TzDataSetVersion;
+import com.android.i18n.timezone.ZoneInfoDb;
 import org.junit.Test;
 
 import android.icu.text.TimeZoneNames;
@@ -39,12 +45,7 @@
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import libcore.timezone.TimeZoneDataFiles;
 import libcore.timezone.TimeZoneFinder;
-import libcore.timezone.TzDataSetVersion;
-import libcore.timezone.ZoneInfoDb;
-import libcore.util.CoreLibraryDebug;
-import libcore.util.DebugInfo;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -54,6 +55,7 @@
 /**
  * Tests that compare ICU and libcore time zone behavior and similar cross-cutting concerns.
  */
+@MainTestShard
 public class TimeZoneIntegrationTest {
 
     // http://b/28949992
@@ -259,7 +261,7 @@
      */
     @Test
     public void testTimeZoneDebugInfo() throws Exception {
-        DebugInfo debugInfo = CoreLibraryDebug.getDebugInfo();
+        DebugInfo debugInfo = I18nModuleDebug.getDebugInfo();
 
         // Devices are expected to have a time zone module which overrides or extends the data in
         // the runtime module depending on the file. It's not actually mandatory for all Android
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/TzDataSetVersionTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/TzDataSetVersionTest.java
index a653301..fdf7294 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/TzDataSetVersionTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/TzDataSetVersionTest.java
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-package libcore.libcore.timezone;
+package com.android.i18n.test.timezone;
 
+import android.icu.testsharding.MainTestShard;
 import junit.framework.TestCase;
 
-import libcore.timezone.TzDataSetVersion;
-import libcore.timezone.TzDataSetVersion.TzDataSetException;
+import com.android.i18n.timezone.TzDataSetVersion;
+import com.android.i18n.timezone.TzDataSetVersion.TzDataSetException;
 
+@MainTestShard
 public class TzDataSetVersionTest extends TestCase {
 
     private static final int INVALID_VERSION_LOW = -1;
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDataTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDataTest.java
index 8b9715f..2957128 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDataTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDataTest.java
@@ -13,36 +13,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package libcore.libcore.util;
+package com.android.i18n.test.timezone;
 
-import junit.framework.TestCase;
-
+import android.icu.testsharding.MainTestShard;
+import com.android.i18n.timezone.ZoneInfoData;
+import com.android.i18n.timezone.ZoneInfoDb;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
 import java.nio.ByteBuffer;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Date;
-import libcore.io.BufferIterator;
-import libcore.timezone.ZoneInfoDb;
+import junit.framework.TestCase;
+import com.android.i18n.timezone.internal.BufferIterator;
 import libcore.timezone.testing.ZoneInfoTestHelper;
-import libcore.util.ZoneInfo;
 
 /**
- * Tests for {@link ZoneInfo}
+ * Tests for {@link ZoneInfoData}
  */
-public class ZoneInfoTest extends TestCase {
+@MainTestShard
+public class ZoneInfoDataTest extends TestCase {
 
   /**
-   * Checks that a {@link ZoneInfo} cannot be created without any types.
+   * Checks that a {@link ZoneInfoData} cannot be created without any types.
    */
   public void testMakeTimeZone_NoTypes() throws Exception {
     long[][] transitions = {};
     int[][] types = {};
     try {
-      createZoneInfo(transitions, types);
+      createZoneInfoData(transitions, types);
       fail();
     } catch (IOException expected) {
     }
@@ -56,7 +55,7 @@
     int[][] types = {
         { 4800, 0 }
     };
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types);
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types);
 
     // If there are no transitions then the offset should be constant irrespective of the time.
     Instant[] times = {
@@ -64,14 +63,14 @@
             Instant.ofEpochMilli(0),
             Instant.ofEpochMilli(Long.MAX_VALUE),
     };
-    assertOffsetAt(zoneInfo, offsetFromSeconds(4800), times);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(4800), times);
 
     // No transitions means no DST.
-    assertFalse("Doesn't use DST", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Doesn't use DST", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
 
     // The raw offset should be the offset of the first type.
-    assertRawOffset(zoneInfo, offsetFromSeconds(4800));
+    assertRawOffset(zoneInfoData, offsetFromSeconds(4800));
   }
 
   /**
@@ -84,18 +83,18 @@
     int[][] types = {
         { 3600, 0 }
     };
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types);
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types);
 
     // Any time before the first transition is assumed to use the first standard transition.
     Instant[] times = { timeFromSeconds(-2), timeFromSeconds(0), timeFromSeconds(2) };
-    assertOffsetAt(zoneInfo, offsetFromSeconds(3600), times);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(3600), times);
 
     // No transitions means no DST.
-    assertFalse("Doesn't use DST", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Doesn't use DST", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
 
     // The raw offset should be the offset of the first type.
-    assertRawOffset(zoneInfo, offsetFromSeconds(3600));
+    assertRawOffset(zoneInfoData, offsetFromSeconds(3600));
   }
 
   /**
@@ -109,7 +108,7 @@
         { 3600, 1 }
     };
     try {
-      createZoneInfo(transitions, types);
+      createZoneInfoData(transitions, types);
       fail("Did not detect no non-DST transitions");
     } catch (IllegalStateException expected) {
     }
@@ -130,26 +129,26 @@
         { 3600, 1 },
         { 5400, 0 }
     };
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types);
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types);
     Instant transitionTime = timeFromSeconds(-5);
 
     // Even a millisecond before a transition means that the transition is not active.
     Instant beforeTransitionTime = transitionTime.minusMillis(1);
-    assertOffsetAt(zoneInfo, offsetFromSeconds(1800), beforeTransitionTime);
-    assertInDaylightTime(zoneInfo, beforeTransitionTime, false);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(1800), beforeTransitionTime);
+    assertInDaylightTime(zoneInfoData, beforeTransitionTime, false);
 
     // A time equal to the transition point activates the transition.
-    assertOffsetAt(zoneInfo, offsetFromSeconds(3600), transitionTime);
-    assertInDaylightTime(zoneInfo, transitionTime, true);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(3600), transitionTime);
+    assertInDaylightTime(zoneInfoData, transitionTime, true);
 
     // A time after the transition point but before the next activates the transition.
     Instant afterTransitionTime = transitionTime.plusMillis(1);
-    assertOffsetAt(zoneInfo, offsetFromSeconds(3600), afterTransitionTime);
-    assertInDaylightTime(zoneInfo, afterTransitionTime, true);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(3600), afterTransitionTime);
+    assertInDaylightTime(zoneInfoData, afterTransitionTime, true);
 
-    assertFalse("Doesn't use DST", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
-    assertRawOffset(zoneInfo, offsetFromSeconds(5400));
+    assertFalse("Doesn't use DST", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
+    assertRawOffset(zoneInfoData, offsetFromSeconds(5400));
   }
 
   /**
@@ -167,27 +166,27 @@
         { 3600, 1 },
         { 5400, 0 }
     };
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types);
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types);
 
     Instant transitionTime = timeFromSeconds(5);
 
     // Even a millisecond before a transition means that the transition is not active.
     Instant beforeTransitionTime = transitionTime.minusMillis(1);
-    assertOffsetAt(zoneInfo, offsetFromSeconds(1800), beforeTransitionTime);
-    assertInDaylightTime(zoneInfo, beforeTransitionTime, false);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(1800), beforeTransitionTime);
+    assertInDaylightTime(zoneInfoData, beforeTransitionTime, false);
 
     // A time equal to the transition point activates the transition.
-    assertOffsetAt(zoneInfo, offsetFromSeconds(3600), transitionTime);
-    assertInDaylightTime(zoneInfo, transitionTime, true);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(3600), transitionTime);
+    assertInDaylightTime(zoneInfoData, transitionTime, true);
 
     // A time after the transition point but before the next activates the transition.
     Instant afterTransitionTime = transitionTime.plusMillis(1);
-    assertOffsetAt(zoneInfo, offsetFromSeconds(3600), afterTransitionTime);
-    assertInDaylightTime(zoneInfo, afterTransitionTime, true);
+    assertOffsetAt(zoneInfoData, offsetFromSeconds(3600), afterTransitionTime);
+    assertInDaylightTime(zoneInfoData, afterTransitionTime, true);
 
-    assertFalse("Doesn't use DST", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
-    assertRawOffset(zoneInfo, offsetFromSeconds(5400));
+    assertFalse("Doesn't use DST", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
+    assertRawOffset(zoneInfoData, offsetFromSeconds(5400));
   }
 
   /**
@@ -210,17 +209,17 @@
     // Or in other words (5400 - 3600) * 1000
     Duration expectedDSTSavings = offsetFromSeconds(5400 - 3600);
 
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(-700));
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types, timeFromSeconds(-700));
 
-    assertTrue("Should use DST but doesn't", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, expectedDSTSavings);
+    assertTrue("Should use DST but doesn't", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, expectedDSTSavings);
 
     // Now create one a few milliseconds before the DST transition to make sure that rounding
     // errors don't cause a problem.
-    zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(-100).minusMillis(5));
+    zoneInfoData = createZoneInfoData(transitions, types, timeFromSeconds(-100).minusMillis(5));
 
-    assertTrue("Should use DST but doesn't", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, expectedDSTSavings);
+    assertTrue("Should use DST but doesn't", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, expectedDSTSavings);
   }
 
   /**
@@ -243,17 +242,18 @@
     // Or in other words (7200 - 3600) * 1000
     Duration expectedDSTSavings = offsetFromSeconds(7200 - 3600);
 
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(4500) /* currentTime */);
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types,
+        timeFromSeconds(4500) /* currentTime */);
 
-    assertTrue("Should use DST but doesn't", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, expectedDSTSavings);
+    assertTrue("Should use DST but doesn't", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, expectedDSTSavings);
 
     // Now create one a few milliseconds before the DST transition to make sure that rounding
     // errors don't cause a problem.
-    zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(6000).minusMillis(5));
+    zoneInfoData = createZoneInfoData(transitions, types, timeFromSeconds(6000).minusMillis(5));
 
-    assertTrue("Should use DST but doesn't", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, expectedDSTSavings);
+    assertTrue("Should use DST but doesn't", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, expectedDSTSavings);
   }
 
   /**
@@ -272,17 +272,18 @@
         { 1800, 1 },
         { 5400, 0 }
     };
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(-1) /* currentTime */);
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types,
+        timeFromSeconds(-1) /* currentTime */);
 
-    assertFalse("Shouldn't use DST but does", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Shouldn't use DST but does", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
 
     // Now create one a few milliseconds after the DST transition to make sure that rounding
     // errors don't cause a problem.
-    zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(-2000).plusMillis(5));
+    zoneInfoData = createZoneInfoData(transitions, types, timeFromSeconds(-2000).plusMillis(5));
 
-    assertFalse("Shouldn't use DST but does", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Shouldn't use DST but does", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
   }
 
   /**
@@ -301,17 +302,17 @@
         { 1800, 1 },
         { 5400, 0 }
     };
-    ZoneInfo zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(4700));
+    ZoneInfoData zoneInfoData = createZoneInfoData(transitions, types, timeFromSeconds(4700));
 
-    assertFalse("Shouldn't use DST but does", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Shouldn't use DST but does", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
 
     // Now create one a few milliseconds after the DST transition to make sure that rounding
     // errors don't cause a problem.
-    zoneInfo = createZoneInfo(transitions, types, timeFromSeconds(4000).plusMillis(5));
+    zoneInfoData = createZoneInfoData(transitions, types, timeFromSeconds(4000).plusMillis(5));
 
-    assertFalse("Shouldn't use DST but does", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Shouldn't use DST but does", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
   }
 
   /**
@@ -356,15 +357,15 @@
       long[][] transitions = {
               { timeToSeconds(firstRealTransitionTime), 2 /* type 2 */ },
       };
-      ZoneInfo oldZoneInfo = createZoneInfo(transitions, types, currentTime);
-      assertRawOffset(oldZoneInfo, type2Offset);
+      ZoneInfoData oldZoneInfoData = createZoneInfoData(transitions, types, currentTime);
+      assertRawOffset(oldZoneInfoData, type2Offset);
 
       // We use the first non-DST type for times before the first transition.
-      assertOffsetAt(oldZoneInfo, type0Offset, before32BitTime);
-      assertOffsetAt(oldZoneInfo, type0Offset, earlyTimes);
+      assertOffsetAt(oldZoneInfoData, type0Offset, before32BitTime);
+      assertOffsetAt(oldZoneInfoData, type0Offset, earlyTimes);
 
       // This is after the first transition, so type 2.
-      assertOffsetAt(oldZoneInfo, type2Offset, afterFirstRealTransitionTimes);
+      assertOffsetAt(oldZoneInfoData, type2Offset, afterFirstRealTransitionTimes);
     }
 
     // Simulation a zone where there is an explicit transition at Integer.MIN_VALUE seconds. This
@@ -374,17 +375,17 @@
               { Integer.MIN_VALUE, 1 /* type 1 */ },
               { timeToSeconds(firstRealTransitionTime), 2 /* type 2 */ },
       };
-      ZoneInfo newZoneInfo = createZoneInfo(transitions, types, currentTime);
-      assertRawOffset(newZoneInfo, type2Offset);
+      ZoneInfoData newZoneInfoData = createZoneInfoData(transitions, types, currentTime);
+      assertRawOffset(newZoneInfoData, type2Offset);
 
       // We use the first non-DST type for times before the first transition.
-      assertOffsetAt(newZoneInfo, type0Offset, before32BitTime);
+      assertOffsetAt(newZoneInfoData, type0Offset, before32BitTime);
 
       // After the first transition, so type 1.
-      assertOffsetAt(newZoneInfo, type1Offset, earlyTimes);
+      assertOffsetAt(newZoneInfoData, type1Offset, earlyTimes);
 
       // This is after the second transition, so type 2.
-      assertOffsetAt(newZoneInfo, type2Offset, afterFirstRealTransitionTimes);
+      assertOffsetAt(newZoneInfoData, type2Offset, afterFirstRealTransitionTimes);
     }
   }
 
@@ -418,8 +419,8 @@
               { 1000, 0 },
               { 2000, 1 },
       };
-      ZoneInfo oldZoneInfo = createZoneInfo(transitions, types, currentTime);
-      assertOffsetAt(oldZoneInfo, expectedLateOffset, timesToCheck);
+      ZoneInfoData oldZoneInfoData = createZoneInfoData(transitions, types, currentTime);
+      assertOffsetAt(oldZoneInfoData, expectedLateOffset, timesToCheck);
     }
 
     // Create a simulation of a zone where there is an explicit transition at Integer.MAX_VALUE
@@ -430,13 +431,13 @@
               { 2000, 1 },
               { Integer.MAX_VALUE, 1}, // The extra transition.
       };
-      ZoneInfo newZoneInfo = createZoneInfo(transitions, types, currentTime);
-      assertOffsetAt(newZoneInfo, expectedLateOffset, timesToCheck);
+      ZoneInfoData newZoneInfoData = createZoneInfoData(transitions, types, currentTime);
+      assertOffsetAt(newZoneInfoData, expectedLateOffset, timesToCheck);
     }
   }
 
   /**
-   * Checks to make sure that ZoneInfo can handle up to 256 types.
+   * Checks to make sure that ZoneInfoData can handle up to 256 types.
    */
   public void testReadTimeZone_MaxTypeCount() throws Exception {
     long[][] transitions = {
@@ -448,23 +449,23 @@
     Arrays.fill(types, new int[2]);
     types[255] = new int[] { 3600, 0 };
 
-    ZoneInfo zoneInfo = createZoneInfo(getName(), transitions, types,
+    ZoneInfoData zoneInfoData = createZoneInfoData(getName(), transitions, types,
             timeFromSeconds(Integer.MIN_VALUE));
 
-    assertFalse("Shouldn't use DST but does", zoneInfo.useDaylightTime());
-    assertDSTSavings(zoneInfo, offsetFromSeconds(0));
+    assertFalse("Shouldn't use DST but does", zoneInfoData.useDaylightTime());
+    assertDSTSavings(zoneInfoData, offsetFromSeconds(0));
 
-    // Make sure that WallTime works properly with a ZoneInfo with 256 types.
-    ZoneInfo.WallTime wallTime = new ZoneInfo.WallTime();
-    wallTime.localtime(0, zoneInfo);
-    wallTime.mktime(zoneInfo);
+    // Make sure that WallTime works properly with a ZoneInfoData with 256 types.
+    ZoneInfoData.WallTime wallTime = new ZoneInfoData.WallTime();
+    wallTime.localtime(0, zoneInfoData);
+    wallTime.mktime(zoneInfoData);
   }
 
   /**
    * Create an instance for every available time zone for which we have data to ensure that they
    * can all be handled correctly.
    *
-   * <p>This is to ensure that ZoneInfo can read all time zone data without failing, it doesn't
+   * <p>This is to ensure that ZoneInfoData can read all time zone data without failing, it doesn't
    * check that it reads it correctly or that the data itself is correct. This is a sanity test
    * to ensure that any additional checks added to the code that reads the data source and
    * creates the {@link ZoneInfo} instances does not prevent any of the time zones being loaded.
@@ -476,12 +477,12 @@
     for (String id : availableIDs) {
       BufferIterator bufferIterator = instance.getBufferIterator(id);
 
-      // Create a ZoneInfo at the earliest possible time to allow us to use the
+      // Create a ZoneInfoData at the earliest possible time to allow us to use the
       // useDaylightTime() method to check whether it ever has or ever will support daylight
       // savings time.
-      ZoneInfo zoneInfo = ZoneInfo.readTimeZone(id, bufferIterator, Long.MIN_VALUE);
-      assertNotNull("TimeZone " + id + " was not created", zoneInfo);
-      assertEquals(id, zoneInfo.getID());
+      ZoneInfoData zoneInfoData = ZoneInfoData.readTimeZone(id, bufferIterator, Long.MIN_VALUE);
+      assertNotNull("TimeZone " + id + " was not created", zoneInfoData);
+      assertEquals(id, zoneInfoData.getID());
     }
   }
 
@@ -489,7 +490,7 @@
     ZoneInfoTestHelper.ZicDataBuilder builder =
             new ZoneInfoTestHelper.ZicDataBuilder()
                     .initializeToValid();
-    assertNotNull(createZoneInfo(getName(), Instant.now(), builder.build()));
+    assertNotNull(createZoneInfoData(getName(), Instant.now(), builder.build()));
   }
 
   public void testReadTimeZone_BadMagic() {
@@ -498,13 +499,13 @@
                     .initializeToValid()
                     .setMagic(0xdeadbeef); // Bad magic.
     try {
-      createZoneInfo(getName(), Instant.now(), builder.build());
+        createZoneInfoData(getName(), Instant.now(), builder.build());
       fail();
     } catch (IOException expected) {}
   }
 
   /**
-   * Checks to make sure that ZoneInfo rejects more than 256 types.
+   * Checks to make sure that ZoneInfoData rejects more than 256 types.
    */
   public void testReadTimeZone_TooManyTypes() {
     int typeCount = 257; // Max types allowed is 256
@@ -517,14 +518,14 @@
                     .setTransitionsAndTypes(transitions, types);
     byte[] bytes = builder.build();
     try {
-      createZoneInfo(getName(), Instant.now(), bytes);
+      createZoneInfoData(getName(), Instant.now(), bytes);
       fail("Did not detect too many types");
     } catch (IOException expected) {
     }
   }
 
   /**
-   * Checks to make sure that ZoneInfo rejects more than 2000 transitions.
+   * Checks to make sure that ZoneInfoData rejects more than 2000 transitions.
    */
   public void testReadTimeZone_TooManyTransitions() {
     int typeCount = 5;
@@ -537,7 +538,7 @@
                     .setTransitionsAndTypes(transitions, types);
     byte[] bytes = builder.build();
     try {
-      createZoneInfo(getName(), Instant.now(), bytes);
+      createZoneInfoData(getName(), Instant.now(), bytes);
       fail("Did not detect too many transitions");
     } catch (IOException expected) {
     }
@@ -561,7 +562,7 @@
 
     byte[] bytes = builder.build();
     try {
-      createZoneInfo(getName(), Instant.now(), bytes);
+      createZoneInfoData(getName(), Instant.now(), bytes);
       fail();
     } catch (IOException expected) {
     }
@@ -585,7 +586,7 @@
 
     byte[] bytes = builder.build();
     try {
-      createZoneInfo(getName(), Instant.now(), bytes);
+      createZoneInfoData(getName(), Instant.now(), bytes);
       fail();
     } catch (IOException expected) {
     }
@@ -609,69 +610,30 @@
 
     byte[] bytes = builder.build();
     try {
-      createZoneInfo(getName(), Instant.now(), bytes);
+      createZoneInfoData(getName(), Instant.now(), bytes);
       fail();
     } catch (IOException expected) {
     }
   }
 
-  /**
-   * Checks that we can read the serialized form of a {@link ZoneInfo} created in pre-OpenJDK
-   * AOSP.
-   *
-   * <p>One minor difference is that in pre-OpenJDK {@link ZoneInfo#mDstSavings} can be non-zero
-   * even if {@link ZoneInfo#mUseDst} was false. That was not visible externally (except through
-   * the {@link ZoneInfo#toString()} method) as the {@link ZoneInfo#getDSTSavings()} would check
-   * {@link ZoneInfo#mUseDst} and if it was false then would return 0. This checks to make sure
-   * that is handled properly. See {@link ZoneInfo#readObject(ObjectInputStream)}.
-   */
-  public void testReadSerialized() throws Exception {
-    ZoneInfo zoneInfoRead;
-    try (InputStream is = getClass().getResourceAsStream("ZoneInfoTest_ZoneInfo.golden.ser");
-         ObjectInputStream ois = new ObjectInputStream(is)) {
-      Object object = ois.readObject();
-      assertTrue("Not a ZoneInfo instance", object instanceof ZoneInfo);
-      zoneInfoRead = (ZoneInfo) object;
-    }
-
-    long[][] transitions = {
-        { -5000, 0 },
-        { -2000, 1 },
-        { -500, 0 },
-        { 0, 2 },
-    };
-    int[][] types = {
-        { 3600, 0 },
-        { 1800, 1 },
-        { 5400, 0 }
-    };
-    ZoneInfo zoneInfoCreated = createZoneInfo(
-            "test", transitions, types, timeFromSeconds(-1));
-
-    assertEquals("Read ZoneInfo does not match created one", zoneInfoCreated, zoneInfoRead);
-    assertEquals("useDaylightTime() mismatch",
-        zoneInfoCreated.useDaylightTime(), zoneInfoRead.useDaylightTime());
-    assertEquals("getDSTSavings() mismatch",
-        zoneInfoCreated.getDSTSavings(), zoneInfoRead.getDSTSavings());
+  private static void assertRawOffset(ZoneInfoData zoneInfoData, Duration expectedOffset) {
+    assertEquals(expectedOffset.toMillis(), zoneInfoData.getRawOffset());
   }
 
-  private static void assertRawOffset(ZoneInfo zoneInfo, Duration expectedOffset) {
-    assertEquals(expectedOffset.toMillis(), zoneInfo.getRawOffset());
+  private static void assertDSTSavings(ZoneInfoData zoneInfoData, Duration expectedDSTSavings) {
+    assertEquals(expectedDSTSavings.toMillis(), zoneInfoData.getDSTSavings());
   }
 
-  private static void assertDSTSavings(ZoneInfo zoneInfo, Duration expectedDSTSavings) {
-    assertEquals(expectedDSTSavings.toMillis(), zoneInfo.getDSTSavings());
-  }
-
-  private static void assertInDaylightTime(ZoneInfo zoneInfo, Instant time, boolean expectedValue) {
-    assertEquals(expectedValue, zoneInfo.inDaylightTime(new Date(time.toEpochMilli())));
+  private static void assertInDaylightTime(ZoneInfoData zoneInfoData, Instant time,
+      boolean expectedValue) {
+    assertEquals(expectedValue, zoneInfoData.inDaylightTime(new Date(time.toEpochMilli())));
   }
 
   private static void assertOffsetAt(
-          ZoneInfo zoneInfo, Duration expectedOffset, Instant... times) {
+          ZoneInfoData zoneInfoData, Duration expectedOffset, Instant... times) {
     for (Instant time : times) {
       assertEquals("Unexpected offset at " + time,
-              expectedOffset.toMillis(), zoneInfo.getOffset(time.toEpochMilli()));
+              expectedOffset.toMillis(), zoneInfoData.getOffset(time.toEpochMilli()));
     }
   }
 
@@ -695,29 +657,29 @@
     return (int) seconds;
   }
 
-  private ZoneInfo createZoneInfo(long[][] transitions, int[][] types)
+  private ZoneInfoData createZoneInfoData(long[][] transitions, int[][] types)
       throws Exception {
-    return createZoneInfo(getName(), transitions, types, Instant.now());
+    return createZoneInfoData(getName(), transitions, types, Instant.now());
   }
 
-  private ZoneInfo createZoneInfo(long[][] transitions, int[][] types, Instant currentTime)
+  private ZoneInfoData createZoneInfoData(long[][] transitions, int[][] types, Instant currentTime)
           throws Exception {
-    return createZoneInfo(getName(), transitions, types, currentTime);
+    return createZoneInfoData(getName(), transitions, types, currentTime);
   }
 
-  private ZoneInfo createZoneInfo(String name, long[][] transitions, int[][] types,
+  private ZoneInfoData createZoneInfoData(String name, long[][] transitions, int[][] types,
           Instant currentTime) throws Exception {
 
     ZoneInfoTestHelper.ZicDataBuilder builder =
             new ZoneInfoTestHelper.ZicDataBuilder()
                     .setTransitionsAndTypes(transitions, types);
-    return createZoneInfo(name, currentTime, builder.build());
+    return createZoneInfoData(name, currentTime, builder.build());
   }
 
-  private static ZoneInfo createZoneInfo(String name, Instant currentTime, byte[] bytes)
+  private static ZoneInfoData createZoneInfoData(String name, Instant currentTime, byte[] bytes)
           throws IOException {
     ByteBufferIterator bufferIterator = new ByteBufferIterator(ByteBuffer.wrap(bytes));
-    return ZoneInfo.readTimeZone(
+    return ZoneInfoData.readTimeZone(
             "TimeZone for '" + name + "'", bufferIterator, currentTime.toEpochMilli());
   }
 
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDbTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDbTest.java
index 48eb286..a02ad84 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDbTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/ZoneInfoDbTest.java
@@ -14,19 +14,21 @@
  * limitations under the License.
  */
 
-package libcore.libcore.timezone;
+package com.android.i18n.test.timezone;
 
+import static com.android.i18n.timezone.ZoneInfoDb.SIZEOF_INDEX_ENTRY;
+
+import android.icu.testsharding.MainTestShard;
+import android.icu.util.TimeZone;
+import com.android.i18n.timezone.TimeZoneDataFiles;
+import com.android.i18n.timezone.ZoneInfoData;
+import com.android.i18n.timezone.ZoneInfoDb;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.RandomAccessFile;
-
-import libcore.timezone.TimeZoneDataFiles;
 import libcore.timezone.testing.ZoneInfoTestHelper;
-import libcore.util.ZoneInfo;
-import libcore.timezone.ZoneInfoDb;
 
-import static libcore.timezone.ZoneInfoDb.SIZEOF_INDEX_ENTRY;
-
+@MainTestShard
 public class ZoneInfoDbTest extends junit.framework.TestCase {
 
   // The base tzdata file, always present on a device.
@@ -213,20 +215,16 @@
     }
   }
 
-  // Confirms any caching that exists correctly handles TimeZone mutability.
+  // Confirms any caching that exists correctly handles ZoneInfoData mutability.
   public void testMakeTimeZone_timeZoneMutability() throws Exception {
     try (ZoneInfoDb data = ZoneInfoDb.loadTzData(TZDATA_FILE)) {
       String tzId = "Europe/London";
-      ZoneInfo first = data.makeTimeZone(tzId);
-      ZoneInfo second = data.makeTimeZone(tzId);
+      ZoneInfoData first = data.makeZoneInfoData(tzId);
+      ZoneInfoData second = data.makeZoneInfoData(tzId);
       assertNotSame(first, second);
 
       assertTrue(first.hasSameRules(second));
 
-      first.setID("Not Europe/London");
-
-      assertFalse(first.getID().equals(second.getID()));
-
       first.setRawOffset(3600);
       assertFalse(first.getRawOffset() == second.getRawOffset());
     }
@@ -234,14 +232,14 @@
 
   public void testMakeTimeZone_notFound() throws Exception {
     try (ZoneInfoDb data = ZoneInfoDb.loadTzData(TZDATA_FILE)) {
-      assertNull(data.makeTimeZone("THIS_TZ_DOES_NOT_EXIST"));
+      assertNull(data.makeZoneInfoData("THIS_TZ_DOES_NOT_EXIST"));
       assertFalse(data.hasTimeZone("THIS_TZ_DOES_NOT_EXIST"));
     }
   }
 
   public void testMakeTimeZone_found() throws Exception {
     try (ZoneInfoDb data = ZoneInfoDb.loadTzData(TZDATA_FILE)) {
-      assertNotNull(data.makeTimeZone("Europe/London"));
+      assertNotNull(data.makeZoneInfoData("Europe/London"));
       assertTrue(data.hasTimeZone("Europe/London"));
     }
   }
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/BasicLruCacheTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/BasicLruCacheTest.java
index 707fdda..8d45083 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/BasicLruCacheTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/BasicLruCacheTest.java
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package libcore.libcore.util;
+package com.android.i18n.test.timezone.internal;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import android.icu.testsharding.MainTestShard;
+import com.android.i18n.timezone.internal.BasicLruCache;
 import junit.framework.TestCase;
 
-import libcore.util.BasicLruCache;
-
+@MainTestShard
 public final class BasicLruCacheTest extends TestCase {
 
     public void testCreateOnCacheMiss() {
@@ -68,64 +65,8 @@
         BasicLruCache<String, String> cache = new BasicLruCache<String, String>(1);
         cache.put("a", "A");
         cache.put("b", "B");
-        assertSnapshot(cache, "b", "B");
     }
 
-    public void testEntryEvictedWhenFull() {
-        List<String> expectedEvictionLog = new ArrayList<String>();
-        final List<String> evictionLog = new ArrayList<String>();
-        BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3) {
-            @Override protected void entryEvicted(String key, String value) {
-                evictionLog.add(key + "=" + value);
-            }
-        };
-
-        cache.put("a", "A");
-        cache.put("b", "B");
-        cache.put("c", "C");
-        assertEquals(expectedEvictionLog, evictionLog);
-
-        cache.put("d", "D");
-        expectedEvictionLog.add("a=A");
-        assertEquals(expectedEvictionLog, evictionLog);
-    }
-
-    /**
-     * Replacing the value for a key doesn't cause an eviction but it does bring
-     * the replaced entry to the front of the queue.
-     */
-    public void testPutDoesNotCauseEviction() {
-        final List<String> evictionLog = new ArrayList<String>();
-        List<String> expectedEvictionLog = new ArrayList<String>();
-        BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3) {
-            @Override protected void entryEvicted(String key, String value) {
-                evictionLog.add(key + "=" + value);
-            }
-        };
-
-        cache.put("a", "A");
-        cache.put("b", "B");
-        cache.put("c", "C");
-        cache.put("b", "B2");
-        assertEquals(expectedEvictionLog, evictionLog);
-        assertSnapshot(cache, "a", "A", "c", "C", "b", "B2");
-    }
-
-    public void testEvictAll() {
-        final List<String> evictionLog = new ArrayList<String>();
-        BasicLruCache<String, String> cache = new BasicLruCache<String, String>(10) {
-            @Override protected void entryEvicted(String key, String value) {
-                evictionLog.add(key + "=" + value);
-            }
-        };
-
-        cache.put("a", "A");
-        cache.put("b", "B");
-        cache.put("c", "C");
-        cache.evictAll();
-        assertSnapshot(cache);
-        assertEquals(Arrays.asList("a=A", "b=B", "c=C"), evictionLog);
-    }
 
     private BasicLruCache<String, String> newCreatingCache() {
         return new BasicLruCache<String, String>(3) {
@@ -134,15 +75,4 @@
             }
         };
     }
-
-    private <T> void assertSnapshot(BasicLruCache<T, T> cache, T... keysAndValues) {
-        List<T> actualKeysAndValues = new ArrayList<T>();
-        for (Map.Entry<T, T> entry : cache.snapshot().entrySet()) {
-            actualKeysAndValues.add(entry.getKey());
-            actualKeysAndValues.add(entry.getValue());
-        }
-
-        // assert using lists because order is important for LRUs
-        assertEquals(Arrays.asList(keysAndValues), actualKeysAndValues);
-    }
 }
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryMappedFileTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryMappedFileTest.java
index 1936ea7..a018f41 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryMappedFileTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryMappedFileTest.java
@@ -14,13 +14,13 @@
  * limitations under the License
  */
 
-package libcore.libcore.io;
+package com.android.i18n.test.timezone.internal;
 
-import junit.framework.TestCase;
-
+import android.icu.testsharding.MainTestShard;
 import android.system.ErrnoException;
 import android.system.OsConstants;
-
+import com.android.i18n.timezone.internal.BufferIterator;
+import com.android.i18n.timezone.internal.MemoryMappedFile;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.nio.ByteBuffer;
@@ -28,10 +28,9 @@
 import java.nio.IntBuffer;
 import java.util.Arrays;
 import java.util.function.Function;
-import libcore.io.BufferIterator;
-import libcore.io.MemoryMappedFile;
-import libcore.testing.io.TestIoUtils;
+import junit.framework.TestCase;
 
+@MainTestShard
 public class MemoryMappedFileTest extends TestCase {
 
     private File tempDir;
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryTest.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryTest.java
index 80ff4ea..ad4a279 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryTest.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/MemoryTest.java
@@ -15,14 +15,15 @@
  * limitations under the License.
  */
 
-package libcore.libcore.io;
+package com.android.i18n.test.timezone.internal;
 
+import android.icu.testsharding.MainTestShard;
+import com.android.i18n.timezone.internal.Memory;
 import dalvik.system.VMRuntime;
 import java.util.Arrays;
 import junit.framework.TestCase;
 
-import libcore.io.Memory;
-
+@MainTestShard
 public class MemoryTest extends TestCase {
     public void testSetIntArray() {
         int[] values = { 3, 7, 31, 127, 8191, 131071, 524287, 2147483647 };
diff --git a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/TestIoUtils.java b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/TestIoUtils.java
index 34a2cf7..e72efdc 100644
--- a/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/TestIoUtils.java
+++ b/android_icu4j/testing/src/com/android/i18n/test/timezone/internal/TestIoUtils.java
@@ -14,28 +14,20 @@
  * limitations under the License.
  */
 
-package libcore.testing.io;
+package com.android.i18n.test.timezone.internal;
 
+import android.icu.testsharding.MainTestShard;
 import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.util.Random;
 
+
+@MainTestShard
 public class TestIoUtils {
     private final static Random random = new Random();
 
     private TestIoUtils() {}
 
     /**
-     * Returns the contents of 'path' as a string. The contents are assumed to be UTF-8.
-     */
-    public static String readFileAsString(String absolutePath) throws IOException {
-        return new String(Files.readAllBytes(Paths.get(absolutePath)), StandardCharsets.UTF_8);
-    }
-
-    /**
      * Creates a unique new temporary directory under "java.io.tmpdir".
      */
     public static File createTemporaryDirectory(String prefix) {