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