aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2020-07-16 14:00:21 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2020-07-16 14:00:21 +0000
commitad18336b2ee5d3546a2eef27ea62d8ebb448b7b5 (patch)
treef1e2fd205c2fb5df0ef2957b5ea2b7977986d7a7
parentb5da79ac8e46033f8a23620b9a2142c9c208b1ab (diff)
parentfe163dc550e4182ab9785a48afda9e1bac98f6f0 (diff)
downloadlibcore-ad18336b2ee5d3546a2eef27ea62d8ebb448b7b5.tar.gz
Snap for 6684105 from fe163dc550e4182ab9785a48afda9e1bac98f6f0 to rvc-beta3-releaseandroid-r-beta-3r-beta-3
Change-Id: I12612ad21e54113ab629a3d7afcc46acc80a7f2a
-rw-r--r--harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ScannerTest.java5
-rw-r--r--luni/src/main/java/android/compat/Compatibility.java8
-rw-r--r--luni/src/main/java/libcore/icu/LocaleData.java64
-rw-r--r--luni/src/test/java/libcore/libcore/icu/LocaleDataTest.java141
-rw-r--r--ojluni/src/main/java/java/text/DateFormatSymbols.java12
-rw-r--r--ojluni/src/main/java/java/util/Calendar.java1
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 */