Human-readable XML on Ravenwood.

We had temporarily been using the binary XML implementation in all
situations, but this change brings in the existing `kxml2-android`
library to offer reading/writing of human-readable XML.  We also
use the upstream SAX parser to offer an event-based parser.

In all cases we carefully keep the existing XML libraries in place
untouched on physical devices to avoid regressions.

Bug: 324417456
Test: atest CtsUtilTestCasesRavenwood
Test: atest FrameworksCoreTestsRavenwood
Test: atest FrameworksUtilTestsRavenwood
Change-Id: I2effc7fca5c586968658073f9652d13cf50a2c66
diff --git a/Ravenwood.bp b/Ravenwood.bp
index d13c4d7..d9db612 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -94,14 +94,19 @@
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     libs: [
-        "framework-minus-apex.ravenwood",
-        "hoststubgen-helper-runtime.ravenwood",
-        "hoststubgen-helper-framework-runtime.ravenwood",
+        // Prefixed with "200" to ensure it's sorted early in Tradefed classpath
+        // so that we provide a concrete implementation before Mainline stubs
+        "200-kxml2-android",
         "all-updatable-modules-system-stubs",
+        "android.test.mock.ravenwood",
+        "framework-minus-apex.ravenwood",
+        "hoststubgen-helper-framework-runtime.ravenwood",
+        "hoststubgen-helper-runtime.ravenwood",
+
+        // Provide runtime versions of utils linked in below
         "junit",
         "truth",
         "ravenwood-junit-impl",
-        "android.test.mock.ravenwood",
         "mockito-ravenwood-prebuilt",
         "inline-mockito-ravenwood-prebuilt",
     ],
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index ec6e90b..50d419f 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -53,9 +53,12 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
+import javax.xml.parsers.SAXParserFactory;
+
 /**
  * XML utility methods.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Xml {
     private Xml() {}
 
@@ -73,8 +76,33 @@
      *
      * @hide
      */
-    public static final boolean ENABLE_BINARY_DEFAULT = SystemProperties
-            .getBoolean("persist.sys.binary_xml", true);
+    public static final boolean ENABLE_BINARY_DEFAULT = shouldEnableBinaryDefault();
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean shouldEnableBinaryDefault() {
+        return SystemProperties.getBoolean("persist.sys.binary_xml", true);
+    }
+
+    private static boolean shouldEnableBinaryDefault$ravenwood() {
+        return true;
+    }
+
+    /**
+     * Feature flag: when set, {@link #resolvePullParser(InputStream)}} will attempt to sniff
+     * using {@code pread} optimization.
+     *
+     * @hide
+     */
+    public static final boolean ENABLE_RESOLVE_OPTIMIZATIONS = shouldEnableResolveOptimizations();
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean shouldEnableResolveOptimizations() {
+        return true;
+    }
+
+    private static boolean shouldEnableResolveOptimizations$ravenwood() {
+        return false;
+    }
 
     /**
      * Parses the given xml string and fires events on the given SAX handler.
@@ -82,7 +110,7 @@
     public static void parse(String xml, ContentHandler contentHandler)
             throws SAXException {
         try {
-            XMLReader reader = XmlObjectFactory.newXMLReader();
+            XMLReader reader = newXMLReader();
             reader.setContentHandler(contentHandler);
             reader.parse(new InputSource(new StringReader(xml)));
         } catch (IOException e) {
@@ -96,7 +124,7 @@
      */
     public static void parse(Reader in, ContentHandler contentHandler)
             throws IOException, SAXException {
-        XMLReader reader = XmlObjectFactory.newXMLReader();
+        XMLReader reader = newXMLReader();
         reader.setContentHandler(contentHandler);
         reader.parse(new InputSource(in));
     }
@@ -107,7 +135,7 @@
      */
     public static void parse(InputStream in, Encoding encoding,
             ContentHandler contentHandler) throws IOException, SAXException {
-        XMLReader reader = XmlObjectFactory.newXMLReader();
+        XMLReader reader = newXMLReader();
         reader.setContentHandler(contentHandler);
         InputSource source = new InputSource(in);
         source.setEncoding(encoding.expatName);
@@ -120,19 +148,26 @@
     @android.ravenwood.annotation.RavenwoodReplace
     public static XmlPullParser newPullParser() {
         try {
-            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            XmlPullParser parser = newXmlPullParser();
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
             return parser;
         } catch (XmlPullParserException e) {
-            throw new AssertionError();
+            throw new AssertionError(e);
         }
     }
 
     /** @hide */
     public static XmlPullParser newPullParser$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlPullParser();
+        try {
+            // Prebuilt kxml2-android does not support FEATURE_PROCESS_DOCDECL, so omit here;
+            // it's quite rare and all tests are passing
+            XmlPullParser parser = newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError(e);
+        }
     }
 
     /**
@@ -145,17 +180,10 @@
      * @hide
      */
     @SuppressWarnings("AndroidFrameworkEfficientXml")
-    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser newFastPullParser() {
         return XmlUtils.makeTyped(newPullParser());
     }
 
