Move binary XML into modules-utils.

Bug: 243194720
Test: presubmit
Change-Id: I5252cf235cedc85aadebbecc078e753b7b54dcdd
This commit is contained in:
Hai Zhang
2022-10-21 03:10:19 -07:00
parent e55b8eeb28
commit f0960b3893
16 changed files with 27 additions and 2466 deletions

View File

@@ -384,6 +384,7 @@ java_defaults {
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
"modules-utils-binary-xml",
"modules-utils-build",
"modules-utils-preconditions",
"modules-utils-statemachine",

View File

@@ -22,6 +22,9 @@ import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.FastDataInput;
import com.android.modules.utils.FastDataOutput;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -18,6 +18,8 @@ package android.util;
import android.annotation.NonNull;
import com.android.modules.utils.ModifiedUtf8;
import dalvik.annotation.optimization.FastNative;
/**
@@ -30,8 +32,7 @@ import dalvik.annotation.optimization.FastNative;
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by {@code kUtfUse4ByteSequence}
* in {@code art/runtime/jni/jni_internal.cc}. If precise modified UTF-8
* encoding is required, use {@link com.android.internal.util.ModifiedUtf8}
* instead.
* encoding is required, use {@link ModifiedUtf8} instead.
*
* @hide
*/
@@ -43,8 +44,8 @@ public class CharsetUtils {
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
* If precise modified UTF-8 encoding is required, use
* {@link com.android.internal.util.ModifiedUtf8} instead.
* If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
* instead.
*
* @param src string value to be encoded
* @param dest destination byte array to encode into
@@ -66,8 +67,8 @@ public class CharsetUtils {
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
* If precise modified UTF-8 encoding is required, use
* {@link com.android.internal.util.ModifiedUtf8} instead.
* If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
* instead.
*
* @param src string value to be encoded
* @param srcLen exact length of string to be encoded
@@ -88,8 +89,8 @@ public class CharsetUtils {
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
* If precise modified UTF-8 encoding is required, use
* {@link com.android.internal.util.ModifiedUtf8} instead.
* If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
* instead.
*
* @param src source byte array to decode from
* @param srcOff offset into source where decoding should begin

View File

@@ -24,9 +24,9 @@ import android.system.Os;
import com.android.internal.util.ArtBinaryXmlPullParser;
import com.android.internal.util.ArtBinaryXmlSerializer;
import com.android.internal.util.BinaryXmlSerializer;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.BinaryXmlSerializer;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;

View File

@@ -18,6 +18,9 @@ package com.android.internal.util;
import android.annotation.NonNull;
import com.android.modules.utils.BinaryXmlPullParser;
import com.android.modules.utils.FastDataInput;
import java.io.DataInput;
import java.io.InputStream;

View File

@@ -18,6 +18,9 @@ package com.android.internal.util;
import android.annotation.NonNull;
import com.android.modules.utils.BinaryXmlSerializer;
import com.android.modules.utils.FastDataOutput;
import java.io.DataOutput;
import java.io.OutputStream;

View File

@@ -19,6 +19,8 @@ package com.android.internal.util;
import android.annotation.NonNull;
import android.util.CharsetUtils;
import com.android.modules.utils.FastDataInput;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;

View File

@@ -19,6 +19,8 @@ package com.android.internal.util;
import android.annotation.NonNull;
import android.util.CharsetUtils;
import com.android.modules.utils.FastDataOutput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;

View File

@@ -1,945 +0,0 @@
/*
* 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 static com.android.internal.util.BinaryXmlSerializer.ATTRIBUTE;
import static com.android.internal.util.BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_FALSE;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_TRUE;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_BASE64;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_HEX;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_DOUBLE;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_FLOAT;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT_HEX;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG_HEX;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_NULL;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING;
import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING_INTERNED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.TextUtils;
import android.util.Base64;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
/**
* Parser that reads XML documents using a custom binary wire protocol which
* benchmarking has shown to be 8.5x faster than {@link Xml.newFastPullParser()}
* for a typical {@code packages.xml}.
* <p>
* The high-level design of the wire protocol is to directly serialize the event
* stream, while efficiently and compactly writing strongly-typed primitives
* delivered through the {@link TypedXmlSerializer} interface.
* <p>
* Each serialized event is a single byte where the lower half is a normal
* {@link XmlPullParser} token and the upper half is an optional data type
* signal, such as {@link #TYPE_INT}.
* <p>
* This parser has some specific limitations:
* <ul>
* <li>Only the UTF-8 encoding is supported.
* <li>Variable length values, such as {@code byte[]} or {@link String}, are
* limited to 65,535 bytes in length. Note that {@link String} values are stored
* as UTF-8 on the wire.
* <li>Namespaces, prefixes, properties, and options are unsupported.
* </ul>
*/
public class BinaryXmlPullParser implements TypedXmlPullParser {
private FastDataInput mIn;
private int mCurrentToken = START_DOCUMENT;
private int mCurrentDepth = 0;
private String mCurrentName;
private String mCurrentText;
/**
* Pool of attributes parsed for the currently tag. All interactions should
* be done via {@link #obtainAttribute()}, {@link #findAttribute(String)},
* and {@link #resetAttributes()}.
*/
private int mAttributeCount = 0;
private Attribute[] mAttributes;
@Override
public void setInput(InputStream is, String encoding) throws XmlPullParserException {
if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
throw new UnsupportedOperationException();
}
if (mIn != null) {
mIn.release();
mIn = null;
}
mIn = obtainFastDataInput(is);
mCurrentToken = START_DOCUMENT;
mCurrentDepth = 0;
mCurrentName = null;
mCurrentText = null;
mAttributeCount = 0;
mAttributes = new Attribute[8];
for (int i = 0; i < mAttributes.length; i++) {
mAttributes[i] = new Attribute();
}
try {
final byte[] magic = new byte[4];
mIn.readFully(magic);
if (!Arrays.equals(magic, PROTOCOL_MAGIC_VERSION_0)) {
throw new IOException("Unexpected magic " + bytesToHexString(magic));
}
// We're willing to immediately consume a START_DOCUMENT if present,
// but we're okay if it's missing
if (peekNextExternalToken() == START_DOCUMENT) {
consumeToken();
}
} catch (IOException e) {
throw new XmlPullParserException(e.toString());
}
}
@NonNull
protected FastDataInput obtainFastDataInput(@NonNull InputStream is) {
return FastDataInput.obtain(is);
}
@Override
public void setInput(Reader in) throws XmlPullParserException {
throw new UnsupportedOperationException();
}
@Override
public int next() throws XmlPullParserException, IOException {
while (true) {
final int token = nextToken();
switch (token) {
case START_TAG:
case END_TAG:
case END_DOCUMENT:
return token;
case TEXT:
consumeAdditionalText();
// Per interface docs, empty text regions are skipped
if (mCurrentText == null || mCurrentText.length() == 0) {
continue;
} else {
return TEXT;
}
}
}
}
@Override
public int nextToken() throws XmlPullParserException, IOException {
if (mCurrentToken == XmlPullParser.END_TAG) {
mCurrentDepth--;
}
int token;
try {
token = peekNextExternalToken();
consumeToken();
} catch (EOFException e) {
token = END_DOCUMENT;
}
switch (token) {
case XmlPullParser.START_TAG:
// We need to peek forward to find the next external token so
// that we parse all pending INTERNAL_ATTRIBUTE tokens
peekNextExternalToken();
mCurrentDepth++;
break;
}
mCurrentToken = token;
return token;
}
/**
* Peek at the next "external" token without consuming it.
* <p>
* External tokens, such as {@link #START_TAG}, are expected by typical
* {@link XmlPullParser} clients. In contrast, internal tokens, such as
* {@link #ATTRIBUTE}, are not expected by typical clients.
* <p>
* This method consumes any internal events until it reaches the next
* external event.
*/
private int peekNextExternalToken() throws IOException, XmlPullParserException {
while (true) {
final int token = peekNextToken();
switch (token) {
case ATTRIBUTE:
consumeToken();
continue;
default:
return token;
}
}
}
/**
* Peek at the next token in the underlying stream without consuming it.
*/
private int peekNextToken() throws IOException {
return mIn.peekByte() & 0x0f;
}
/**
* Parse and consume the next token in the underlying stream.
*/
private void consumeToken() throws IOException, XmlPullParserException {
final int event = mIn.readByte();
final int token = event & 0x0f;
final int type = event & 0xf0;
switch (token) {
case ATTRIBUTE: {
final Attribute attr = obtainAttribute();
attr.name = mIn.readInternedUTF();
attr.type = type;
switch (type) {
case TYPE_NULL:
case TYPE_BOOLEAN_TRUE:
case TYPE_BOOLEAN_FALSE:
// Nothing extra to fill in
break;
case TYPE_STRING:
attr.valueString = mIn.readUTF();
break;
case TYPE_STRING_INTERNED:
attr.valueString = mIn.readInternedUTF();
break;
case TYPE_BYTES_HEX:
case TYPE_BYTES_BASE64:
final int len = mIn.readUnsignedShort();
final byte[] res = new byte[len];
mIn.readFully(res);
attr.valueBytes = res;
break;
case TYPE_INT:
case TYPE_INT_HEX:
attr.valueInt = mIn.readInt();
break;
case TYPE_LONG:
case TYPE_LONG_HEX:
attr.valueLong = mIn.readLong();
break;
case TYPE_FLOAT:
attr.valueFloat = mIn.readFloat();
break;
case TYPE_DOUBLE:
attr.valueDouble = mIn.readDouble();
break;
default:
throw new IOException("Unexpected data type " + type);
}
break;
}
case XmlPullParser.START_DOCUMENT: {
mCurrentName = null;
mCurrentText = null;
if (mAttributeCount > 0) resetAttributes();
break;
}
case XmlPullParser.END_DOCUMENT: {
mCurrentName = null;
mCurrentText = null;
if (mAttributeCount > 0) resetAttributes();
break;
}
case XmlPullParser.START_TAG: {
mCurrentName = mIn.readInternedUTF();
mCurrentText = null;
if (mAttributeCount > 0) resetAttributes();
break;
}
case XmlPullParser.END_TAG: {
mCurrentName = mIn.readInternedUTF();
mCurrentText = null;
if (mAttributeCount > 0) resetAttributes();
break;
}
case XmlPullParser.TEXT:
case XmlPullParser.CDSECT:
case XmlPullParser.PROCESSING_INSTRUCTION:
case XmlPullParser.COMMENT:
case XmlPullParser.DOCDECL:
case XmlPullParser.IGNORABLE_WHITESPACE: {
mCurrentName = null;
mCurrentText = mIn.readUTF();
if (mAttributeCount > 0) resetAttributes();
break;
}
case XmlPullParser.ENTITY_REF: {
mCurrentName = mIn.readUTF();
mCurrentText = resolveEntity(mCurrentName);
if (mAttributeCount > 0) resetAttributes();
break;
}
default: {
throw new IOException("Unknown token " + token + " with type " + type);
}
}
}
/**
* When the current tag is {@link #TEXT}, consume all subsequent "text"
* events, as described by {@link #next}. When finished, the current event
* will still be {@link #TEXT}.
*/
private void consumeAdditionalText() throws IOException, XmlPullParserException {
String combinedText = mCurrentText;
while (true) {
final int token = peekNextExternalToken();
switch (token) {
case COMMENT:
case PROCESSING_INSTRUCTION:
// Quietly consumed
consumeToken();
break;
case TEXT:
case CDSECT:
case ENTITY_REF:
// Additional text regions collected
consumeToken();
combinedText += mCurrentText;
break;
default:
// Next token is something non-text, so wrap things up
mCurrentToken = TEXT;
mCurrentName = null;
mCurrentText = combinedText;
return;
}
}
}
static @NonNull String resolveEntity(@NonNull String entity)
throws XmlPullParserException {
switch (entity) {
case "lt": return "<";
case "gt": return ">";
case "amp": return "&";
case "apos": return "'";
case "quot": return "\"";
}
if (entity.length() > 1 && entity.charAt(0) == '#') {
final char c = (char) Integer.parseInt(entity.substring(1));
return new String(new char[] { c });
}
throw new XmlPullParserException("Unknown entity " + entity);
}
@Override
public void require(int type, String namespace, String name)
throws XmlPullParserException, IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
if (mCurrentToken != type || !Objects.equals(mCurrentName, name)) {
throw new XmlPullParserException(getPositionDescription());
}
}
@Override
public String nextText() throws XmlPullParserException, IOException {
if (getEventType() != START_TAG) {
throw new XmlPullParserException(getPositionDescription());
}
int eventType = next();
if (eventType == TEXT) {
String result = getText();
eventType = next();
if (eventType != END_TAG) {
throw new XmlPullParserException(getPositionDescription());
}
return result;
} else if (eventType == END_TAG) {
return "";
} else {
throw new XmlPullParserException(getPositionDescription());
}
}
@Override
public int nextTag() throws XmlPullParserException, IOException {
int eventType = next();
if (eventType == TEXT && isWhitespace()) {
eventType = next();
}
if (eventType != START_TAG && eventType != END_TAG) {
throw new XmlPullParserException(getPositionDescription());
}
return eventType;
}
/**
* Allocate and return a new {@link Attribute} associated with the tag being
* currently processed. This will automatically grow the internal pool as
* needed.
*/
private @NonNull Attribute obtainAttribute() {
if (mAttributeCount == mAttributes.length) {
final int before = mAttributes.length;
final int after = before + (before >> 1);
mAttributes = Arrays.copyOf(mAttributes, after);
for (int i = before; i < after; i++) {
mAttributes[i] = new Attribute();
}
}
return mAttributes[mAttributeCount++];
}
/**
* Clear any {@link Attribute} instances that have been allocated by
* {@link #obtainAttribute()}, returning them into the pool for recycling.
*/
private void resetAttributes() {
for (int i = 0; i < mAttributeCount; i++) {
mAttributes[i].reset();
}
mAttributeCount = 0;
}
@Override
public int getAttributeIndex(String namespace, String name) {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
for (int i = 0; i < mAttributeCount; i++) {
if (Objects.equals(mAttributes[i].name, name)) {
return i;
}
}
return -1;
}
@Override
public String getAttributeValue(String namespace, String name) {
final int index = getAttributeIndex(namespace, name);
if (index != -1) {
return mAttributes[index].getValueString();
} else {
return null;
}
}
@Override
public String getAttributeValue(int index) {
return mAttributes[index].getValueString();
}
@Override
public byte[] getAttributeBytesHex(int index) throws XmlPullParserException {
return mAttributes[index].getValueBytesHex();
}
@Override
public byte[] getAttributeBytesBase64(int index) throws XmlPullParserException {
return mAttributes[index].getValueBytesBase64();
}
@Override
public int getAttributeInt(int index) throws XmlPullParserException {
return mAttributes[index].getValueInt();
}
@Override
public int getAttributeIntHex(int index) throws XmlPullParserException {
return mAttributes[index].getValueIntHex();
}
@Override
public long getAttributeLong(int index) throws XmlPullParserException {
return mAttributes[index].getValueLong();
}
@Override
public long getAttributeLongHex(int index) throws XmlPullParserException {
return mAttributes[index].getValueLongHex();
}
@Override
public float getAttributeFloat(int index) throws XmlPullParserException {
return mAttributes[index].getValueFloat();
}
@Override
public double getAttributeDouble(int index) throws XmlPullParserException {
return mAttributes[index].getValueDouble();
}
@Override
public boolean getAttributeBoolean(int index) throws XmlPullParserException {
return mAttributes[index].getValueBoolean();
}
@Override
public String getText() {
return mCurrentText;
}
@Override
public char[] getTextCharacters(int[] holderForStartAndLength) {
final char[] chars = mCurrentText.toCharArray();
holderForStartAndLength[0] = 0;
holderForStartAndLength[1] = chars.length;
return chars;
}
@Override
public String getInputEncoding() {
return StandardCharsets.UTF_8.name();
}
@Override
public int getDepth() {
return mCurrentDepth;
}
@Override
public String getPositionDescription() {
// Not very helpful, but it's the best information we have
return "Token " + mCurrentToken + " at depth " + mCurrentDepth;
}
@Override
public int getLineNumber() {
return -1;
}
@Override
public int getColumnNumber() {
return -1;
}
@Override
public boolean isWhitespace() throws XmlPullParserException {
switch (mCurrentToken) {
case IGNORABLE_WHITESPACE:
return true;
case TEXT:
case CDSECT:
return !TextUtils.isGraphic(mCurrentText);
default:
throw new XmlPullParserException("Not applicable for token " + mCurrentToken);
}
}
@Override
public String getNamespace() {
switch (mCurrentToken) {
case START_TAG:
case END_TAG:
// Namespaces are unsupported
return NO_NAMESPACE;
default:
return null;
}
}
@Override
public String getName() {
return mCurrentName;
}
@Override
public String getPrefix() {
// Prefixes are not supported
return null;
}
@Override
public boolean isEmptyElementTag() throws XmlPullParserException {
switch (mCurrentToken) {
case START_TAG:
try {
return (peekNextExternalToken() == END_TAG);
} catch (IOException e) {
throw new XmlPullParserException(e.toString());
}
default:
throw new XmlPullParserException("Not at START_TAG");
}
}
@Override
public int getAttributeCount() {
return mAttributeCount;
}
@Override
public String getAttributeNamespace(int index) {
// Namespaces are unsupported
return NO_NAMESPACE;
}
@Override
public String getAttributeName(int index) {
return mAttributes[index].name;
}
@Override
public String getAttributePrefix(int index) {
// Prefixes are not supported
return null;
}
@Override
public String getAttributeType(int index) {
// Validation is not supported
return "CDATA";
}
@Override
public boolean isAttributeDefault(int index) {
// Validation is not supported
return false;
}
@Override
public int getEventType() throws XmlPullParserException {
return mCurrentToken;
}
@Override
public int getNamespaceCount(int depth) throws XmlPullParserException {
// Namespaces are unsupported
return 0;
}
@Override
public String getNamespacePrefix(int pos) throws XmlPullParserException {
// Namespaces are unsupported
throw new UnsupportedOperationException();
}
@Override
public String getNamespaceUri(int pos) throws XmlPullParserException {
// Namespaces are unsupported
throw new UnsupportedOperationException();
}
@Override
public String getNamespace(String prefix) {
// Namespaces are unsupported
throw new UnsupportedOperationException();
}
@Override
public void defineEntityReplacementText(String entityName, String replacementText)
throws XmlPullParserException {
// Custom entities are not supported
throw new UnsupportedOperationException();
}
@Override
public void setFeature(String name, boolean state) throws XmlPullParserException {
// Features are not supported
throw new UnsupportedOperationException();
}
@Override
public boolean getFeature(String name) {
// Features are not supported
throw new UnsupportedOperationException();
}
@Override
public void setProperty(String name, Object value) throws XmlPullParserException {
// Properties are not supported
throw new UnsupportedOperationException();
}
@Override
public Object getProperty(String name) {
// Properties are not supported
throw new UnsupportedOperationException();
}
private static IllegalArgumentException illegalNamespace() {
throw new IllegalArgumentException("Namespaces are not supported");
}
/**
* Holder representing a single attribute. This design enables object
* recycling without resorting to autoboxing.
* <p>
* To support conversion between human-readable XML and binary XML, the
* various accessor methods will transparently convert from/to
* human-readable values when needed.
*/
private static class Attribute {
public String name;
public int type;
public String valueString;
public byte[] valueBytes;
public int valueInt;
public long valueLong;
public float valueFloat;
public double valueDouble;
public void reset() {
name = null;
valueString = null;
valueBytes = null;
}
public @Nullable String getValueString() {
switch (type) {
case TYPE_NULL:
return null;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
return valueString;
case TYPE_BYTES_HEX:
return bytesToHexString(valueBytes);
case TYPE_BYTES_BASE64:
return Base64.encodeToString(valueBytes, Base64.NO_WRAP);
case TYPE_INT:
return Integer.toString(valueInt);
case TYPE_INT_HEX:
return Integer.toString(valueInt, 16);
case TYPE_LONG:
return Long.toString(valueLong);
case TYPE_LONG_HEX:
return Long.toString(valueLong, 16);
case TYPE_FLOAT:
return Float.toString(valueFloat);
case TYPE_DOUBLE:
return Double.toString(valueDouble);
case TYPE_BOOLEAN_TRUE:
return "true";
case TYPE_BOOLEAN_FALSE:
return "false";
default:
// Unknown data type; null is the best we can offer
return null;
}
}
public @Nullable byte[] getValueBytesHex() throws XmlPullParserException {
switch (type) {
case TYPE_NULL:
return null;
case TYPE_BYTES_HEX:
case TYPE_BYTES_BASE64:
return valueBytes;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return hexStringToBytes(valueString);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public @Nullable byte[] getValueBytesBase64() throws XmlPullParserException {
switch (type) {
case TYPE_NULL:
return null;
case TYPE_BYTES_HEX:
case TYPE_BYTES_BASE64:
return valueBytes;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Base64.decode(valueString, Base64.NO_WRAP);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public int getValueInt() throws XmlPullParserException {
switch (type) {
case TYPE_INT:
case TYPE_INT_HEX:
return valueInt;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Integer.parseInt(valueString);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public int getValueIntHex() throws XmlPullParserException {
switch (type) {
case TYPE_INT:
case TYPE_INT_HEX:
return valueInt;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Integer.parseInt(valueString, 16);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public long getValueLong() throws XmlPullParserException {
switch (type) {
case TYPE_LONG:
case TYPE_LONG_HEX:
return valueLong;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Long.parseLong(valueString);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public long getValueLongHex() throws XmlPullParserException {
switch (type) {
case TYPE_LONG:
case TYPE_LONG_HEX:
return valueLong;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Long.parseLong(valueString, 16);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public float getValueFloat() throws XmlPullParserException {
switch (type) {
case TYPE_FLOAT:
return valueFloat;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Float.parseFloat(valueString);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public double getValueDouble() throws XmlPullParserException {
switch (type) {
case TYPE_DOUBLE:
return valueDouble;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
try {
return Double.parseDouble(valueString);
} catch (Exception e) {
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
public boolean getValueBoolean() throws XmlPullParserException {
switch (type) {
case TYPE_BOOLEAN_TRUE:
return true;
case TYPE_BOOLEAN_FALSE:
return false;
case TYPE_STRING:
case TYPE_STRING_INTERNED:
if ("true".equalsIgnoreCase(valueString)) {
return true;
} else if ("false".equalsIgnoreCase(valueString)) {
return false;
} else {
throw new XmlPullParserException(
"Invalid attribute " + name + ": " + valueString);
}
default:
throw new XmlPullParserException("Invalid conversion from " + type);
}
}
}
// NOTE: To support unbundled clients, we include an inlined copy
// of hex conversion logic from HexDump below
private final static char[] HEX_DIGITS =
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private static int toByte(char c) {
if (c >= '0' && c <= '9') return (c - '0');
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
throw new IllegalArgumentException("Invalid hex char '" + c + "'");
}
static String bytesToHexString(byte[] value) {
final int length = value.length;
final char[] buf = new char[length * 2];
int bufIndex = 0;
for (int i = 0; i < length; i++) {
byte b = value[i];
buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
}
return new String(buf);
}
static byte[] hexStringToBytes(String value) {
final int length = value.length();
if (length % 2 != 0) {
throw new IllegalArgumentException("Invalid hex length " + length);
}
byte[] buffer = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
buffer[i / 2] = (byte) ((toByte(value.charAt(i)) << 4)
| toByte(value.charAt(i + 1)));
}
return buffer;
}
}

View File

@@ -1,404 +0,0 @@
/*
* 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 static org.xmlpull.v1.XmlPullParser.CDSECT;
import static org.xmlpull.v1.XmlPullParser.COMMENT;
import static org.xmlpull.v1.XmlPullParser.DOCDECL;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.ENTITY_REF;
import static org.xmlpull.v1.XmlPullParser.IGNORABLE_WHITESPACE;
import static org.xmlpull.v1.XmlPullParser.PROCESSING_INSTRUCTION;
import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static org.xmlpull.v1.XmlPullParser.TEXT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* Serializer that writes XML documents using a custom binary wire protocol
* which benchmarking has shown to be 4.3x faster and use 2.4x less disk space
* than {@code Xml.newFastSerializer()} for a typical {@code packages.xml}.
* <p>
* The high-level design of the wire protocol is to directly serialize the event
* stream, while efficiently and compactly writing strongly-typed primitives
* delivered through the {@link TypedXmlSerializer} interface.
* <p>
* Each serialized event is a single byte where the lower half is a normal
* {@link XmlPullParser} token and the upper half is an optional data type
* signal, such as {@link #TYPE_INT}.
* <p>
* This serializer has some specific limitations:
* <ul>
* <li>Only the UTF-8 encoding is supported.
* <li>Variable length values, such as {@code byte[]} or {@link String}, are
* limited to 65,535 bytes in length. Note that {@link String} values are stored
* as UTF-8 on the wire.
* <li>Namespaces, prefixes, properties, and options are unsupported.
* </ul>
*/
public class BinaryXmlSerializer implements TypedXmlSerializer {
/**
* The wire protocol always begins with a well-known magic value of
* {@code ABX_}, representing "Android Binary XML." The final byte is a
* version number which may be incremented as the protocol changes.
*/
public static final byte[] PROTOCOL_MAGIC_VERSION_0 = new byte[] { 0x41, 0x42, 0x58, 0x00 };
/**
* Internal token which represents an attribute associated with the most
* recent {@link #START_TAG} token.
*/
static final int ATTRIBUTE = 15;
static final int TYPE_NULL = 1 << 4;
static final int TYPE_STRING = 2 << 4;
static final int TYPE_STRING_INTERNED = 3 << 4;
static final int TYPE_BYTES_HEX = 4 << 4;
static final int TYPE_BYTES_BASE64 = 5 << 4;
static final int TYPE_INT = 6 << 4;
static final int TYPE_INT_HEX = 7 << 4;
static final int TYPE_LONG = 8 << 4;
static final int TYPE_LONG_HEX = 9 << 4;
static final int TYPE_FLOAT = 10 << 4;
static final int TYPE_DOUBLE = 11 << 4;
static final int TYPE_BOOLEAN_TRUE = 12 << 4;
static final int TYPE_BOOLEAN_FALSE = 13 << 4;
private FastDataOutput mOut;
/**
* Stack of tags which are currently active via {@link #startTag} and which
* haven't been terminated via {@link #endTag}.
*/
private int mTagCount = 0;
private String[] mTagNames;
/**
* Write the given token and optional {@link String} into our buffer.
*/
private void writeToken(int token, @Nullable String text) throws IOException {
if (text != null) {
mOut.writeByte(token | TYPE_STRING);
mOut.writeUTF(text);
} else {
mOut.writeByte(token | TYPE_NULL);
}
}
@Override
public void setOutput(@NonNull OutputStream os, @Nullable String encoding) throws IOException {
if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
throw new UnsupportedOperationException();
}
mOut = obtainFastDataOutput(os);
mOut.write(PROTOCOL_MAGIC_VERSION_0);
mTagCount = 0;
mTagNames = new String[8];
}
@NonNull
protected FastDataOutput obtainFastDataOutput(@NonNull OutputStream os) {
return FastDataOutput.obtain(os);
}
@Override
public void setOutput(Writer writer) {
throw new UnsupportedOperationException();
}
@Override
public void flush() throws IOException {
if (mOut != null) {
mOut.flush();
}
}
@Override
public void startDocument(@Nullable String encoding, @Nullable Boolean standalone)
throws IOException {
if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
throw new UnsupportedOperationException();
}
if (standalone != null && !standalone) {
throw new UnsupportedOperationException();
}
mOut.writeByte(START_DOCUMENT | TYPE_NULL);
}
@Override
public void endDocument() throws IOException {
mOut.writeByte(END_DOCUMENT | TYPE_NULL);
flush();
mOut.release();
mOut = null;
}
@Override
public int getDepth() {
return mTagCount;
}
@Override
public String getNamespace() {
// Namespaces are unsupported
return XmlPullParser.NO_NAMESPACE;
}
@Override
public String getName() {
return mTagNames[mTagCount - 1];
}
@Override
public XmlSerializer startTag(String namespace, String name) throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
if (mTagCount == mTagNames.length) {
mTagNames = Arrays.copyOf(mTagNames, mTagCount + (mTagCount >> 1));
}
mTagNames[mTagCount++] = name;
mOut.writeByte(START_TAG | TYPE_STRING_INTERNED);
mOut.writeInternedUTF(name);
return this;
}
@Override
public XmlSerializer endTag(String namespace, String name) throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mTagCount--;
mOut.writeByte(END_TAG | TYPE_STRING_INTERNED);
mOut.writeInternedUTF(name);
return this;
}
@Override
public XmlSerializer attribute(String namespace, String name, String value) throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_STRING);
mOut.writeInternedUTF(name);
mOut.writeUTF(value);
return this;
}
@Override
public XmlSerializer attributeInterned(String namespace, String name, String value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_STRING_INTERNED);
mOut.writeInternedUTF(name);
mOut.writeInternedUTF(value);
return this;
}
@Override
public XmlSerializer attributeBytesHex(String namespace, String name, byte[] value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_HEX);
mOut.writeInternedUTF(name);
mOut.writeShort(value.length);
mOut.write(value);
return this;
}
@Override
public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
mOut.writeInternedUTF(name);
mOut.writeShort(value.length);
mOut.write(value);
return this;
}
@Override
public XmlSerializer attributeInt(String namespace, String name, int value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_INT);
mOut.writeInternedUTF(name);
mOut.writeInt(value);
return this;
}
@Override
public XmlSerializer attributeIntHex(String namespace, String name, int value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_INT_HEX);
mOut.writeInternedUTF(name);
mOut.writeInt(value);
return this;
}
@Override
public XmlSerializer attributeLong(String namespace, String name, long value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_LONG);
mOut.writeInternedUTF(name);
mOut.writeLong(value);
return this;
}
@Override
public XmlSerializer attributeLongHex(String namespace, String name, long value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_LONG_HEX);
mOut.writeInternedUTF(name);
mOut.writeLong(value);
return this;
}
@Override
public XmlSerializer attributeFloat(String namespace, String name, float value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_FLOAT);
mOut.writeInternedUTF(name);
mOut.writeFloat(value);
return this;
}
@Override
public XmlSerializer attributeDouble(String namespace, String name, double value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_DOUBLE);
mOut.writeInternedUTF(name);
mOut.writeDouble(value);
return this;
}
@Override
public XmlSerializer attributeBoolean(String namespace, String name, boolean value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
if (value) {
mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_TRUE);
mOut.writeInternedUTF(name);
} else {
mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_FALSE);
mOut.writeInternedUTF(name);
}
return this;
}
@Override
public XmlSerializer text(char[] buf, int start, int len) throws IOException {
writeToken(TEXT, new String(buf, start, len));
return this;
}
@Override
public XmlSerializer text(String text) throws IOException {
writeToken(TEXT, text);
return this;
}
@Override
public void cdsect(String text) throws IOException {
writeToken(CDSECT, text);
}
@Override
public void entityRef(String text) throws IOException {
writeToken(ENTITY_REF, text);
}
@Override
public void processingInstruction(String text) throws IOException {
writeToken(PROCESSING_INSTRUCTION, text);
}
@Override
public void comment(String text) throws IOException {
writeToken(COMMENT, text);
}
@Override
public void docdecl(String text) throws IOException {
writeToken(DOCDECL, text);
}
@Override
public void ignorableWhitespace(String text) throws IOException {
writeToken(IGNORABLE_WHITESPACE, text);
}
@Override
public void setFeature(String name, boolean state) {
// Quietly handle no-op features
if ("http://xmlpull.org/v1/doc/features.html#indent-output".equals(name)) {
return;
}
// Features are not supported
throw new UnsupportedOperationException();
}
@Override
public boolean getFeature(String name) {
// Features are not supported
throw new UnsupportedOperationException();
}
@Override
public void setProperty(String name, Object value) {
// Properties are not supported
throw new UnsupportedOperationException();
}
@Override
public Object getProperty(String name) {
// Properties are not supported
throw new UnsupportedOperationException();
}
@Override
public void setPrefix(String prefix, String namespace) {
// Prefixes are not supported
throw new UnsupportedOperationException();
}
@Override
public String getPrefix(String namespace, boolean generatePrefix) {
// Prefixes are not supported
throw new UnsupportedOperationException();
}
private static IllegalArgumentException illegalNamespace() {
throw new IllegalArgumentException("Namespaces are not supported");
}
}

View File

@@ -1,300 +0,0 @@
/*
* 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 dalvik.system.VMRuntime;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Objects;
/**
* Optimized implementation of {@link DataInput} which buffers data in memory
* from the underlying {@link InputStream}.
* <p>
* Benchmarks have demonstrated this class is 3x more efficient than using a
* {@link DataInputStream} with a {@link BufferedInputStream}.
*/
public class FastDataInput implements DataInput, Closeable {
protected static final int MAX_UNSIGNED_SHORT = 65_535;
protected static final int DEFAULT_BUFFER_SIZE = 32_768;
protected final VMRuntime mRuntime;
protected final byte[] mBuffer;
protected final int mBufferCap;
private InputStream mIn;
protected int mBufferPos;
protected int mBufferLim;
/**
* Values that have been "interned" by {@link #readInternedUTF()}.
*/
private int mStringRefCount = 0;
private String[] mStringRefs = new String[32];
public FastDataInput(@NonNull InputStream in, int bufferSize) {
mRuntime = VMRuntime.getRuntime();
mIn = Objects.requireNonNull(in);
if (bufferSize < 8) {
throw new IllegalArgumentException();
}
mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
mBufferCap = mBuffer.length;
}
/**
* Obtain a {@link FastDataInput} configured with the given
* {@link InputStream} and which encodes large code-points using 3-byte
* sequences.
* <p>
* This <em>is</em> compatible with the {@link DataInput} API contract,
* which specifies that large code-points must be encoded with 3-byte
* sequences.
*/
public static FastDataInput obtain(@NonNull InputStream in) {
return new FastDataInput(in, DEFAULT_BUFFER_SIZE);
}
/**
* Release a {@link FastDataInput} to potentially be recycled. You must not
* interact with the object after releasing it.
*/
public void release() {
mIn = null;
mBufferPos = 0;
mBufferLim = 0;
mStringRefCount = 0;
}
/**
* Re-initializes the object for the new input.
*/
protected void setInput(@NonNull InputStream in) {
if (mIn != null) {
throw new IllegalStateException("setInput() called before calling release()");
}
mIn = Objects.requireNonNull(in);
mBufferPos = 0;
mBufferLim = 0;
mStringRefCount = 0;
}
protected void fill(int need) throws IOException {
final int remain = mBufferLim - mBufferPos;
System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain);
mBufferPos = 0;
mBufferLim = remain;
need -= remain;
while (need > 0) {
int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim);
if (c == -1) {
throw new EOFException();
} else {
mBufferLim += c;
need -= c;
}
}
}
@Override
public void close() throws IOException {
mIn.close();
release();
}
@Override
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
// Attempt to read directly from buffer space if there's enough room,
// otherwise fall back to chunking into place
if (mBufferCap >= len) {
if (mBufferLim - mBufferPos < len) fill(len);
System.arraycopy(mBuffer, mBufferPos, b, off, len);
mBufferPos += len;
} else {
final int remain = mBufferLim - mBufferPos;
System.arraycopy(mBuffer, mBufferPos, b, off, remain);
mBufferPos += remain;
off += remain;
len -= remain;
while (len > 0) {
int c = mIn.read(b, off, len);
if (c == -1) {
throw new EOFException();
} else {
off += c;
len -= c;
}
}
}
}
@Override
public String readUTF() throws IOException {
// Attempt to read directly from buffer space if there's enough room,
// otherwise fall back to chunking into place
final int len = readUnsignedShort();
if (mBufferCap > len) {
if (mBufferLim - mBufferPos < len) fill(len);
final String res = ModifiedUtf8.decode(mBuffer, new char[len], mBufferPos, len);
mBufferPos += len;
return res;
} else {
final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
readFully(tmp, 0, len);
return ModifiedUtf8.decode(tmp, new char[len], 0, len);
}
}
/**
* Read a {@link String} value with the additional signal that the given
* value is a candidate for being canonicalized, similar to
* {@link String#intern()}.
* <p>
* Canonicalization is implemented by writing each unique string value once
* the first time it appears, and then writing a lightweight {@code short}
* reference when that string is written again in the future.
*
* @see FastDataOutput#writeInternedUTF(String)
*/
public @NonNull String readInternedUTF() throws IOException {
final int ref = readUnsignedShort();
if (ref == MAX_UNSIGNED_SHORT) {
final String s = readUTF();
// We can only safely intern when we have remaining values; if we're
// full we at least sent the string value above
if (mStringRefCount < MAX_UNSIGNED_SHORT) {
if (mStringRefCount == mStringRefs.length) {
mStringRefs = Arrays.copyOf(mStringRefs,
mStringRefCount + (mStringRefCount >> 1));
}
mStringRefs[mStringRefCount++] = s;
}
return s;
} else {
return mStringRefs[ref];
}
}
@Override
public boolean readBoolean() throws IOException {
return readByte() != 0;
}
/**
* Returns the same decoded value as {@link #readByte()} but without
* actually consuming the underlying data.
*/
public byte peekByte() throws IOException {
if (mBufferLim - mBufferPos < 1) fill(1);
return mBuffer[mBufferPos];
}
@Override
public byte readByte() throws IOException {
if (mBufferLim - mBufferPos < 1) fill(1);
return mBuffer[mBufferPos++];
}
@Override
public int readUnsignedByte() throws IOException {
return Byte.toUnsignedInt(readByte());
}
@Override
public short readShort() throws IOException {
if (mBufferLim - mBufferPos < 2) fill(2);
return (short) (((mBuffer[mBufferPos++] & 0xff) << 8) |
((mBuffer[mBufferPos++] & 0xff) << 0));
}
@Override
public int readUnsignedShort() throws IOException {
return Short.toUnsignedInt((short) readShort());
}
@Override
public char readChar() throws IOException {
return (char) readShort();
}
@Override
public int readInt() throws IOException {
if (mBufferLim - mBufferPos < 4) fill(4);
return (((mBuffer[mBufferPos++] & 0xff) << 24) |
((mBuffer[mBufferPos++] & 0xff) << 16) |
((mBuffer[mBufferPos++] & 0xff) << 8) |
((mBuffer[mBufferPos++] & 0xff) << 0));
}
@Override
public long readLong() throws IOException {
if (mBufferLim - mBufferPos < 8) fill(8);
int h = ((mBuffer[mBufferPos++] & 0xff) << 24) |
((mBuffer[mBufferPos++] & 0xff) << 16) |
((mBuffer[mBufferPos++] & 0xff) << 8) |
((mBuffer[mBufferPos++] & 0xff) << 0);
int l = ((mBuffer[mBufferPos++] & 0xff) << 24) |
((mBuffer[mBufferPos++] & 0xff) << 16) |
((mBuffer[mBufferPos++] & 0xff) << 8) |
((mBuffer[mBufferPos++] & 0xff) << 0);
return (((long) h) << 32L) | ((long) l) & 0xffffffffL;
}
@Override
public float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
@Override
public double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
@Override
public int skipBytes(int n) throws IOException {
// Callers should read data piecemeal
throw new UnsupportedOperationException();
}
@Override
public String readLine() throws IOException {
// Callers should read data piecemeal
throw new UnsupportedOperationException();
}
}

