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