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