View File

@@ -1,269 +0,0 @@
/*
* 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 dalvik.system.VMRuntime;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Objects;
/**
* Optimized implementation of {@link DataOutput} which buffers data in memory
* before flushing to the underlying {@link OutputStream}.
* <p>
* Benchmarks have demonstrated this class is 2x more efficient than using a
* {@link DataOutputStream} with a {@link BufferedOutputStream}.
*/
public class FastDataOutput implements DataOutput, Flushable, Closeable {
protected static final int MAX_UNSIGNED_SHORT = 65_535;
protected static final int DEFAULT_BUFFER_SIZE = 32_768;
protected final VMRuntime mRuntime;
protected final byte[] mBuffer;
protected final int mBufferCap;
private OutputStream mOut;
protected int mBufferPos;
/**
* Values that have been "interned" by {@link #writeInternedUTF(String)}.
*/
private final HashMap<String, Integer> mStringRefs = new HashMap<>();
public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
mRuntime = VMRuntime.getRuntime();
if (bufferSize < 8) {
throw new IllegalArgumentException();
}
mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
mBufferCap = mBuffer.length;
setOutput(out);
}
/**
* Obtain a {@link FastDataOutput} configured with the given
* {@link OutputStream} and which encodes large code-points using 3-byte
* sequences.
* <p>
* This <em>is</em> compatible with the {@link DataOutput} API contract,
* which specifies that large code-points must be encoded with 3-byte
* sequences.
*/
public static FastDataOutput obtain(@NonNull OutputStream out) {
return new FastDataOutput(out, DEFAULT_BUFFER_SIZE);
}
/**
* Release a {@link FastDataOutput} to potentially be recycled. You must not
* interact with the object after releasing it.
*/
public void release() {
if (mBufferPos > 0) {
throw new IllegalStateException("Lingering data, call flush() before releasing.");
}
mOut = null;
mBufferPos = 0;
mStringRefs.clear();
}
/**
* Re-initializes the object for the new output.
*/
protected void setOutput(@NonNull OutputStream out) {
if (mOut != null) {
throw new IllegalStateException("setOutput() called before calling release()");
}
mOut = Objects.requireNonNull(out);
mBufferPos = 0;
mStringRefs.clear();
}
protected void drain() throws IOException {
if (mBufferPos > 0) {
mOut.write(mBuffer, 0, mBufferPos);
mBufferPos = 0;
}
}
@Override
public void flush() throws IOException {
drain();
mOut.flush();
}
@Override
public void close() throws IOException {
mOut.close();
release();
}
@Override
public void write(int b) throws IOException {
writeByte(b);
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (mBufferCap < len) {
drain();
mOut.write(b, off, len);
} else {
if (mBufferCap - mBufferPos < len) drain();
System.arraycopy(b, off, mBuffer, mBufferPos, len);
mBufferPos += len;
}
}
@Override
public void writeUTF(String s) throws IOException {
final int len = (int) ModifiedUtf8.countBytes(s, false);
if (len > MAX_UNSIGNED_SHORT) {
throw new IOException("Modified UTF-8 length too large: " + len);
}
// Attempt to write directly to buffer space if there's enough room,
// otherwise fall back to chunking into place
if (mBufferCap >= 2 + len) {
if (mBufferCap - mBufferPos < 2 + len) drain();
writeShort(len);
ModifiedUtf8.encode(mBuffer, mBufferPos, s);
mBufferPos += len;
} else {
final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
ModifiedUtf8.encode(tmp, 0, s);
writeShort(len);
write(tmp, 0, len);
}
}
/**
* Write a {@link String} value with the additional signal that the given
* value is a candidate for being canonicalized, similar to
* {@link String#intern()}.
* <p>
* Canonicalization is implemented by writing each unique string value once
* the first time it appears, and then writing a lightweight {@code short}
* reference when that string is written again in the future.
*
* @see FastDataInput#readInternedUTF()
*/
public void writeInternedUTF(@NonNull String s) throws IOException {
Integer ref = mStringRefs.get(s);
if (ref != null) {
writeShort(ref);
} else {
writeShort(MAX_UNSIGNED_SHORT);
writeUTF(s);
// We can only safely intern when we have remaining values; if we're
// full we at least sent the string value above
ref = mStringRefs.size();
if (ref < MAX_UNSIGNED_SHORT) {
mStringRefs.put(s, ref);
}
}
}
@Override
public void writeBoolean(boolean v) throws IOException {
writeByte(v ? 1 : 0);
}
@Override
public void writeByte(int v) throws IOException {
if (mBufferCap - mBufferPos < 1) drain();
mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
}
@Override
public void writeShort(int v) throws IOException {
if (mBufferCap - mBufferPos < 2) drain();
mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
}
@Override
public void writeChar(int v) throws IOException {
writeShort((short) v);
}
@Override
public void writeInt(int v) throws IOException {
if (mBufferCap - mBufferPos < 4) drain();
mBuffer[mBufferPos++] = (byte) ((v >> 24) & 0xff);
mBuffer[mBufferPos++] = (byte) ((v >> 16) & 0xff);
mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
}
@Override
public void writeLong(long v) throws IOException {
if (mBufferCap - mBufferPos < 8) drain();
int i = (int) (v >> 32);
mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
i = (int) v;
mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
}
@Override
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
@Override
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
@Override
public void writeBytes(String s) throws IOException {
// Callers should use writeUTF()
throw new UnsupportedOperationException();
}
@Override
public void writeChars(String s) throws IOException {
// Callers should use writeUTF()
throw new UnsupportedOperationException();
}
}

