Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 42bd7605 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

"Resolve" both human-readable and binary XML.

Recent refactorings have pivoted many XML users across the system
to call resolveSerializer() and resolvePullParser(), which gives us
an easy path to pivot between human-readable XML and our binary XML
wire format.

This change introduces a feature flag (still disabled by default)
for emitting binary XML by default when serializing.  It also adds
logic to automatically select the relevant parser by sniffing the
magic file header.

This is the key to our upgrade strategy: we'll gladly parse
human-readable XML from old devices, or binary XML from new devices.

Bug: 171832118
Test: atest FrameworksCoreTests:android.util.XmlTest
Test: atest FrameworksCoreTests:android.util.BinaryXmlTest
Change-Id: I14fdab9a6667819db38116a8ce126f5f2a5a04df
parent a01f7505
Loading
Loading
Loading
Loading
+38 −5
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.util;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.system.ErrnoException;
import android.system.Os;

import com.android.internal.util.BinaryXmlPullParser;
import com.android.internal.util.BinaryXmlSerializer;
@@ -35,7 +37,7 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -43,6 +45,7 @@ import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * XML utility methods.
@@ -58,6 +61,12 @@ public class Xml {
     */
    public static String FEATURE_RELAXED = "http://xmlpull.org/v1/doc/features.html#relaxed";

    /**
     * Feature flag: when set, {@link #resolveSerializer(OutputStream)} will
     * emit binary XML by default.
     */
    private static final boolean ENABLE_BINARY_DEFAULT = false;

    /**
     * Parses the given xml string and fires events on the given SAX handler.
     */
@@ -151,8 +160,28 @@ public class Xml {
     */
    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
            throws IOException {
        // TODO: add support for binary format
        final TypedXmlPullParser xml = newFastPullParser();
        final byte[] magic = new byte[4];
        if (in instanceof FileInputStream) {
            try {
                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
            } catch (ErrnoException e) {
                throw e.rethrowAsIOException();
            }
        } else {
            if (!in.markSupported()) {
                in = new BufferedInputStream(in);
            }
            in.mark(4);
            in.read(magic);
            in.reset();
        }

        final TypedXmlPullParser xml;
        if (Arrays.equals(magic, BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0)) {
            xml = newBinaryPullParser();
        } else {
            xml = newFastPullParser();
        }
        try {
            xml.setInput(in, StandardCharsets.UTF_8.name());
        } catch (XmlPullParserException e) {
@@ -209,8 +238,12 @@ public class Xml {
     */
    public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out)
            throws IOException {
        // TODO: add support for binary format
        final TypedXmlSerializer xml = newFastSerializer();
        final TypedXmlSerializer xml;
        if (ENABLE_BINARY_DEFAULT) {
            xml = newBinarySerializer();
        } else {
            xml = newFastSerializer();
        }
        xml.setOutput(out, StandardCharsets.UTF_8.name());
        return xml;
    }
+1 −1
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer {
     * {@code ABX_}, representing "Android Binary XML." The final byte is a
     * version number which may be incremented as the protocol changes.
     */
    static final byte[] PROTOCOL_MAGIC_VERSION_0 = new byte[] { 0x41, 0x42, 0x58, 0x00 };
    public static final byte[] PROTOCOL_MAGIC_VERSION_0 = new byte[] { 0x41, 0x42, 0x58, 0x00 };

    /**
     * Internal token which represents an attribute associated with the most
+61 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.util.XmlTest.assertNext;
import static android.util.XmlTest.buildPersistableBundle;
import static android.util.XmlTest.doPersistableBundleRead;
import static android.util.XmlTest.doPersistableBundleWrite;
import static android.util.XmlTest.doVerifyRead;
import static android.util.XmlTest.doVerifyWrite;

import static org.junit.Assert.assertEquals;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -33,6 +35,11 @@ import org.junit.runner.RunWith;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

@RunWith(AndroidJUnit4.class)
@@ -96,4 +103,58 @@ public class BinaryXmlTest {
        final PersistableBundle actual = doPersistableBundleRead(secondIn, os.toByteArray());
        assertEquals(expected.toString(), actual.toString());
    }

    @Test
    public void testResolve_File() throws Exception {
        {
            final File file = File.createTempFile("fast", ".xml");
            try (OutputStream os = new FileOutputStream(file)) {
                TypedXmlSerializer xml = Xml.newFastSerializer();
                xml.setOutput(os, StandardCharsets.UTF_8.name());
                doVerifyWrite(xml);
            }
            try (InputStream is = new FileInputStream(file)) {
                doVerifyRead(Xml.resolvePullParser(is));
            }
        }
        {
            final File file = File.createTempFile("binary", ".xml");
            try (OutputStream os = new FileOutputStream(file)) {
                TypedXmlSerializer xml = Xml.newBinarySerializer();
                xml.setOutput(os, StandardCharsets.UTF_8.name());
                doVerifyWrite(xml);
            }
            try (InputStream is = new FileInputStream(file)) {
                doVerifyRead(Xml.resolvePullParser(is));
            }
        }
    }

    @Test
    public void testResolve_Memory() throws Exception {
        {
            final byte[] data;
            try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
                TypedXmlSerializer xml = Xml.newFastSerializer();
                xml.setOutput(os, StandardCharsets.UTF_8.name());
                doVerifyWrite(xml);
                data = os.toByteArray();
            }
            try (InputStream is = new ByteArrayInputStream(data)) {
                doVerifyRead(Xml.resolvePullParser(is));
            }
        }
        {
            final byte[] data;
            try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
                TypedXmlSerializer xml = Xml.newBinarySerializer();
                xml.setOutput(os, StandardCharsets.UTF_8.name());
                doVerifyWrite(xml);
                data = os.toByteArray();
            }
            try (InputStream is = new ByteArrayInputStream(data)) {
                doVerifyRead(Xml.resolvePullParser(is));
            }
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -205,7 +205,7 @@ public class XmlTest {
    private static final byte[] TEST_BYTES = new byte[] { 0, 1, 2, 3, 4, 3, 2, 1, 0 };
    private static final byte[] TEST_BYTES_EMPTY = new byte[0];

    private static void doVerifyWrite(TypedXmlSerializer out) throws Exception {
    static void doVerifyWrite(TypedXmlSerializer out) throws Exception {
        out.startDocument(StandardCharsets.UTF_8.name(), true);
        out.startTag(null, "one");
        {
@@ -244,7 +244,7 @@ public class XmlTest {
        out.endDocument();
    }

    private static void doVerifyRead(TypedXmlPullParser in) throws Exception {
    static void doVerifyRead(TypedXmlPullParser in) throws Exception {
        assertEquals(START_DOCUMENT, in.getEventType());
        assertNext(in, START_TAG, "one", 1);
        {