/* * Copyright (C) 2006 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.content.res; import static android.content.res.Resources.ID_NULL; import static android.system.OsConstants.EINVAL; import android.annotation.AnyRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; import java.io.Reader; /** * Wrapper around a compiled XML file. * * {@hide} */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class XmlBlock implements AutoCloseable { private static final boolean DEBUG=false; @UnsupportedAppUsage public XmlBlock(byte[] data) { mAssets = null; mNative = nativeCreate(data, 0, data.length); mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } public XmlBlock(byte[] data, int offset, int size) { mAssets = null; mNative = nativeCreate(data, offset, size); mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } @Override public void close() { synchronized (this) { if (mOpen) { mOpen = false; decOpenCountLocked(); } } } private void decOpenCountLocked() { mOpenCount--; if (mOpenCount == 0) { mStrings.close(); nativeDestroy(mNative); mNative = 0; if (mAssets != null) { mAssets.xmlBlockGone(hashCode()); } } } @UnsupportedAppUsage public XmlResourceParser newParser() { return newParser(ID_NULL); } public XmlResourceParser newParser(@AnyRes int resId) { synchronized (this) { if (mNative != 0) { return new Parser(nativeCreateParseState(mNative, resId), this); } return null; } } /** * Returns a XmlResourceParser that validates the xml using the given validator. */ public XmlResourceParser newParser(@AnyRes int resId, Validator validator) { synchronized (this) { if (mNative != 0) { return new Parser(nativeCreateParseState(mNative, resId), this, validator); } return null; } } /** * Reference Error.h UNEXPECTED_NULL */ private static final int ERROR_NULL_DOCUMENT = Integer.MIN_VALUE + 8; /** * The reason not to ResXMLParser::BAD_DOCUMENT which is -1 is that other places use the same * value. Reference Error.h BAD_VALUE = -EINVAL */ private static final int ERROR_BAD_DOCUMENT = -EINVAL; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class Parser implements XmlResourceParser { Validator mValidator; Parser(long parseState, XmlBlock block) { mParseState = parseState; mBlock = block; block.mOpenCount++; } Parser(long parseState, XmlBlock block, Validator validator) { this(parseState, block); mValidator = validator; } @AnyRes public int getSourceResId() { return nativeGetSourceResId(mParseState); } public void setFeature(String name, boolean state) throws XmlPullParserException { if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { return; } if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) { return; } throw new XmlPullParserException("Unsupported feature: " + name); } public boolean getFeature(String name) { if (FEATURE_PROCESS_NAMESPACES.equals(name)) { return true; } if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) { return true; } return false; } public void setProperty(String name, Object value) throws XmlPullParserException { throw new XmlPullParserException("setProperty() not supported"); } public Object getProperty(String name) { return null; } public void setInput(Reader in) throws XmlPullParserException { throw new XmlPullParserException("setInput() not supported"); } public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException { throw new XmlPullParserException("setInput() not supported"); } public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException { throw new XmlPullParserException("defineEntityReplacementText() not supported"); } public String getNamespacePrefix(int pos) throws XmlPullParserException { throw new XmlPullParserException("getNamespacePrefix() not supported"); } public String getInputEncoding() { return null; } public String getNamespace(String prefix) { throw new RuntimeException("getNamespace() not supported"); } public int getNamespaceCount(int depth) throws XmlPullParserException { throw new XmlPullParserException("getNamespaceCount() not supported"); } public String getPositionDescription() { return "Binary XML file line #" + getLineNumber(); } public String getNamespaceUri(int pos) throws XmlPullParserException { throw new XmlPullParserException("getNamespaceUri() not supported"); } public int getColumnNumber() { return -1; } public int getDepth() { return mDepth; } @Nullable public String getText() { int id = nativeGetText(mParseState); return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } public int getLineNumber() { final int lineNumber = nativeGetLineNumber(mParseState); if (lineNumber == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return lineNumber; } public int getEventType() throws XmlPullParserException { return mEventType; } public boolean isWhitespace() throws XmlPullParserException { // whitespace was stripped by aapt. return false; } public String getPrefix() { throw new RuntimeException("getPrefix not supported"); } public char[] getTextCharacters(int[] holderForStartAndLength) { String txt = getText(); char[] chars = null; if (txt != null) { holderForStartAndLength[0] = 0; holderForStartAndLength[1] = txt.length(); chars = new char[txt.length()]; txt.getChars(0, txt.length(), chars, 0); } return chars; } @Nullable public String getNamespace() { int id = nativeGetNamespace(mParseState); return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : ""; } @Nullable public String getName() { int id = nativeGetName(mParseState); return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } @NonNull public String getAttributeNamespace(int index) { final int id = nativeGetAttributeNamespace(mParseState, index); if (id == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id); if (id >= 0) return getSequenceString(mStrings.getSequence(id)); else if (id == -1) return ""; throw new IndexOutOfBoundsException(String.valueOf(index)); } @NonNull public String getAttributeName(int index) { final int id = nativeGetAttributeName(mParseState, index); if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id); if (id == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } if (id >= 0) return getSequenceString(mStrings.getSequence(id)); throw new IndexOutOfBoundsException(String.valueOf(index)); } public String getAttributePrefix(int index) { throw new RuntimeException("getAttributePrefix not supported"); } public boolean isEmptyElementTag() throws XmlPullParserException { // XXX Need to detect this. return false; } public int getAttributeCount() { if (mEventType == START_TAG) { final int count = nativeGetAttributeCount(mParseState); if (count == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return count; } else { return -1; } } @NonNull public String getAttributeValue(int index) { final int id = nativeGetAttributeStringValue(mParseState, index); if (id == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id); if (id >= 0) return getSequenceString(mStrings.getSequence(id)); // May be some other type... check and try to convert if so. final int t = nativeGetAttributeDataType(mParseState, index); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } if (t == TypedValue.TYPE_NULL) { throw new IndexOutOfBoundsException(String.valueOf(index)); } final int v = nativeGetAttributeData(mParseState, index); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return TypedValue.coerceToString(t, v); } public String getAttributeType(int index) { return "CDATA"; } public boolean isAttributeDefault(int index) { return false; } public int nextToken() throws XmlPullParserException,IOException { return next(); } public String getAttributeValue(String namespace, String name) { int idx = nativeGetAttributeIndex(mParseState, namespace, name); if (idx >= 0) { if (DEBUG) System.out.println("getAttributeName of " + namespace + ":" + name + " index = " + idx); if (DEBUG) System.out.println( "Namespace=" + getAttributeNamespace(idx) + "Name=" + getAttributeName(idx) + ", Value=" + getAttributeValue(idx)); String value = getAttributeValue(idx); if (mValidator != null) { mValidator.validateStrAttr(this, name, value); } return value; } return null; } public int next() throws XmlPullParserException,IOException { if (!mStarted) { mStarted = true; return START_DOCUMENT; } if (mParseState == 0) { return END_DOCUMENT; } int ev = nativeNext(mParseState); if (ev == ERROR_BAD_DOCUMENT) { throw new XmlPullParserException("Corrupt XML binary file"); } if (mDecNextDepth) { mDepth--; mDecNextDepth = false; } switch (ev) { case START_TAG: mDepth++; break; case END_TAG: mDecNextDepth = true; break; } mEventType = ev; if (mValidator != null) { mValidator.validate(this); } if (ev == END_DOCUMENT) { // Automatically close the parse when we reach the end of // a document, since the standard XmlPullParser interface // doesn't have such an API so most clients will leave us // dangling. close(); } return ev; } public void require(int type, String namespace, String name) throws XmlPullParserException,IOException { if (type != getEventType() || (namespace != null && !namespace.equals( getNamespace () ) ) || (name != null && !name.equals( getName() ) ) ) throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription()); } public String nextText() throws XmlPullParserException,IOException { if(getEventType() != START_TAG) { throw new XmlPullParserException( getPositionDescription() + ": parser must be on START_TAG to read next text", this, null); } int eventType = next(); if(eventType == TEXT) { String result = getText(); eventType = next(); if(eventType != END_TAG) { throw new XmlPullParserException( getPositionDescription() + ": event TEXT it must be immediately followed by END_TAG", this, null); } return result; } else if(eventType == END_TAG) { return ""; } else { throw new XmlPullParserException( getPositionDescription() + ": parser must be on START_TAG or TEXT to read text", this, null); } } public int nextTag() throws XmlPullParserException,IOException { int eventType = next(); if(eventType == TEXT && isWhitespace()) { // skip whitespace eventType = next(); } if (eventType != START_TAG && eventType != END_TAG) { throw new XmlPullParserException( getPositionDescription() + ": expected start or end tag", this, null); } return eventType; } public int getAttributeNameResource(int index) { final int resourceNameId = nativeGetAttributeResource(mParseState, index); if (resourceNameId == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return resourceNameId; } public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) { int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); if (idx >= 0) { return getAttributeListValue(idx, options, defaultValue); } return defaultValue; } public boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue) { int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); if (idx >= 0) { return getAttributeBooleanValue(idx, defaultValue); } return defaultValue; } public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); if (idx >= 0) { return getAttributeResourceValue(idx, defaultValue); } return defaultValue; } public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); if (idx >= 0) { return getAttributeIntValue(idx, defaultValue); } return defaultValue; } public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); if (idx >= 0) { return getAttributeUnsignedIntValue(idx, defaultValue); } return defaultValue; } public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) { int idx = nativeGetAttributeIndex(mParseState, namespace, attribute); if (idx >= 0) { return getAttributeFloatValue(idx, defaultValue); } return defaultValue; } public int getAttributeListValue(int idx, String[] options, int defaultValue) { final int t = nativeGetAttributeDataType(mParseState, idx); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } final int v = nativeGetAttributeData(mParseState, idx); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } if (t == TypedValue.TYPE_STRING) { return XmlUtils.convertValueToList( mStrings.getSequence(v), options, defaultValue); } return v; } public boolean getAttributeBooleanValue(int idx, boolean defaultValue) { final int t = nativeGetAttributeDataType(mParseState, idx); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { final int v = nativeGetAttributeData(mParseState, idx); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return v != 0; } return defaultValue; } public int getAttributeResourceValue(int idx, int defaultValue) { final int t = nativeGetAttributeDataType(mParseState, idx); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t == TypedValue.TYPE_REFERENCE) { final int v = nativeGetAttributeData(mParseState, idx); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return v; } return defaultValue; } public int getAttributeIntValue(int idx, int defaultValue) { final int t = nativeGetAttributeDataType(mParseState, idx); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { final int v = nativeGetAttributeData(mParseState, idx); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return v; } return defaultValue; } public int getAttributeUnsignedIntValue(int idx, int defaultValue) { int t = nativeGetAttributeDataType(mParseState, idx); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) { final int v = nativeGetAttributeData(mParseState, idx); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return v; } return defaultValue; } public float getAttributeFloatValue(int idx, float defaultValue) { final int t = nativeGetAttributeDataType(mParseState, idx); if (t == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } // Note: don't attempt to convert any other types, because // we want to count on aapt doing the conversion for us. if (t == TypedValue.TYPE_FLOAT) { final int v = nativeGetAttributeData(mParseState, idx); if (v == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return Float.intBitsToFloat(v); } throw new RuntimeException("not a float!"); } @Nullable public String getIdAttribute() { final int id = nativeGetIdAttribute(mParseState); if (id == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } @Nullable public String getClassAttribute() { final int id = nativeGetClassAttribute(mParseState); if (id == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null; } public int getIdAttributeResourceValue(int defaultValue) { //todo: create and use native method return getAttributeResourceValue(null, "id", defaultValue); } public int getStyleAttribute() { final int styleAttributeId = nativeGetStyleAttribute(mParseState); if (styleAttributeId == ERROR_NULL_DOCUMENT) { throw new NullPointerException("Null document"); } return styleAttributeId; } private String getSequenceString(@Nullable CharSequence str) { if (str == null) { // A value of null retrieved from a StringPool indicates that retrieval of the // string failed due to incremental installation. The presence of all the XmlBlock // data is verified when it is created, so this exception must not be possible. throw new IllegalStateException("Retrieving a string from the StringPool of an" + " XmlBlock should never fail"); } return str.toString(); } public void close() { synchronized (mBlock) { if (mParseState != 0) { nativeDestroyParseState(mParseState); mParseState = 0; mBlock.decOpenCountLocked(); } } } protected void finalize() throws Throwable { close(); } @Nullable /*package*/ final CharSequence getPooledString(int id) { return mStrings.getSequence(id); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) /*package*/ long mParseState; @UnsupportedAppUsage private final XmlBlock mBlock; private boolean mStarted = false; private boolean mDecNextDepth = false; private int mDepth = 0; private int mEventType = START_DOCUMENT; } protected void finalize() throws Throwable { close(); } /** * Create from an existing xml block native object. This is * -extremely- dangerous -- only use it if you absolutely know what you * are doing! The given native object must exist for the entire lifetime * of this newly creating XmlBlock. */ XmlBlock(@Nullable AssetManager assets, long xmlBlock) { mAssets = assets; mNative = xmlBlock; mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false); } private @Nullable final AssetManager mAssets; private long mNative; // final, but gets reset on close /*package*/ final StringBlock mStrings; private boolean mOpen = true; private int mOpenCount = 1; private static final native long nativeCreate(byte[] data, int offset, int size); private static final native long nativeGetStringBlock(long obj); private static final native long nativeCreateParseState(long obj, int resId); private static final native void nativeDestroyParseState(long state); private static final native void nativeDestroy(long obj); // ----------- @FastNative ------------------ @FastNative private static native int nativeGetAttributeIndex( long state, String namespace, String name); // ----------- @CriticalNative ------------------ @CriticalNative /*package*/ static final native int nativeNext(long state); @CriticalNative private static final native int nativeGetNamespace(long state); @CriticalNative /*package*/ static final native int nativeGetName(long state); @CriticalNative private static final native int nativeGetText(long state); @CriticalNative private static final native int nativeGetLineNumber(long state); @CriticalNative private static final native int nativeGetAttributeCount(long state); @CriticalNative private static final native int nativeGetAttributeNamespace(long state, int idx); @CriticalNative private static final native int nativeGetAttributeName(long state, int idx); @CriticalNative private static final native int nativeGetAttributeResource(long state, int idx); @CriticalNative private static final native int nativeGetAttributeDataType(long state, int idx); @CriticalNative private static final native int nativeGetAttributeData(long state, int idx); @CriticalNative private static final native int nativeGetAttributeStringValue(long state, int idx); @CriticalNative private static final native int nativeGetIdAttribute(long state); @CriticalNative private static final native int nativeGetClassAttribute(long state); @CriticalNative private static final native int nativeGetStyleAttribute(long state); @CriticalNative private static final native int nativeGetSourceResId(long state); }