diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2020-07-16 14:00:21 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2020-07-16 14:00:21 +0000 |
commit | ad18336b2ee5d3546a2eef27ea62d8ebb448b7b5 (patch) | |
tree | f1e2fd205c2fb5df0ef2957b5ea2b7977986d7a7 | |
parent | b5da79ac8e46033f8a23620b9a2142c9c208b1ab (diff) | |
parent | fe163dc550e4182ab9785a48afda9e1bac98f6f0 (diff) | |
download | libcore-ad18336b2ee5d3546a2eef27ea62d8ebb448b7b5.tar.gz |
Snap for 6684105 from fe163dc550e4182ab9785a48afda9e1bac98f6f0 to rvc-beta3-releaseandroid-r-beta-3r-beta-3
Change-Id: I12612ad21e54113ab629a3d7afcc46acc80a7f2a
6 files changed, 221 insertions, 10 deletions
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerTest.java index 2191b0c75cb..85899f3f3c0 100644 --- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerTest.java +++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerTest.java @@ -5694,7 +5694,9 @@ public class ScannerTest extends TestCase { } // http://code.google.com/p/android/issues/detail?id=57050 - public void testPerformance() throws Exception { + // Disable this test since it causes oom failures in follow on + // tests. See b/160171148 for details. + public void disableTestPerformance() throws Exception { int count = 100000; ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -5739,5 +5741,6 @@ public class ScannerTest extends TestCase { fail(); } } + System.gc(); } } diff --git a/luni/src/main/java/android/compat/Compatibility.java b/luni/src/main/java/android/compat/Compatibility.java index e5d7cbb8d8c..9b4b1afe839 100644 --- a/luni/src/main/java/android/compat/Compatibility.java +++ b/luni/src/main/java/android/compat/Compatibility.java @@ -121,13 +121,13 @@ public final class Compatibility { } @CorePlatformApi protected void reportChange(long changeId) { - System.logW(String.format( - "No Compatibility callbacks set! Reporting change %d", changeId)); + // Do not use String.format here (b/160912695) + System.logW("No Compatibility callbacks set! Reporting change " + changeId); } @CorePlatformApi protected boolean isChangeEnabled(long changeId) { - System.logW(String.format( - "No Compatibility callbacks set! Querying change %d", changeId)); + // Do not use String.format here (b/160912695) + System.logW("No Compatibility callbacks set! Querying change " + changeId); return true; } } diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java index ea6fe6fe917..12b978de154 100644 --- a/luni/src/main/java/libcore/icu/LocaleData.java +++ b/luni/src/main/java/libcore/icu/LocaleData.java @@ -16,12 +16,17 @@ package libcore.icu; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; +import android.compat.Compatibility; import android.icu.impl.ICUData; import android.icu.impl.ICUResourceBundle; import android.icu.text.NumberingSystem; import android.icu.util.UResourceBundle; +import dalvik.system.VMRuntime; + import java.text.DateFormat; import java.util.HashMap; import java.util.Locale; @@ -38,6 +43,44 @@ import libcore.util.Objects; */ @libcore.api.CorePlatformApi public final class LocaleData { + + /** + * @see #USE_REAL_ROOT_LOCALE + */ + private static final Locale LOCALE_EN_US_POSIX = new Locale("en", "US", "POSIX"); + + // In Android Q or before, when this class tries to load {@link Locale#ROOT} data, en_US_POSIX + // locale data is incorrectly loaded due to a bug b/159514442 (public bug b/159047832). + // + // This class used to pass "und" string as BCP47 language tag to our jni code, which then + // passes the string as as ICU Locale ID to ICU4C. ICU4C 63 or older version doesn't recognize + // "und" as a valid locale id, and fallback the default locale. The default locale is + // normally selected in the Locale picker in the Settings app by the user and set via + // frameworks. But this class statically cached the ROOT locale data before the + // default locale being set by framework, and without initialization, ICU4C uses en_US_POSIX + // as default locale. Thus, in Q or before, en_US_POSIX data is loaded. + // + // ICU version 64.1 resolved inconsistent behavior of + // "root", "und" and "" (empty) Locale ID which libcore previously relied on, and they are + // recognized correctly as {@link Locale#ROOT} since Android R. This ChangeId gated the change, + // and fallback to the old behavior by checking targetSdkVersion version. + // + // The below javadoc is shown in http://developer.android.com for consumption by app developers. + /** + * Since Android 11, formatter classes, e.g. java.text.SimpleDateFormat, no longer + * provide English data when Locale.ROOT format is requested. Please use + * Locale.ENGLISH to format in English. + * + * Note that Locale.ROOT is used as language/country neutral locale or fallback locale, + * and does not guarantee to represent English locale. + * + * This flag is only for documentation and can't be overridden by app. Please use + * {@code targetSdkVersion} to enable the new behavior. + */ + @ChangeId + @EnabledAfter(targetSdkVersion=29 /* Android Q */) + public static final long USE_REAL_ROOT_LOCALE = 159047832L; + // A cache for the locale-specific data. private static final HashMap<String, LocaleData> localeDataCache = new HashMap<String, LocaleData>(); static { @@ -171,6 +214,25 @@ public final class LocaleData { } /** + * Normally, this utility function is used by secondary cache above {@link LocaleData}, + * because the cache needs a correct key. + * @see #USE_REAL_ROOT_LOCALE + * @return a compatible locale for the bug b/159514442 + */ + public static Locale getCompatibleLocaleForBug159514442(Locale locale) { + if (Locale.ROOT.equals(locale)) { + int targetSdkVersion = VMRuntime.getRuntime().getTargetSdkVersion(); + // Don't use Compatibility.isChangeEnabled(USE_REAL_ROOT_LOCALE) because the app compat + // framework lives in libcore and can depend on this class via various format methods, + // e.g. String.format(). See b/160912695. + if (targetSdkVersion <= 29 /* Android Q */) { + locale = LOCALE_EN_US_POSIX; + } + } + return locale; + } + + /** * Returns a shared LocaleData for the given locale. */ @UnsupportedAppUsage @@ -180,6 +242,8 @@ public final class LocaleData { throw new NullPointerException("locale == null"); } + locale = getCompatibleLocaleForBug159514442(locale); + final String languageTag = locale.toLanguageTag(); synchronized (localeDataCache) { LocaleData localeData = localeDataCache.get(languageTag); diff --git a/luni/src/test/java/libcore/libcore/icu/LocaleDataTest.java b/luni/src/test/java/libcore/libcore/icu/LocaleDataTest.java index 607397d577e..eace2333c98 100644 --- a/luni/src/test/java/libcore/libcore/icu/LocaleDataTest.java +++ b/luni/src/test/java/libcore/libcore/icu/LocaleDataTest.java @@ -16,10 +16,37 @@ package libcore.libcore.icu; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.icu.text.DateTimePatternGenerator; + +import java.text.DateFormatSymbols; +import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; import java.util.Locale; +import java.util.TimeZone; + import libcore.icu.LocaleData; +import libcore.junit.util.SwitchTargetSdkVersionRule; -public class LocaleDataTest extends junit.framework.TestCase { +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class LocaleDataTest { + + @Rule + public TestRule switchTargetSdkVersionRule = SwitchTargetSdkVersionRule.getInstance(); + + @Test public void testAll() throws Exception { // Test that we can get the locale data for all known locales. for (Locale l : Locale.getAvailableLocales()) { @@ -29,6 +56,7 @@ public class LocaleDataTest extends junit.framework.TestCase { } } + @Test public void test_en_US() throws Exception { LocaleData l = LocaleData.get(Locale.US); assertEquals("AM", l.amPm[0]); @@ -57,6 +85,7 @@ public class LocaleDataTest extends junit.framework.TestCase { assertEquals("Tomorrow", l.tomorrow); } + @Test public void test_de_DE() throws Exception { LocaleData l = LocaleData.get(new Locale("de", "DE")); @@ -65,6 +94,7 @@ public class LocaleDataTest extends junit.framework.TestCase { assertEquals("Morgen", l.tomorrow); } + @Test public void test_cs_CZ() throws Exception { LocaleData l = LocaleData.get(new Locale("cs", "CZ")); @@ -77,6 +107,7 @@ public class LocaleDataTest extends junit.framework.TestCase { assertEquals("1", l.tinyStandAloneMonthNames[0]); } + @Test public void test_ko_KR() throws Exception { LocaleData l = LocaleData.get(new Locale("ko", "KR")); @@ -86,6 +117,7 @@ public class LocaleDataTest extends junit.framework.TestCase { assertEquals("내일", l.tomorrow); } + @Test public void test_ru_RU() throws Exception { LocaleData l = LocaleData.get(new Locale("ru", "RU")); @@ -100,6 +132,7 @@ public class LocaleDataTest extends junit.framework.TestCase { } // http://code.google.com/p/android/issues/detail?id=38844 + @Test public void testDecimalFormatSymbols_es() throws Exception { LocaleData es = LocaleData.get(new Locale("es")); assertEquals(',', es.decimalSeparator); @@ -123,6 +156,7 @@ public class LocaleDataTest extends junit.framework.TestCase { } // http://b/7924970 + @Test public void testTimeFormat12And24() throws Exception { LocaleData en_US = LocaleData.get(Locale.US); assertEquals("h:mm a", en_US.timeFormat_hm); @@ -134,6 +168,7 @@ public class LocaleDataTest extends junit.framework.TestCase { } // http://b/26397197 + @Test public void testPatternWithOverride() throws Exception { LocaleData haw = LocaleData.get(new Locale("haw")); assertFalse(haw.shortDateFormat.isEmpty()); @@ -143,7 +178,111 @@ public class LocaleDataTest extends junit.framework.TestCase { * Check that LocaleData.get() does not throw when the input locale is invalid. * http://b/129070579 */ + @Test public void testInvalidLocale() { LocaleData.get(new Locale("invalidLocale")); } + + // Test for b/159514442 when targetSdkVersion == current + @Test + public void test_rootLocale_icu4jConsistency() { + assertRootDataEqualsToTargetLocaleData(Locale.ROOT); + } + + // Test for b/159514442 + @Test + @SwitchTargetSdkVersionRule.TargetSdkVersion(30) + public void test_rootLocale_useRealRootLocaleData() { + assertRootDataEqualsToTargetLocaleData(Locale.ROOT); + + // Regression test as in b/159514442. + SimpleDateFormat df = new SimpleDateFormat("MMM", Locale.ROOT); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("M07", df.format(new Date(1594255915217L))); + } + + // Test for b/159514442 + @Test + @SwitchTargetSdkVersionRule.TargetSdkVersion(29) + public void test_rootLocale_notUseRealRootLocaleData() { + Locale LOCALE_EN_US_POSIX = new Locale("en", "US", "POSIX"); + assertRootDataEqualsToTargetLocaleData(LOCALE_EN_US_POSIX); + + // Regression test as in b/159514442. + SimpleDateFormat df = new SimpleDateFormat("MMM", Locale.ROOT); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Jul", df.format(new Date(1594255915217L))); + } + + private static void assertRootDataEqualsToTargetLocaleData(Locale targetLocale) { + LocaleData localeData = LocaleData.get(Locale.ROOT); + Calendar calendar = Calendar.getInstance(Locale.ROOT); + android.icu.util.Calendar icuCalendar = android.icu.util.Calendar.getInstance(targetLocale); + DateFormatSymbols dateFormatSymbols = DateFormatSymbols.getInstance(Locale.ROOT); + android.icu.text.DateFormatSymbols icuDateFormatSymbols = + android.icu.text.DateFormatSymbols.getInstance(targetLocale); + DecimalFormatSymbols decimalFormatSymbols = DecimalFormatSymbols.getInstance(Locale.ROOT); + android.icu.text.DecimalFormatSymbols icuDecimalFormatSymbols = + android.icu.text.DecimalFormatSymbols.getInstance(targetLocale); + DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(Locale.ROOT); + + assertEquals(localeData.firstDayOfWeek, (Integer) icuCalendar.getFirstDayOfWeek()); + assertEquals(localeData.minimalDaysInFirstWeek, + (Integer) icuCalendar.getMinimalDaysInFirstWeek()); + + assertArrayEquals(localeData.amPm, icuDateFormatSymbols.getAmPmStrings()); + assertArrayEquals(localeData.eras, icuDateFormatSymbols.getEras()); + assertArrayEquals(localeData.longMonthNames, icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.WIDE)); + assertArrayEquals(localeData.tinyMonthNames, icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.NARROW)); + assertArrayEquals(localeData.shortMonthNames, icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.ABBREVIATED)); + assertArrayEquals(localeData.longStandAloneMonthNames, icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.STANDALONE, android.icu.text.DateFormatSymbols.WIDE)); + assertArrayEquals(localeData.tinyStandAloneMonthNames, icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.STANDALONE, android.icu.text.DateFormatSymbols.NARROW)); + assertArrayEquals(localeData.shortStandAloneMonthNames, icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.STANDALONE, + android.icu.text.DateFormatSymbols.ABBREVIATED)); + assertArrayEquals(localeData.longWeekdayNames, icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.WIDE)); + assertArrayEquals(localeData.tinyWeekdayNames, icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.NARROW)); + assertArrayEquals(localeData.shortWeekdayNames, icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.ABBREVIATED)); + assertArrayEquals(localeData.longStandAloneWeekdayNames, icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.STANDALONE, android.icu.text.DateFormatSymbols.WIDE)); + assertArrayEquals(localeData.tinyStandAloneWeekdayNames, icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.STANDALONE, android.icu.text.DateFormatSymbols.NARROW)); + assertArrayEquals(localeData.shortStandAloneWeekdayNames, icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.STANDALONE, + android.icu.text.DateFormatSymbols.ABBREVIATED)); + + // ICU DecimalFormatSymbols has data slightly different from LocaleData, but infinity is known + // to be the same, but caused the bug b/68318492 in old Android version. + assertEquals(localeData.infinity, icuDecimalFormatSymbols.getInfinity()); + assertEquals(decimalFormatSymbols.getInfinity(), icuDecimalFormatSymbols.getInfinity()); + + assertEquals(localeData.timeFormat_Hm, dtpg.getBestPattern("Hm")); + assertEquals(localeData.timeFormat_hm, dtpg.getBestPattern("hm")); + assertEquals(localeData.timeFormat_Hms, dtpg.getBestPattern("Hms")); + assertEquals(localeData.timeFormat_hms, dtpg.getBestPattern("hms")); + + // Explicitly test Calendar and DateFormatSymbols here because they are known to + // cache some part of LocaleData. + assertEquals(calendar.getFirstDayOfWeek(), icuCalendar.getFirstDayOfWeek()); + assertEquals(calendar.getMinimalDaysInFirstWeek(), icuCalendar.getMinimalDaysInFirstWeek()); + assertArrayEquals(dateFormatSymbols.getAmPmStrings(), icuDateFormatSymbols.getAmPmStrings()); + assertArrayEquals(dateFormatSymbols.getEras(), icuDateFormatSymbols.getEras()); + + assertArrayEquals(dateFormatSymbols.getMonths(), icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.WIDE)); + assertArrayEquals(dateFormatSymbols.getShortMonths(), icuDateFormatSymbols.getMonths( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.ABBREVIATED)); + assertArrayEquals(dateFormatSymbols.getWeekdays(), icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.WIDE)); + assertArrayEquals(dateFormatSymbols.getShortWeekdays(), icuDateFormatSymbols.getWeekdays( + android.icu.text.DateFormatSymbols.FORMAT, android.icu.text.DateFormatSymbols.ABBREVIATED)); + } } diff --git a/ojluni/src/main/java/java/text/DateFormatSymbols.java b/ojluni/src/main/java/java/text/DateFormatSymbols.java index 97dc528e513..5216928dc84 100644 --- a/ojluni/src/main/java/java/text/DateFormatSymbols.java +++ b/ojluni/src/main/java/java/text/DateFormatSymbols.java @@ -421,25 +421,27 @@ public class DateFormatSymbols implements Serializable, Cloneable { // BEGIN Android-changed: Replace getProviderInstance() with getCachedInstance(). // Android removed support for DateFormatSymbolsProviders, but still caches DFS. + // App compat change for b/159514442. /** * Returns a cached DateFormatSymbols if it's found in the * cache. Otherwise, this method returns a newly cached instance * for the given locale. */ private static DateFormatSymbols getCachedInstance(Locale locale) { - SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale); + Locale cacheKey = LocaleData.getCompatibleLocaleForBug159514442(locale); + SoftReference<DateFormatSymbols> ref = cachedInstances.get(cacheKey); DateFormatSymbols dfs; if (ref == null || (dfs = ref.get()) == null) { dfs = new DateFormatSymbols(locale); ref = new SoftReference<>(dfs); - SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(locale, ref); + SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(cacheKey, ref); if (x != null) { DateFormatSymbols y = x.get(); if (y != null) { dfs = y; } else { // Replace the empty SoftReference with ref. - cachedInstances.put(locale, ref); + cachedInstances.put(cacheKey, ref); } } } @@ -818,7 +820,9 @@ public class DateFormatSymbols implements Serializable, Cloneable { * appropriate LocaleData object. Note: zoneStrings isn't initialized in this method. */ private void initializeData(Locale locale) { - SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale); + // Android-changed: App compat change for b/159514442. + Locale cacheKey = LocaleData.getCompatibleLocaleForBug159514442(locale); + SoftReference<DateFormatSymbols> ref = cachedInstances.get(cacheKey); DateFormatSymbols dfs; // Android-changed: invert cache presence check to simplify code flow. if (ref != null && (dfs = ref.get()) != null) { diff --git a/ojluni/src/main/java/java/util/Calendar.java b/ojluni/src/main/java/java/util/Calendar.java index 3a0343b8c84..7093533484b 100644 --- a/ojluni/src/main/java/java/util/Calendar.java +++ b/ojluni/src/main/java/java/util/Calendar.java @@ -3376,6 +3376,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca */ private void setWeekCountData(Locale desiredLocale) { + desiredLocale = LocaleData.getCompatibleLocaleForBug159514442(desiredLocale); /* try to get the Locale data from the cache */ int[] data = cachedLocaleData.get(desiredLocale); if (data == null) { /* cache miss */ |