diff options
Diffstat (limited to 'android_icu4j')
7 files changed, 108 insertions, 35 deletions
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 6f3b16eac..2ffdef015 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 @@ -39,14 +39,27 @@ public final class TimeZoneDataFiles { * Returns time zone file paths for the specified file name in an array in the order they * should be tried. See {@link AndroidDataFiles#generateIcuDataPath()} for ICU files instead. * <ul> - * <li>[0] - the location of the file from the time zone module under /apex (must exist).</li> + * <li>[0] - the location of the versioned file from the time zone module under /apex + * (must exist).</li> + * <li>[1] - old, unversioned location of the file from the time zone module under /apex. Will + * be removed once prebuilts are updated.</> * </ul> */ // VisibleForTesting public static String[] getTimeZoneFilePaths(String fileName) { - return new String[] { getTimeZoneModuleTzFile(fileName) }; + return new String[] { + // TODO(b/319103072) There should be only versioned path. + getTimeZoneModuleTzFile(fileName), + getVersionedTimeZoneModuleTzFile(fileName) }; } + // TODO(b/319103072) This should be removed once prebuilts are updated. + public static String getVersionedTimeZoneModuleTzFile(String fileName) { + return getTimeZoneModuleFile("tz/versioned/" + + TzDataSetVersion.currentFormatMajorVersion() + "/" + fileName); + } + + // TODO(b/319103072) This method should read from versioned directory. public static String getTimeZoneModuleTzFile(String fileName) { return getTimeZoneModuleFile("tz/" + fileName); } 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 c91745cbd..e3dd27581 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 @@ -61,7 +61,9 @@ public final class TzDataSetVersion { * version to 1 when doing so. */ // @VisibleForTesting : Keep this inline-able: it is used from CTS tests. + // LINT.IfChange public static final int CURRENT_FORMAT_MAJOR_VERSION = 8; // Android V + // LINT.ThenChange(external/icu/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java) /** * Returns the major tz data format version supported by this device. @@ -86,12 +88,9 @@ public final class TzDataSetVersion { return CURRENT_FORMAT_MINOR_VERSION; } - /** The full major + minor tz data format version for this device. */ - private static final String FULL_CURRENT_FORMAT_VERSION_STRING = - toFormatVersionString(CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION); - - private static final int FORMAT_VERSION_STRING_LENGTH = - FULL_CURRENT_FORMAT_VERSION_STRING.length(); + /** The full major + minor tz data format version's length for this device. */ + // @VisibleForTesting + public static final int FORMAT_VERSION_STRING_LENGTH = 7; private static final Pattern FORMAT_VERSION_PATTERN = Pattern.compile("(\\d{3})\\.(\\d{3})"); /** A pattern that matches the IANA rules value of a rules update. e.g. "2016g" */ @@ -287,7 +286,8 @@ public final class TzDataSetVersion { return value; } - private static String toFormatVersionString(int majorFormatVersion, int minorFormatVersion) { + // @VisibleForTesting + public static String toFormatVersionString(int majorFormatVersion, int minorFormatVersion) { return to3DigitVersionString(majorFormatVersion) + "." + to3DigitVersionString(minorFormatVersion); } diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java b/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java index 5e0c57f09..f5f0c6adc 100644 --- a/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java +++ b/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java @@ -21,7 +21,6 @@ import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.MissingResourceException; import java.util.Set; @@ -226,11 +225,15 @@ public final class ICUBinary { abstract void addBaseNamesInFolder(String folder, String suffix, Set<String> names); } + // BEGIN Android-changed: Map file only once during ICUBinary initialization. Attempt to fix + // some apps not seeing metazones.res file. See b/339899412. private static final class SingleDataFile extends DataFile { + private final ByteBuffer bytes; private final File path; SingleDataFile(String item, File path) { super(item); + this.bytes = mapFile(path); this.path = path; } @Override @@ -241,12 +244,13 @@ public final class ICUBinary { @Override ByteBuffer getData(String requestedPath) { if (requestedPath.equals(itemPath)) { - return mapFile(path); + return bytes.duplicate(); } else { return null; } } - + // END Android-changed: Map file only once during ICUBinary initialization. Attempt to fix + // some apps not seeing metazones.res files in b/339899412. @Override void addBaseNamesInFolder(String folder, String suffix, Set<String> names) { if (itemPath.length() > folder.length() + suffix.length() && @@ -284,8 +288,7 @@ public final class ICUBinary { } } - // Android-changed: make icuDataFiles immutable and assign value exactly once. - private static final List<DataFile> icuDataFiles; + private static final List<DataFile> icuDataFiles = new ArrayList<>(); static { // BEGIN Android-changed: Initialize ICU data file paths. @@ -299,15 +302,9 @@ public final class ICUBinary { dataPath = AndroidDataFiles.generateIcuDataPath(); } // END Android-changed: Initialize ICU data file paths. - // BEGIN Android-changed: make icuDataFiles immutable and assign value exactly once. if (dataPath != null) { - List<DataFile> resolvedFiles = new ArrayList<>(); - addDataFilesFromPath(dataPath, resolvedFiles); - icuDataFiles = Collections.unmodifiableList(resolvedFiles); - } else { - icuDataFiles = Collections.emptyList(); + addDataFilesFromPath(dataPath, icuDataFiles); } - // END Android-changed: make icuDataFiles immutable and assign value exactly once. } private static void addDataFilesFromPath(String dataPath, List<DataFile> files) { @@ -330,8 +327,7 @@ public final class ICUBinary { path = path.substring(0, path.length() - 1); } if (path.length() != 0) { - // Android-changed: pass `files` argument and not icuDataFiles. - addDataFilesFromFolder(new File(path), new StringBuilder(), files); + addDataFilesFromFolder(new File(path), new StringBuilder(), icuDataFiles); } if (sepIndex < 0) { break; diff --git a/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java b/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java index 74ab82fc3..121197863 100644 --- a/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java +++ b/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java @@ -38,8 +38,19 @@ public class AndroidDataFiles { public static final String ANDROID_I18N_ROOT_ENV = "ANDROID_I18N_ROOT"; public static final String ANDROID_TZDATA_ROOT_ENV = "ANDROID_TZDATA_ROOT"; + /** + * This is identical to + * {@link com.android.i18n.timezone.TzDataSetVersion#CURRENT_FORMAT_MAJOR_VERSION}, but because + * dependency is in the opposite direction we can't refer to that field from this class. + * TzDataSetVersionTest ensures that their values are the same. + */ + // VisibleForTesting + // LINT.IfChange + public static final int CURRENT_MAJOR_VERSION = 8; + // LINT.ThenChange(external/icu/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java) + // VisibleForTesting - public static String getTimeZoneModuleIcuFile(String fileName) { + public static String getTimeZoneModuleIcuFileAtOldLocation(String fileName) { return getTimeZoneModuleFile("icu/" + fileName); } @@ -47,6 +58,10 @@ public class AndroidDataFiles { return System.getenv(ANDROID_TZDATA_ROOT_ENV) + "/etc/" + fileName; } + private static String getTimeZoneModuleIcuFile(String fileName) { + return getTimeZoneModuleFile("tz/versioned/" + CURRENT_MAJOR_VERSION + "/icu/" + fileName); + } + // VisibleForTesting public static String getI18nModuleIcuFile(String fileName) { return getI18nModuleFile("icu/" + fileName); @@ -64,7 +79,13 @@ public class AndroidDataFiles { // ICU should look for a mounted time zone module file in /apex. This is used for // (optional) time zone data that can be updated with an APEX file. - String timeZoneModuleIcuDataPath = getTimeZoneModuleIcuFile(""); + paths.add(getTimeZoneModuleIcuFile("")); + + // TODO (b/319103072): remove this path once prebuilts are updated. + // Starting from V content of the tzdata module is versioned so it can be used across + // multiple Android releases. timeZoneModuleIcuDataPath will be removed once all prebuilts + // are updated. Production tzdata6 won't have ICU files under etc/icu. + String timeZoneModuleIcuDataPath = getTimeZoneModuleIcuFileAtOldLocation(""); paths.add(timeZoneModuleIcuDataPath); // ICU should always look in the i18n module path as this is where most of the data diff --git a/android_icu4j/testing/src/android/icu/extratest/platform/AndroidDataFilesTest.java b/android_icu4j/testing/src/android/icu/extratest/platform/AndroidDataFilesTest.java index 190fb3d1b..f91fbcac1 100644 --- a/android_icu4j/testing/src/android/icu/extratest/platform/AndroidDataFilesTest.java +++ b/android_icu4j/testing/src/android/icu/extratest/platform/AndroidDataFilesTest.java @@ -48,11 +48,21 @@ import java.util.stream.Stream; public class AndroidDataFilesTest { private static final Set<String> TZDATA_RES_FILES = - Set.of( + Stream.of( + "/apex/com.android.tzdata/etc/tz/versioned/%d/icu/metaZones.res", + "/apex/com.android.tzdata/etc/tz/versioned/%d/icu/windowsZones.res", + "/apex/com.android.tzdata/etc/tz/versioned/%d/icu/zoneinfo64.res", + "/apex/com.android.tzdata/etc/tz/versioned/%d/icu/timezoneTypes.res") + .map(path -> path.formatted(AndroidDataFiles.CURRENT_MAJOR_VERSION)) + .collect(toSet()); + + private static final Set<String> TZDATA_RES_FILES_AT_OLD_LOCATION = + Stream.of( "/apex/com.android.tzdata/etc/icu/metaZones.res", "/apex/com.android.tzdata/etc/icu/windowsZones.res", "/apex/com.android.tzdata/etc/icu/zoneinfo64.res", - "/apex/com.android.tzdata/etc/icu/timezoneTypes.res"); + "/apex/com.android.tzdata/etc/icu/timezoneTypes.res") + .collect(toSet()); private static final String ICU_DAT_PATH = "/apex/com.android.i18n/etc/icu/icudt" + VersionInfo.ICU_VERSION.getMajor() + "l.dat"; @@ -80,12 +90,16 @@ public class AndroidDataFilesTest { .map(f -> f.getPath()) .collect(toSet()); - for (String resFile : TZDATA_RES_FILES) { - assertContains(icuFiles, resFile); - } + assertTrue(containsAllResFiles(icuFiles)); + assertContains(icuFiles, ICU_DAT_PATH); } + private static boolean containsAllResFiles(Set<String> existingFiles) { + return existingFiles.containsAll(TZDATA_RES_FILES) + || existingFiles.containsAll(TZDATA_RES_FILES_AT_OLD_LOCATION); + } + private static boolean isIcuFile(File file) { return file.getName().endsWith(".res") || file.getName().endsWith(".dat"); } 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 064c7bbe0..3b5aff459 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 @@ -41,7 +41,7 @@ public class TimeZoneDataFilesTest { @Test public void getTimeZoneFilePaths() { String[] paths = TimeZoneDataFiles.getTimeZoneFilePaths("foo"); - assertEquals(1, paths.length); + assertEquals(2, paths.length); assertTrue(paths[0].startsWith(System.getenv(ANDROID_TZDATA_ROOT_ENV))); assertTrue(paths[0].endsWith("/foo")); @@ -53,13 +53,19 @@ public class TimeZoneDataFilesTest { String icuDataPath = AndroidDataFiles.generateIcuDataPath(); String[] paths = icuDataPath.split(":"); - assertEquals(2, paths.length); + assertEquals(3, paths.length); + + String versionedTzdataModulePath = paths[0]; + assertTrue(versionedTzdataModulePath + " invalid", + versionedTzdataModulePath.startsWith(System.getenv(ANDROID_TZDATA_ROOT_ENV))); + assertTrue(versionedTzdataModulePath + " should be versioned", + versionedTzdataModulePath.contains("versioned")); - String tzdataModulePath = paths[0]; + String tzdataModulePath = paths[1]; assertTrue(tzdataModulePath + " invalid", tzdataModulePath.startsWith(System.getenv(ANDROID_TZDATA_ROOT_ENV))); - String runtimeModulePath = paths[1]; + String runtimeModulePath = paths[2]; assertTrue(runtimeModulePath + " invalid", runtimeModulePath.startsWith(System.getenv(ANDROID_I18N_ROOT_ENV))); assertTrue(runtimeModulePath + " invalid", runtimeModulePath.contains("/etc/icu")); 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 fdf7294bf..937ad5c12 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 @@ -16,12 +16,24 @@ package com.android.i18n.test.timezone; +import static com.android.i18n.timezone.TzDataSetVersion.CURRENT_FORMAT_MAJOR_VERSION; +import static com.android.i18n.timezone.TzDataSetVersion.CURRENT_FORMAT_MINOR_VERSION; +import static com.android.i18n.timezone.TzDataSetVersion.FORMAT_VERSION_STRING_LENGTH; +import static com.android.i18n.timezone.TzDataSetVersion.toFormatVersionString; + +import static java.lang.invoke.MethodType.methodType; + +import android.icu.platform.AndroidDataFiles; import android.icu.testsharding.MainTestShard; -import junit.framework.TestCase; import com.android.i18n.timezone.TzDataSetVersion; import com.android.i18n.timezone.TzDataSetVersion.TzDataSetException; +import junit.framework.TestCase; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + @MainTestShard public class TzDataSetVersionTest extends TestCase { @@ -100,6 +112,17 @@ public class TzDataSetVersionTest extends TestCase { assertFalse(TzDataSetVersion.isCompatibleWithThisDevice(olderMinor)); } + public void testConsistency() { + String msg = "Major versions in TzDataSetVersion and AndroidDataFiles differ"; + assertEquals(msg, CURRENT_FORMAT_MAJOR_VERSION, AndroidDataFiles.CURRENT_MAJOR_VERSION); + } + + public void testFormatLength() { + String formatVersion = toFormatVersionString(CURRENT_FORMAT_MAJOR_VERSION, + CURRENT_FORMAT_MINOR_VERSION); + assertEquals(FORMAT_VERSION_STRING_LENGTH, formatVersion.length()); + } + private TzDataSetVersion createTzDataSetVersion(int majorFormatVersion, int minorFormatVersion) throws TzDataSetException { return new TzDataSetVersion(majorFormatVersion, minorFormatVersion, VALID_RULES_VERSION, 3); |