View File

@@ -1,110 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.io.UTFDataFormatException;
public class ModifiedUtf8 {
/**
* Decodes a byte array containing <i>modified UTF-8</i> bytes into a string.
*
* <p>Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
* that's what the RI does too.
*/
public static String decode(byte[] in, char[] out, int offset, int utfSize)
throws UTFDataFormatException {
int count = 0, s = 0, a;
while (count < utfSize) {
if ((out[s] = (char) in[offset + count++]) < '\u0080') {
s++;
} else if (((a = out[s]) & 0xe0) == 0xc0) {
if (count >= utfSize) {
throw new UTFDataFormatException("bad second byte at " + count);
}
int b = in[offset + count++];
if ((b & 0xC0) != 0x80) {
throw new UTFDataFormatException("bad second byte at " + (count - 1));
}
out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
} else if ((a & 0xf0) == 0xe0) {
if (count + 1 >= utfSize) {
throw new UTFDataFormatException("bad third byte at " + (count + 1));
}
int b = in[offset + count++];
int c = in[offset + count++];
if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
}
out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
} else {
throw new UTFDataFormatException("bad byte at " + (count - 1));
}
}
return new String(out, 0, s);
}
/**
* Returns the number of bytes the modified UTF-8 representation of 's' would take. Note
* that this is just the space for the bytes representing the characters, not the length
* which precedes those bytes, because different callers represent the length differently,
* as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
* exception if the string is too long for its length to be represented by a short.
*/
public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
long result = 0;
final int length = s.length();
for (int i = 0; i < length; ++i) {
char ch = s.charAt(i);
if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
++result;
} else if (ch <= 2047) {
result += 2;
} else {
result += 3;
}
if (shortLength && result > 65535) {
throw new UTFDataFormatException("String more than 65535 UTF bytes long");
}
}
return result;
}
/**
* Encodes the <i>modified UTF-8</i> bytes corresponding to string {@code s} into the
* byte array {@code dst}, starting at the given {@code offset}.
*/
public static void encode(byte[] dst, int offset, String s) {
final int length = s.length();
for (int i = 0; i < length; i++) {
char ch = s.charAt(i);
if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
dst[offset++] = (byte) ch;
} else if (ch <= 2047) {
dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
dst[offset++] = (byte) (0x80 | (0x3f & ch));
} else {
dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
dst[offset++] = (byte) (0x80 | (0x3f & ch));
}
}
}
private ModifiedUtf8() {
}
}