-    /** @hide */
-    public static TypedXmlPullParser newFastPullParser$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlPullParser();
-    }
-
     /**
      * Creates a new {@link XmlPullParser} that reads XML documents using a
      * custom binary wire protocol which benchmarking has shown to be 8.5x
@@ -189,11 +217,10 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
             throws IOException {
         final byte[] magic = new byte[4];
-        if (in instanceof FileInputStream) {
+        if (ENABLE_RESOLVE_OPTIMIZATIONS && in instanceof FileInputStream) {
             try {
                 Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
             } catch (ErrnoException e) {
@@ -222,31 +249,11 @@
         return xml;
     }
 
-    /** @hide */
-    public static @NonNull TypedXmlPullParser resolvePullParser$ravenwood(@NonNull InputStream in)
-            throws IOException {
-        // TODO: remove once we're linking against libcore
-        final TypedXmlPullParser xml = new BinaryXmlPullParser();
-        try {
-            xml.setInput(in, StandardCharsets.UTF_8.name());
-        } catch (XmlPullParserException e) {
-            throw new IOException(e);
-        }
-        return xml;
-    }
-
     /**
      * Creates a new xml serializer.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
     public static XmlSerializer newSerializer() {
-        return XmlObjectFactory.newXmlSerializer();
-    }
-
-    /** @hide */
-    public static XmlSerializer newSerializer$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlSerializer();
+        return newXmlSerializer();
     }
 
     /**
@@ -259,17 +266,10 @@
      * @hide
      */
     @SuppressWarnings("AndroidFrameworkEfficientXml")
-    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlSerializer newFastSerializer() {
         return XmlUtils.makeTyped(new FastXmlSerializer());
     }
 
-    /** @hide */
-    public static @NonNull TypedXmlSerializer newFastSerializer$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlSerializer();
-    }
-
     /**
      * Creates a new {@link XmlSerializer} that writes XML documents using a
      * custom binary wire protocol which benchmarking has shown to be 4.4x
@@ -334,7 +334,6 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static void copy(@NonNull XmlPullParser in, @NonNull XmlSerializer out)
             throws XmlPullParserException, IOException {
         // Some parsers may have already consumed the event that starts the
@@ -394,7 +393,6 @@
      * unsupported, which can confuse serializers. This method normalizes empty
      * strings to be {@code null}.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     private static @Nullable String normalizeNamespace(@Nullable String namespace) {
         if (namespace == null || namespace.isEmpty()) {
             return null;
@@ -457,4 +455,45 @@
                 ? (AttributeSet) parser
                 : new XmlPullAttributes(parser);
     }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static @NonNull XmlSerializer newXmlSerializer() {
+        return XmlObjectFactory.newXmlSerializer();
+    }
+
+    private static @NonNull XmlSerializer newXmlSerializer$ravenwood() {
+        try {
+            return XmlPullParserFactory.newInstance().newSerializer();
+        } catch (XmlPullParserException e) {
+            throw new UnsupportedOperationException(e);
+        }
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static @NonNull XmlPullParser newXmlPullParser() {
+        return XmlObjectFactory.newXmlPullParser();
+    }
+
+    private static @NonNull XmlPullParser newXmlPullParser$ravenwood() {
+        try {
+            return XmlPullParserFactory.newInstance().newPullParser();
+        } catch (XmlPullParserException e) {
+            throw new UnsupportedOperationException(e);
+        }
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static @NonNull XMLReader newXMLReader() {
+        return XmlObjectFactory.newXMLReader();
+    }
+
+    private static @NonNull XMLReader newXMLReader$ravenwood() {
+        try {
+            final SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            return factory.newSAXParser().getXMLReader();
+        } catch (Exception e) {
+            throw new UnsupportedOperationException(e);
+        }
+    }
 }
diff --git a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
index 7723d58..f91172d 100644
--- a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
@@ -45,7 +45,6 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Xml.class)
 public class FastXmlSerializerTest {
     private static final String TAG = "FastXmlSerializerTest";
 
@@ -146,6 +145,7 @@
 
     @Test
     @LargeTest
+    @IgnoreUnderRavenwood(reason = "Long test runtime")
     public void testAllCharacters() throws Exception {
         boolean ok = true;
         for (int i = 0; i < 0xffff; i++) {
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 1ac69f6..35ce481 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -58,11 +58,14 @@
     visibility: ["//visibility:public"],
 }
 
-java_host_for_device {
-    name: "core-xml-for-device",
-    libs: [
-        "core-xml-for-host",
+java_library {
+    // Prefixed with "200" to ensure it's sorted early in Tradefed classpath
+    // so that we provide a concrete implementation before Mainline stubs
+    name: "200-kxml2-android",
+    static_libs: [
+        "kxml2-android",
     ],
+    visibility: ["//frameworks/base"],
 }
 
 java_host_for_device {
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 0229611..2eef0cd 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -89,6 +89,7 @@
 
 # Internals
 class com.android.internal.util.FileRotator stubclass
+class com.android.internal.util.FastXmlSerializer stubclass
 class com.android.internal.util.HexDump stubclass
 class com.android.internal.util.MessageUtils stubclass
 class com.android.internal.util.Preconditions stubclass
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 57bcc04..c6dd29c 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -180,7 +180,6 @@
         "framework-minus-apex.ravenwood",
     ],
     static_libs: [
-        "core-xml-for-device",
         "hoststubgen-helper-libcore-runtime.ravenwood",
     ],
 }