Merge RP1A.200720.012
Change-Id: If2e804d6e08c1f6c15257f93f91303169cb0bb9e
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 2191b0c..85899f3 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 @@
     }
 
     // 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 @@
                 fail();
             }
         }
+        System.gc();
     }
 }
diff --git a/luni/src/main/java/android/compat/Compatibility.java b/luni/src/main/java/android/compat/Compatibility.java
index e5d7cbb..9b4b1af 100644
--- a/luni/src/main/java/android/compat/Compatibility.java
+++ b/luni/src/main/java/android/compat/Compatibility.java
@@ -121,13 +121,13 @@
         }
         @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 ea6fe6f..12b978de 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 @@
  */
 @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 @@
     }
 
     /**
+     * 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 @@
             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 607397d..eace233 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 java.util.Locale;
-import libcore.icu.LocaleData;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-public class LocaleDataTest extends junit.framework.TestCase {
+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;
+
+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 @@
     }
   }
 
+  @Test
   public void test_en_US() throws Exception {
     LocaleData l = LocaleData.get(Locale.US);
     assertEquals("AM", l.amPm[0]);
@@ -57,6 +85,7 @@
     assertEquals("Tomorrow", l.tomorrow);
   }
 
+  @Test
   public void test_de_DE() throws Exception {
     LocaleData l = LocaleData.get(new Locale("de", "DE"));
 
@@ -65,6 +94,7 @@
     assertEquals("Morgen", l.tomorrow);
   }
 
+  @Test
   public void test_cs_CZ() throws Exception {
     LocaleData l = LocaleData.get(new Locale("cs", "CZ"));
 
@@ -77,6 +107,7 @@
     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 @@
     assertEquals("내일", l.tomorrow);
   }
 
+  @Test
   public void test_ru_RU() throws Exception {
     LocaleData l = LocaleData.get(new Locale("ru", "RU"));
 
@@ -100,6 +132,7 @@
   }
 
   // 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 @@
   }
 
   // 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 @@
   }
 
   // http://b/26397197
+  @Test
   public void testPatternWithOverride() throws Exception {
     LocaleData haw = LocaleData.get(new Locale("haw"));
     assertFalse(haw.shortDateFormat.isEmpty());
@@ -143,7 +178,111 @@
    * 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 97dc528..5216928 100644
--- a/ojluni/src/main/java/java/text/DateFormatSymbols.java
+++ b/ojluni/src/main/java/java/text/DateFormatSymbols.java
@@ -421,25 +421,27 @@
 
     // 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 @@
      * 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 3a0343b..7093533 100644
--- a/ojluni/src/main/java/java/util/Calendar.java
+++ b/ojluni/src/main/java/java/util/Calendar.java
@@ -3376,6 +3376,7 @@
      */
     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 */