View File

@@ -1,328 +0,0 @@
/*
* 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.modules.utils;
import android.annotation.NonNull;
import android.annotation.Nullable;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Specialization of {@link XmlPullParser} which adds explicit methods to
* support consistent and efficient conversion of primitive data types.
*/
public interface TypedXmlPullParser extends XmlPullParser {
/**
* @return index of requested attribute, otherwise {@code -1} if undefined
*/
default int getAttributeIndex(@Nullable String namespace, @NonNull String name) {
final boolean namespaceNull = (namespace == null);
final int count = getAttributeCount();
for (int i = 0; i < count; i++) {
if ((namespaceNull || namespace.equals(getAttributeNamespace(i)))
&& name.equals(getAttributeName(i))) {
return i;
}
}
return -1;
}
/**
* @return index of requested attribute
* @throws XmlPullParserException if the value is undefined
*/
default int getAttributeIndexOrThrow(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
final int index = getAttributeIndex(namespace, name);
if (index == -1) {
throw new XmlPullParserException("Missing attribute " + name);
} else {
return index;
}
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
@NonNull byte[] getAttributeBytesHex(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
@NonNull byte[] getAttributeBytesBase64(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
int getAttributeInt(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
int getAttributeIntHex(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
long getAttributeLong(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
long getAttributeLongHex(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
float getAttributeFloat(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
double getAttributeDouble(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed
*/
boolean getAttributeBoolean(int index) throws XmlPullParserException;
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default @NonNull byte[] getAttributeBytesHex(@Nullable String namespace,
@NonNull String name) throws XmlPullParserException {
return getAttributeBytesHex(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default @NonNull byte[] getAttributeBytesBase64(@Nullable String namespace,
@NonNull String name) throws XmlPullParserException {
return getAttributeBytesBase64(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default int getAttributeInt(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeInt(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default int getAttributeIntHex(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeIntHex(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default long getAttributeLong(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeLong(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default long getAttributeLongHex(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeLongHex(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default float getAttributeFloat(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeFloat(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default double getAttributeDouble(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeDouble(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}
* @throws XmlPullParserException if the value is malformed or undefined
*/
default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name)
throws XmlPullParserException {
return getAttributeBoolean(getAttributeIndexOrThrow(namespace, name));
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
* default value if the value is malformed or undefined
*/
default @Nullable byte[] getAttributeBytesHex(@Nullable String namespace,
@NonNull String name, @Nullable byte[] defaultValue) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeBytesHex(index);
} catch (Exception ignored) {
return defaultValue;
}
}
/**
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
* default value if the value is malformed or undefined
*/
default @Nullable byte[] getAttributeBytesBase64(@Nullable String namespace,
@NonNull String name, @Nullable byte[] defaultValue) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeBytesBase64(index);
} catch (Exception ignored) {
return defaultValue;
}
}
/**
* @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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeInt(index);
} 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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeIntHex(index);
} 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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeLong(index);
} 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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeLongHex(index);
} 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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeFloat(index);
} 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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeDouble(index);
} 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) {
final int index = getAttributeIndex(namespace, name);
if (index == -1) return defaultValue;
try {
return getAttributeBoolean(index);
} catch (Exception ignored) {
return defaultValue;
}
}
}

View File

@@ -1,101 +0,0 @@
/*
* 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.modules.utils;
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.
*/
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,
@NonNull 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,
@NonNull 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,
@NonNull 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;
}

View File

@@ -23,6 +23,9 @@ import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.util.ExceptionUtils;
import com.android.modules.utils.FastDataInput;
import com.android.modules.utils.FastDataOutput;
import libcore.util.HexEncoding;
import org.junit.Assume;