• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.content.res;
18 
19 import static android.content.res.Resources.ID_NULL;
20 
21 import android.annotation.AnyRes;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.compat.annotation.UnsupportedAppUsage;
25 import android.os.Build;
26 import android.util.TypedValue;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.XmlUtils;
30 
31 import dalvik.annotation.optimization.FastNative;
32 
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.Reader;
38 
39 /**
40  * Wrapper around a compiled XML file.
41  *
42  * {@hide}
43  */
44 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
45 public final class XmlBlock implements AutoCloseable {
46     private static final boolean DEBUG=false;
47 
48     @UnsupportedAppUsage
XmlBlock(byte[] data)49     public XmlBlock(byte[] data) {
50         mAssets = null;
51         mNative = nativeCreate(data, 0, data.length);
52         mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
53     }
54 
XmlBlock(byte[] data, int offset, int size)55     public XmlBlock(byte[] data, int offset, int size) {
56         mAssets = null;
57         mNative = nativeCreate(data, offset, size);
58         mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
59     }
60 
61     @Override
close()62     public void close() {
63         synchronized (this) {
64             if (mOpen) {
65                 mOpen = false;
66                 decOpenCountLocked();
67             }
68         }
69     }
70 
decOpenCountLocked()71     private void decOpenCountLocked() {
72         mOpenCount--;
73         if (mOpenCount == 0) {
74             nativeDestroy(mNative);
75             if (mAssets != null) {
76                 mAssets.xmlBlockGone(hashCode());
77             }
78         }
79     }
80 
81     @UnsupportedAppUsage
newParser()82     public XmlResourceParser newParser() {
83         return newParser(ID_NULL);
84     }
85 
newParser(@nyRes int resId)86     public XmlResourceParser newParser(@AnyRes int resId) {
87         synchronized (this) {
88             if (mNative != 0) {
89                 return new Parser(nativeCreateParseState(mNative, resId), this);
90             }
91             return null;
92         }
93     }
94 
95     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
96     public final class Parser implements XmlResourceParser {
Parser(long parseState, XmlBlock block)97         Parser(long parseState, XmlBlock block) {
98             mParseState = parseState;
99             mBlock = block;
100             block.mOpenCount++;
101         }
102 
103         @AnyRes
getSourceResId()104         public int getSourceResId() {
105             return nativeGetSourceResId(mParseState);
106         }
107 
setFeature(String name, boolean state)108         public void setFeature(String name, boolean state) throws XmlPullParserException {
109             if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
110                 return;
111             }
112             if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
113                 return;
114             }
115             throw new XmlPullParserException("Unsupported feature: " + name);
116         }
getFeature(String name)117         public boolean getFeature(String name) {
118             if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
119                 return true;
120             }
121             if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
122                 return true;
123             }
124             return false;
125         }
setProperty(String name, Object value)126         public void setProperty(String name, Object value) throws XmlPullParserException {
127             throw new XmlPullParserException("setProperty() not supported");
128         }
getProperty(String name)129         public Object getProperty(String name) {
130             return null;
131         }
setInput(Reader in)132         public void setInput(Reader in) throws XmlPullParserException {
133             throw new XmlPullParserException("setInput() not supported");
134         }
setInput(InputStream inputStream, String inputEncoding)135         public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
136             throw new XmlPullParserException("setInput() not supported");
137         }
defineEntityReplacementText(String entityName, String replacementText)138         public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
139             throw new XmlPullParserException("defineEntityReplacementText() not supported");
140         }
getNamespacePrefix(int pos)141         public String getNamespacePrefix(int pos) throws XmlPullParserException {
142             throw new XmlPullParserException("getNamespacePrefix() not supported");
143         }
getInputEncoding()144         public String getInputEncoding() {
145             return null;
146         }
getNamespace(String prefix)147         public String getNamespace(String prefix) {
148             throw new RuntimeException("getNamespace() not supported");
149         }
getNamespaceCount(int depth)150         public int getNamespaceCount(int depth) throws XmlPullParserException {
151             throw new XmlPullParserException("getNamespaceCount() not supported");
152         }
getPositionDescription()153         public String getPositionDescription() {
154             return "Binary XML file line #" + getLineNumber();
155         }
getNamespaceUri(int pos)156         public String getNamespaceUri(int pos) throws XmlPullParserException {
157             throw new XmlPullParserException("getNamespaceUri() not supported");
158         }
getColumnNumber()159         public int getColumnNumber() {
160             return -1;
161         }
getDepth()162         public int getDepth() {
163             return mDepth;
164         }
165         @Nullable
getText()166         public String getText() {
167             int id = nativeGetText(mParseState);
168             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
169         }
getLineNumber()170         public int getLineNumber() {
171             return nativeGetLineNumber(mParseState);
172         }
getEventType()173         public int getEventType() throws XmlPullParserException {
174             return mEventType;
175         }
isWhitespace()176         public boolean isWhitespace() throws XmlPullParserException {
177             // whitespace was stripped by aapt.
178             return false;
179         }
getPrefix()180         public String getPrefix() {
181             throw new RuntimeException("getPrefix not supported");
182         }
getTextCharacters(int[] holderForStartAndLength)183         public char[] getTextCharacters(int[] holderForStartAndLength) {
184             String txt = getText();
185             char[] chars = null;
186             if (txt != null) {
187                 holderForStartAndLength[0] = 0;
188                 holderForStartAndLength[1] = txt.length();
189                 chars = new char[txt.length()];
190                 txt.getChars(0, txt.length(), chars, 0);
191             }
192             return chars;
193         }
194         @Nullable
getNamespace()195         public String getNamespace() {
196             int id = nativeGetNamespace(mParseState);
197             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : "";
198         }
199         @Nullable
getName()200         public String getName() {
201             int id = nativeGetName(mParseState);
202             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
203         }
204         @NonNull
getAttributeNamespace(int index)205         public String getAttributeNamespace(int index) {
206             int id = nativeGetAttributeNamespace(mParseState, index);
207             if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id);
208             if (id >= 0) return getSequenceString(mStrings.getSequence(id));
209             else if (id == -1) return "";
210             throw new IndexOutOfBoundsException(String.valueOf(index));
211         }
212         @NonNull
getAttributeName(int index)213         public String getAttributeName(int index) {
214             int id = nativeGetAttributeName(mParseState, index);
215             if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id);
216             if (id >= 0) return getSequenceString(mStrings.getSequence(id));
217             throw new IndexOutOfBoundsException(String.valueOf(index));
218         }
getAttributePrefix(int index)219         public String getAttributePrefix(int index) {
220             throw new RuntimeException("getAttributePrefix not supported");
221         }
isEmptyElementTag()222         public boolean isEmptyElementTag() throws XmlPullParserException {
223             // XXX Need to detect this.
224             return false;
225         }
getAttributeCount()226         public int getAttributeCount() {
227             return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1;
228         }
229         @NonNull
getAttributeValue(int index)230         public String getAttributeValue(int index) {
231             int id = nativeGetAttributeStringValue(mParseState, index);
232             if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id);
233             if (id >= 0) return getSequenceString(mStrings.getSequence(id));
234 
235             // May be some other type...  check and try to convert if so.
236             int t = nativeGetAttributeDataType(mParseState, index);
237             if (t == TypedValue.TYPE_NULL) {
238                 throw new IndexOutOfBoundsException(String.valueOf(index));
239             }
240 
241             int v = nativeGetAttributeData(mParseState, index);
242             return TypedValue.coerceToString(t, v);
243         }
getAttributeType(int index)244         public String getAttributeType(int index) {
245             return "CDATA";
246         }
isAttributeDefault(int index)247         public boolean isAttributeDefault(int index) {
248             return false;
249         }
nextToken()250         public int nextToken() throws XmlPullParserException,IOException {
251             return next();
252         }
getAttributeValue(String namespace, String name)253         public String getAttributeValue(String namespace, String name) {
254             int idx = nativeGetAttributeIndex(mParseState, namespace, name);
255             if (idx >= 0) {
256                 if (DEBUG) System.out.println("getAttributeName of "
257                         + namespace + ":" + name + " index = " + idx);
258                 if (DEBUG) System.out.println(
259                         "Namespace=" + getAttributeNamespace(idx)
260                         + "Name=" + getAttributeName(idx)
261                         + ", Value=" + getAttributeValue(idx));
262                 return getAttributeValue(idx);
263             }
264             return null;
265         }
next()266         public int next() throws XmlPullParserException,IOException {
267             if (!mStarted) {
268                 mStarted = true;
269                 return START_DOCUMENT;
270             }
271             if (mParseState == 0) {
272                 return END_DOCUMENT;
273             }
274             int ev = nativeNext(mParseState);
275             if (mDecNextDepth) {
276                 mDepth--;
277                 mDecNextDepth = false;
278             }
279             switch (ev) {
280             case START_TAG:
281                 mDepth++;
282                 break;
283             case END_TAG:
284                 mDecNextDepth = true;
285                 break;
286             }
287             mEventType = ev;
288             if (ev == END_DOCUMENT) {
289                 // Automatically close the parse when we reach the end of
290                 // a document, since the standard XmlPullParser interface
291                 // doesn't have such an API so most clients will leave us
292                 // dangling.
293                 close();
294             }
295             return ev;
296         }
require(int type, String namespace, String name)297         public void require(int type, String namespace, String name) throws XmlPullParserException,IOException {
298             if (type != getEventType()
299                 || (namespace != null && !namespace.equals( getNamespace () ) )
300                 || (name != null && !name.equals( getName() ) ) )
301                 throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription());
302         }
nextText()303         public String nextText() throws XmlPullParserException,IOException {
304             if(getEventType() != START_TAG) {
305                throw new XmlPullParserException(
306                  getPositionDescription()
307                  + ": parser must be on START_TAG to read next text", this, null);
308             }
309             int eventType = next();
310             if(eventType == TEXT) {
311                String result = getText();
312                eventType = next();
313                if(eventType != END_TAG) {
314                  throw new XmlPullParserException(
315                     getPositionDescription()
316                     + ": event TEXT it must be immediately followed by END_TAG", this, null);
317                 }
318                 return result;
319             } else if(eventType == END_TAG) {
320                return "";
321             } else {
322                throw new XmlPullParserException(
323                  getPositionDescription()
324                  + ": parser must be on START_TAG or TEXT to read text", this, null);
325             }
326         }
nextTag()327         public int nextTag() throws XmlPullParserException,IOException {
328             int eventType = next();
329             if(eventType == TEXT && isWhitespace()) {   // skip whitespace
330                eventType = next();
331             }
332             if (eventType != START_TAG && eventType != END_TAG) {
333                throw new XmlPullParserException(
334                    getPositionDescription()
335                    + ": expected start or end tag", this, null);
336             }
337             return eventType;
338         }
339 
getAttributeNameResource(int index)340         public int getAttributeNameResource(int index) {
341             return nativeGetAttributeResource(mParseState, index);
342         }
343 
getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)344         public int getAttributeListValue(String namespace, String attribute,
345                 String[] options, int defaultValue) {
346             int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
347             if (idx >= 0) {
348                 return getAttributeListValue(idx, options, defaultValue);
349             }
350             return defaultValue;
351         }
getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)352         public boolean getAttributeBooleanValue(String namespace, String attribute,
353                 boolean defaultValue) {
354             int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
355             if (idx >= 0) {
356                 return getAttributeBooleanValue(idx, defaultValue);
357             }
358             return defaultValue;
359         }
getAttributeResourceValue(String namespace, String attribute, int defaultValue)360         public int getAttributeResourceValue(String namespace, String attribute,
361                 int defaultValue) {
362             int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
363             if (idx >= 0) {
364                 return getAttributeResourceValue(idx, defaultValue);
365             }
366             return defaultValue;
367         }
getAttributeIntValue(String namespace, String attribute, int defaultValue)368         public int getAttributeIntValue(String namespace, String attribute,
369                 int defaultValue) {
370             int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
371             if (idx >= 0) {
372                 return getAttributeIntValue(idx, defaultValue);
373             }
374             return defaultValue;
375         }
getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)376         public int getAttributeUnsignedIntValue(String namespace, String attribute,
377                                                 int defaultValue)
378         {
379             int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
380             if (idx >= 0) {
381                 return getAttributeUnsignedIntValue(idx, defaultValue);
382             }
383             return defaultValue;
384         }
getAttributeFloatValue(String namespace, String attribute, float defaultValue)385         public float getAttributeFloatValue(String namespace, String attribute,
386                 float defaultValue) {
387             int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
388             if (idx >= 0) {
389                 return getAttributeFloatValue(idx, defaultValue);
390             }
391             return defaultValue;
392         }
393 
getAttributeListValue(int idx, String[] options, int defaultValue)394         public int getAttributeListValue(int idx,
395                 String[] options, int defaultValue) {
396             int t = nativeGetAttributeDataType(mParseState, idx);
397             int v = nativeGetAttributeData(mParseState, idx);
398             if (t == TypedValue.TYPE_STRING) {
399                 return XmlUtils.convertValueToList(
400                     mStrings.getSequence(v), options, defaultValue);
401             }
402             return v;
403         }
getAttributeBooleanValue(int idx, boolean defaultValue)404         public boolean getAttributeBooleanValue(int idx,
405                 boolean defaultValue) {
406             int t = nativeGetAttributeDataType(mParseState, idx);
407             // Note: don't attempt to convert any other types, because
408             // we want to count on aapt doing the conversion for us.
409             if (t >= TypedValue.TYPE_FIRST_INT &&
410                 t <= TypedValue.TYPE_LAST_INT) {
411                 return nativeGetAttributeData(mParseState, idx) != 0;
412             }
413             return defaultValue;
414         }
getAttributeResourceValue(int idx, int defaultValue)415         public int getAttributeResourceValue(int idx, int defaultValue) {
416             int t = nativeGetAttributeDataType(mParseState, idx);
417             // Note: don't attempt to convert any other types, because
418             // we want to count on aapt doing the conversion for us.
419             if (t == TypedValue.TYPE_REFERENCE) {
420                 return nativeGetAttributeData(mParseState, idx);
421             }
422             return defaultValue;
423         }
getAttributeIntValue(int idx, int defaultValue)424         public int getAttributeIntValue(int idx, int defaultValue) {
425             int t = nativeGetAttributeDataType(mParseState, idx);
426             // Note: don't attempt to convert any other types, because
427             // we want to count on aapt doing the conversion for us.
428             if (t >= TypedValue.TYPE_FIRST_INT &&
429                 t <= TypedValue.TYPE_LAST_INT) {
430                 return nativeGetAttributeData(mParseState, idx);
431             }
432             return defaultValue;
433         }
getAttributeUnsignedIntValue(int idx, int defaultValue)434         public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
435             int t = nativeGetAttributeDataType(mParseState, idx);
436             // Note: don't attempt to convert any other types, because
437             // we want to count on aapt doing the conversion for us.
438             if (t >= TypedValue.TYPE_FIRST_INT &&
439                 t <= TypedValue.TYPE_LAST_INT) {
440                 return nativeGetAttributeData(mParseState, idx);
441             }
442             return defaultValue;
443         }
getAttributeFloatValue(int idx, float defaultValue)444         public float getAttributeFloatValue(int idx, float defaultValue) {
445             int t = nativeGetAttributeDataType(mParseState, idx);
446             // Note: don't attempt to convert any other types, because
447             // we want to count on aapt doing the conversion for us.
448             if (t == TypedValue.TYPE_FLOAT) {
449                 return Float.intBitsToFloat(
450                     nativeGetAttributeData(mParseState, idx));
451             }
452             throw new RuntimeException("not a float!");
453         }
454         @Nullable
getIdAttribute()455         public String getIdAttribute() {
456             int id = nativeGetIdAttribute(mParseState);
457             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
458         }
459         @Nullable
getClassAttribute()460         public String getClassAttribute() {
461             int id = nativeGetClassAttribute(mParseState);
462             return id >= 0 ? getSequenceString(mStrings.getSequence(id)) : null;
463         }
464 
getIdAttributeResourceValue(int defaultValue)465         public int getIdAttributeResourceValue(int defaultValue) {
466             //todo: create and use native method
467             return getAttributeResourceValue(null, "id", defaultValue);
468         }
469 
getStyleAttribute()470         public int getStyleAttribute() {
471             return nativeGetStyleAttribute(mParseState);
472         }
473 
getSequenceString(@ullable CharSequence str)474         private String getSequenceString(@Nullable CharSequence str) {
475             if (str == null) {
476                 // A value of null retrieved from a StringPool indicates that retrieval of the
477                 // string failed due to incremental installation. The presence of all the XmlBlock
478                 // data is verified when it is created, so this exception must not be possible.
479                 throw new IllegalStateException("Retrieving a string from the StringPool of an"
480                         + " XmlBlock should never fail");
481             }
482             return str.toString();
483         }
484 
close()485         public void close() {
486             synchronized (mBlock) {
487                 if (mParseState != 0) {
488                     nativeDestroyParseState(mParseState);
489                     mParseState = 0;
490                     mBlock.decOpenCountLocked();
491                 }
492             }
493         }
494 
finalize()495         protected void finalize() throws Throwable {
496             close();
497         }
498 
499         @Nullable
getPooledString(int id)500         /*package*/ final CharSequence getPooledString(int id) {
501             return mStrings.getSequence(id);
502         }
503 
504         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
505         /*package*/ long mParseState;
506         @UnsupportedAppUsage
507         private final XmlBlock mBlock;
508         private boolean mStarted = false;
509         private boolean mDecNextDepth = false;
510         private int mDepth = 0;
511         private int mEventType = START_DOCUMENT;
512     }
513 
finalize()514     protected void finalize() throws Throwable {
515         close();
516     }
517 
518     /**
519      * Create from an existing xml block native object.  This is
520      * -extremely- dangerous -- only use it if you absolutely know what you
521      *  are doing!  The given native object must exist for the entire lifetime
522      *  of this newly creating XmlBlock.
523      */
XmlBlock(@ullable AssetManager assets, long xmlBlock)524     XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
525         mAssets = assets;
526         mNative = xmlBlock;
527         mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
528     }
529 
530     private @Nullable final AssetManager mAssets;
531     private final long mNative;
532     /*package*/ final StringBlock mStrings;
533     private boolean mOpen = true;
534     private int mOpenCount = 1;
535 
nativeCreate(byte[] data, int offset, int size)536     private static final native long nativeCreate(byte[] data,
537                                                  int offset,
538                                                  int size);
nativeGetStringBlock(long obj)539     private static final native long nativeGetStringBlock(long obj);
nativeCreateParseState(long obj, int resId)540     private static final native long nativeCreateParseState(long obj, int resId);
nativeDestroyParseState(long state)541     private static final native void nativeDestroyParseState(long state);
nativeDestroy(long obj)542     private static final native void nativeDestroy(long obj);
543 
544     // ----------- @FastNative ------------------
545 
546     @FastNative
nativeNext(long state)547     /*package*/ static final native int nativeNext(long state);
548     @FastNative
nativeGetNamespace(long state)549     private static final native int nativeGetNamespace(long state);
550     @FastNative
nativeGetName(long state)551     /*package*/ static final native int nativeGetName(long state);
552     @FastNative
nativeGetText(long state)553     private static final native int nativeGetText(long state);
554     @FastNative
nativeGetLineNumber(long state)555     private static final native int nativeGetLineNumber(long state);
556     @FastNative
nativeGetAttributeCount(long state)557     private static final native int nativeGetAttributeCount(long state);
558     @FastNative
nativeGetAttributeNamespace(long state, int idx)559     private static final native int nativeGetAttributeNamespace(long state, int idx);
560     @FastNative
nativeGetAttributeName(long state, int idx)561     private static final native int nativeGetAttributeName(long state, int idx);
562     @FastNative
nativeGetAttributeResource(long state, int idx)563     private static final native int nativeGetAttributeResource(long state, int idx);
564     @FastNative
nativeGetAttributeDataType(long state, int idx)565     private static final native int nativeGetAttributeDataType(long state, int idx);
566     @FastNative
nativeGetAttributeData(long state, int idx)567     private static final native int nativeGetAttributeData(long state, int idx);
568     @FastNative
nativeGetAttributeStringValue(long state, int idx)569     private static final native int nativeGetAttributeStringValue(long state, int idx);
570     @FastNative
nativeGetIdAttribute(long state)571     private static final native int nativeGetIdAttribute(long state);
572     @FastNative
nativeGetClassAttribute(long state)573     private static final native int nativeGetClassAttribute(long state);
574     @FastNative
nativeGetStyleAttribute(long state)575     private static final native int nativeGetStyleAttribute(long state);
576     @FastNative
nativeGetAttributeIndex(long state, String namespace, String name)577     private static final native int nativeGetAttributeIndex(long state, String namespace, String name);
578     @FastNative
nativeGetSourceResId(long state)579     private static final native int nativeGetSourceResId(long state);
580 }
581