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

Commit b5856365 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Progress towards efficient XML serialization.

We've identified that XML writing and reading uses roughly 1.5% of
all system_server CPU, and can generate many temporary objects.

To set the stage for some long-term improvements, this change
introduces new TypedXmlSerializer and TypedXmlPullParser interfaces
which offer efficient access to primitive attributes and text.

This change also updates XmlUtils to redirect primitive operations
through these new interfaces.

Bug: 171832118
Test: atest FrameworksCoreTests:android.util.XmlTest
Change-Id: I5ba3ad87cf79ca10705a96b98855164a27fee021
parent 162f86dd
Loading
Loading
Loading
Loading
+186 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import android.annotation.NonNull;
import android.annotation.Nullable;

import org.xmlpull.v1.XmlPullParser;

import java.io.IOException;

/**
 * Specialization of {@link XmlPullParser} which adds explicit methods to
 * support consistent and efficient conversion of primitive data types.
 *
 * @hide
 */
public interface TypedXmlPullParser extends XmlPullParser {
    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, or
     *         {@code null} if malformed or undefined
     */
    @Nullable byte[] getAttributeBytesHex(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, or
     *         {@code null} if malformed or undefined
     */
    @Nullable byte[] getAttributeBytesBase64(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    int getAttributeInt(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    int getAttributeIntHex(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    long getAttributeLong(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    long getAttributeLongHex(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    float getAttributeFloat(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    double getAttributeDouble(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}
     * @throws IOException if the value is malformed or undefined
     */
    boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name)
            throws IOException;

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default int getAttributeInt(@Nullable String namespace, @NonNull String name,
            int defaultValue) {
        try {
            return getAttributeInt(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default int getAttributeIntHex(@Nullable String namespace, @NonNull String name,
            int defaultValue) {
        try {
            return getAttributeIntHex(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default long getAttributeLong(@Nullable String namespace, @NonNull String name,
            long defaultValue) {
        try {
            return getAttributeLong(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default long getAttributeLongHex(@Nullable String namespace, @NonNull String name,
            long defaultValue) {
        try {
            return getAttributeLongHex(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default float getAttributeFloat(@Nullable String namespace, @NonNull String name,
            float defaultValue) {
        try {
            return getAttributeFloat(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default double getAttributeDouble(@Nullable String namespace, @NonNull String name,
            double defaultValue) {
        try {
            return getAttributeDouble(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    /**
     * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
     *         default value if the value is malformed or undefined
     */
    default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name,
            boolean defaultValue) {
        try {
            return getAttributeBoolean(namespace, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }
}
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import android.annotation.NonNull;
import android.annotation.Nullable;

import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;

/**
 * Specialization of {@link XmlSerializer} which adds explicit methods to
 * support consistent and efficient conversion of primitive data types.
 *
 * @hide
 */
public interface TypedXmlSerializer extends XmlSerializer {
    /**
     * Functionally equivalent to {@link #attribute(String, String, String)} but
     * with the additional signal that the given value is a candidate for being
     * canonicalized, similar to {@link String#intern()}.
     */
    @NonNull XmlSerializer attributeInterned(@Nullable String namespace, @NonNull String name,
            @Nullable String value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeBytesHex(@Nullable String namespace, @NonNull String name,
            byte[] value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeBytesBase64(@Nullable String namespace, @NonNull String name,
            byte[] value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeInt(@Nullable String namespace, @NonNull String name,
            int value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeIntHex(@Nullable String namespace, @NonNull String name,
            int value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeLong(@Nullable String namespace, @NonNull String name,
            long value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeLongHex(@Nullable String namespace, @NonNull String name,
            long value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeFloat(@Nullable String namespace, @NonNull String name,
            float value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeDouble(@Nullable String namespace, @NonNull String name,
            double value) throws IOException;

    /**
     * Encode the given strongly-typed value and serialize using
     * {@link #attribute(String, String, String)}.
     */
    @NonNull XmlSerializer attributeBoolean(@Nullable String namespace, @NonNull String name,
            boolean value) throws IOException;
}
+83 −0
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package android.util;

import android.annotation.NonNull;

import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;

import libcore.util.XmlObjectFactory;

import org.xml.sax.ContentHandler;
@@ -26,11 +31,15 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

/**
 * XML utility methods.
@@ -98,6 +107,45 @@ public class Xml {
        }
    }

    /**
     * Creates a new {@link TypedXmlPullParser} which is optimized for use
     * inside the system, typically by supporting only a basic set of features.
     * <p>
     * In particular, the returned parser does not support namespaces, prefixes,
     * properties, or options.
     *
     * @hide
     */
    public static @NonNull TypedXmlPullParser newFastPullParser() {
        return XmlUtils.makeTyped(newPullParser());
    }

    /**
     * Creates a new {@link XmlPullParser} which is optimized for use inside the
     * system, typically by supporting only a basic set of features.
     * <p>
     * This returned instance may be configured to read using an efficient
     * binary format instead of a human-readable text format, depending on
     * device feature flags.
     * <p>
     * To ensure that both formats are detected and transparently handled
     * correctly, you must shift to using both {@link #resolveSerializer} and
     * {@link #resolvePullParser}.
     *
     * @hide
     */
    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
            throws IOException {
        // TODO: add support for binary format
        final TypedXmlPullParser xml = newFastPullParser();
        try {
            xml.setInput(in, StandardCharsets.UTF_8.name());
        } catch (XmlPullParserException e) {
            throw new IOException(e);
        }
        return xml;
    }

    /**
     * Creates a new xml serializer.
     */
@@ -105,6 +153,41 @@ public class Xml {
        return XmlObjectFactory.newXmlSerializer();
    }

    /**
     * Creates a new {@link XmlSerializer} which is optimized for use inside the
     * system, typically by supporting only a basic set of features.
     * <p>
     * In particular, the returned parser does not support namespaces, prefixes,
     * properties, or options.
     *
     * @hide
     */
    public static @NonNull TypedXmlSerializer newFastSerializer() {
        return XmlUtils.makeTyped(new FastXmlSerializer());
    }

    /**
     * Creates a new {@link XmlSerializer} which is optimized for use inside the
     * system, typically by supporting only a basic set of features.
     * <p>
     * This returned instance may be configured to write using an efficient
     * binary format instead of a human-readable text format, depending on
     * device feature flags.
     * <p>
     * To ensure that both formats are detected and transparently handled
     * correctly, you must shift to using both {@link #resolveSerializer} and
     * {@link #resolvePullParser}.
     *
     * @hide
     */
    public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out)
            throws IOException {
        // TODO: add support for binary format
        final TypedXmlSerializer xml = newFastSerializer();
        xml.setOutput(out, StandardCharsets.UTF_8.name());
        return xml;
    }

    /**
     * Supported character encodings.
     */
+189 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.util;

import android.annotation.NonNull;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Objects;

/**
 * Wrapper which delegates all calls through to the given {@link XmlPullParser}.
 */
public class XmlPullParserWrapper implements XmlPullParser {
    private final XmlPullParser mWrapped;

    public XmlPullParserWrapper(@NonNull XmlPullParser wrapped) {
        mWrapped = Objects.requireNonNull(wrapped);
    }

    public void setFeature(String name, boolean state) throws XmlPullParserException {
        mWrapped.setFeature(name, state);
    }

    public boolean getFeature(String name) {
        return mWrapped.getFeature(name);
    }

    public void setProperty(String name, Object value) throws XmlPullParserException {
        mWrapped.setProperty(name, value);
    }

    public Object getProperty(String name) {
        return mWrapped.getProperty(name);
    }

    public void setInput(Reader in) throws XmlPullParserException {
        mWrapped.setInput(in);
    }

    public void setInput(InputStream inputStream, String inputEncoding)
            throws XmlPullParserException {
        mWrapped.setInput(inputStream, inputEncoding);
    }

    public String getInputEncoding() {
        return mWrapped.getInputEncoding();
    }

    public void defineEntityReplacementText(String entityName, String replacementText)
            throws XmlPullParserException {
        mWrapped.defineEntityReplacementText(entityName, replacementText);
    }

    public int getNamespaceCount(int depth) throws XmlPullParserException {
        return mWrapped.getNamespaceCount(depth);
    }

    public String getNamespacePrefix(int pos) throws XmlPullParserException {
        return mWrapped.getNamespacePrefix(pos);
    }

    public String getNamespaceUri(int pos) throws XmlPullParserException {
        return mWrapped.getNamespaceUri(pos);
    }

    public String getNamespace(String prefix) {
        return mWrapped.getNamespace(prefix);
    }

    public int getDepth() {
        return mWrapped.getDepth();
    }

    public String getPositionDescription() {
        return mWrapped.getPositionDescription();
    }

    public int getLineNumber() {
        return mWrapped.getLineNumber();
    }

    public int getColumnNumber() {
        return mWrapped.getColumnNumber();
    }

    public boolean isWhitespace() throws XmlPullParserException {
        return mWrapped.isWhitespace();
    }

    public String getText() {
        return mWrapped.getText();
    }

    public char[] getTextCharacters(int[] holderForStartAndLength) {
        return mWrapped.getTextCharacters(holderForStartAndLength);
    }

    public String getNamespace() {
        return mWrapped.getNamespace();
    }

    public String getName() {
        return mWrapped.getName();
    }

    public String getPrefix() {
        return mWrapped.getPrefix();
    }

    public boolean isEmptyElementTag() throws XmlPullParserException {
        return mWrapped.isEmptyElementTag();
    }

    public int getAttributeCount() {
        return mWrapped.getAttributeCount();
    }

    public String getAttributeNamespace(int index) {
        return mWrapped.getAttributeNamespace(index);
    }

    public String getAttributeName(int index) {
        return mWrapped.getAttributeName(index);
    }

    public String getAttributePrefix(int index) {
        return mWrapped.getAttributePrefix(index);
    }

    public String getAttributeType(int index) {
        return mWrapped.getAttributeType(index);
    }

    public boolean isAttributeDefault(int index) {
        return mWrapped.isAttributeDefault(index);
    }

    public String getAttributeValue(int index) {
        return mWrapped.getAttributeValue(index);
    }

    public String getAttributeValue(String namespace, String name) {
        return mWrapped.getAttributeValue(namespace, name);
    }

    public int getEventType() throws XmlPullParserException {
        return mWrapped.getEventType();
    }

    public int next() throws XmlPullParserException, IOException {
        return mWrapped.next();
    }

    public int nextToken() throws XmlPullParserException, IOException {
        return mWrapped.nextToken();
    }

    public void require(int type, String namespace, String name)
            throws XmlPullParserException, IOException {
        mWrapped.require(type, namespace, name);
    }

    public String nextText() throws XmlPullParserException, IOException {
        return mWrapped.nextText();
    }

    public int nextTag() throws XmlPullParserException, IOException {
        return mWrapped.nextTag();
    }
}
+140 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.util;

import android.annotation.NonNull;

import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Objects;

/**
 * Wrapper which delegates all calls through to the given {@link XmlSerializer}.
 */
public class XmlSerializerWrapper {
    private final XmlSerializer mWrapped;

    public XmlSerializerWrapper(@NonNull XmlSerializer wrapped) {
        mWrapped = Objects.requireNonNull(wrapped);
    }

    public void setFeature(String name, boolean state) {
        mWrapped.setFeature(name, state);
    }

    public boolean getFeature(String name) {
        return mWrapped.getFeature(name);
    }

    public void setProperty(String name, Object value) {
        mWrapped.setProperty(name, value);
    }

    public Object getProperty(String name) {
        return mWrapped.getProperty(name);
    }

    public void setOutput(OutputStream os, String encoding) throws IOException {
        mWrapped.setOutput(os, encoding);
    }

    public void setOutput(Writer writer)
            throws IOException, IllegalArgumentException, IllegalStateException {
        mWrapped.setOutput(writer);
    }

    public void startDocument(String encoding, Boolean standalone) throws IOException {
        mWrapped.startDocument(encoding, standalone);
    }

    public void endDocument() throws IOException {
        mWrapped.endDocument();
    }

    public void setPrefix(String prefix, String namespace) throws IOException {
        mWrapped.setPrefix(prefix, namespace);
    }

    public String getPrefix(String namespace, boolean generatePrefix) {
        return mWrapped.getPrefix(namespace, generatePrefix);
    }

    public int getDepth() {
        return mWrapped.getDepth();
    }

    public String getNamespace() {
        return mWrapped.getNamespace();
    }

    public String getName() {
        return mWrapped.getName();
    }

    public XmlSerializer startTag(String namespace, String name) throws IOException {
        return mWrapped.startTag(namespace, name);
    }

    public XmlSerializer attribute(String namespace, String name, String value)
            throws IOException {
        return mWrapped.attribute(namespace, name, value);
    }

    public XmlSerializer endTag(String namespace, String name) throws IOException {
        return mWrapped.endTag(namespace, name);
    }

    public XmlSerializer text(String text) throws IOException{
        return mWrapped.text(text);
    }

    public XmlSerializer text(char[] buf, int start, int len) throws IOException {
        return mWrapped.text(buf, start, len);
    }

    public void cdsect(String text)
            throws IOException, IllegalArgumentException, IllegalStateException {
        mWrapped.cdsect(text);
    }

    public void entityRef(String text) throws IOException {
        mWrapped.entityRef(text);
    }

    public void processingInstruction(String text) throws IOException {
        mWrapped.processingInstruction(text);
    }

    public void comment(String text) throws IOException {
        mWrapped.comment(text);
    }

    public void docdecl(String text) throws IOException {
        mWrapped.docdecl(text);
    }

    public void ignorableWhitespace(String text) throws IOException {
        mWrapped.ignorableWhitespace(text);
    }

    public void flush() throws IOException {
        mWrapped.flush();
    }
}
Loading