1 // 2 // Copyright 2006 The Android Open Source Project 3 // 4 // Build resource files from raw assets. 5 // 6 7 #include "ResourceTable.h" 8 9 #include "AaptUtil.h" 10 #include "XMLNode.h" 11 #include "ResourceFilter.h" 12 #include "ResourceIdCache.h" 13 #include "SdkConstants.h" 14 15 #include <algorithm> 16 #include <androidfw/ResourceTypes.h> 17 #include <utils/ByteOrder.h> 18 #include <utils/TypeHelpers.h> 19 #include <stdarg.h> 20 21 // SSIZE: mingw does not have signed size_t == ssize_t. 22 // STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary. 23 #if !defined(_WIN32) 24 # define SSIZE(x) x 25 # define STATUST(x) x 26 #else 27 # define SSIZE(x) (signed size_t)x 28 # define STATUST(x) (status_t)x 29 #endif 30 31 // Set to true for noisy debug output. 32 static const bool kIsDebug = false; 33 34 #if PRINT_STRING_METRICS 35 static const bool kPrintStringMetrics = true; 36 #else 37 static const bool kPrintStringMetrics = false; 38 #endif 39 40 static const char* kAttrPrivateType = "^attr-private"; 41 42 status_t compileXmlFile(const Bundle* bundle, 43 const sp<AaptAssets>& assets, 44 const String16& resourceName, 45 const sp<AaptFile>& target, 46 ResourceTable* table, 47 int options) 48 { 49 sp<XMLNode> root = XMLNode::parse(target); 50 if (root == NULL) { 51 return UNKNOWN_ERROR; 52 } 53 54 return compileXmlFile(bundle, assets, resourceName, root, target, table, options); 55 } 56 57 status_t compileXmlFile(const Bundle* bundle, 58 const sp<AaptAssets>& assets, 59 const String16& resourceName, 60 const sp<AaptFile>& target, 61 const sp<AaptFile>& outTarget, 62 ResourceTable* table, 63 int options) 64 { 65 sp<XMLNode> root = XMLNode::parse(target); 66 if (root == NULL) { 67 return UNKNOWN_ERROR; 68 } 69 70 return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options); 71 } 72 73 status_t compileXmlFile(const Bundle* bundle, 74 const sp<AaptAssets>& assets, 75 const String16& resourceName, 76 const sp<XMLNode>& root, 77 const sp<AaptFile>& target, 78 ResourceTable* table, 79 int options) 80 { 81 if (table->versionForCompat(bundle, resourceName, target, root)) { 82 // The file was versioned, so stop processing here. 83 // The resource entry has already been removed and the new one added. 84 // Remove the assets entry. 85 sp<AaptDir> resDir = assets->getDirs().valueFor(String8("res")); 86 sp<AaptDir> dir = resDir->getDirs().valueFor(target->getGroupEntry().toDirName( 87 target->getResourceType())); 88 dir->removeFile(target->getPath().getPathLeaf()); 89 return NO_ERROR; 90 } 91 92 if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) { 93 root->removeWhitespace(true, NULL); 94 } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) { 95 root->removeWhitespace(false, NULL); 96 } 97 98 if ((options&XML_COMPILE_UTF8) != 0) { 99 root->setUTF8(true); 100 } 101 102 if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) { 103 return UNKNOWN_ERROR; 104 } 105 106 bool hasErrors = false; 107 if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { 108 status_t err = root->assignResourceIds(assets, table); 109 if (err != NO_ERROR) { 110 hasErrors = true; 111 } 112 } 113 114 if ((options&XML_COMPILE_PARSE_VALUES) != 0) { 115 status_t err = root->parseValues(assets, table); 116 if (err != NO_ERROR) { 117 hasErrors = true; 118 } 119 } 120 121 if (hasErrors) { 122 return UNKNOWN_ERROR; 123 } 124 125 if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) { 126 return UNKNOWN_ERROR; 127 } 128 129 if (kIsDebug) { 130 printf("Input XML Resource:\n"); 131 root->print(); 132 } 133 status_t err = root->flatten(target, 134 (options&XML_COMPILE_STRIP_COMMENTS) != 0, 135 (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); 136 if (err != NO_ERROR) { 137 return err; 138 } 139 140 if (kIsDebug) { 141 printf("Output XML Resource:\n"); 142 ResXMLTree tree; 143 tree.setTo(target->getData(), target->getSize()); 144 printXMLBlock(&tree); 145 } 146 147 target->setCompressionMethod(ZipEntry::kCompressDeflated); 148 149 return err; 150 } 151 152 struct flag_entry 153 { 154 const char16_t* name; 155 size_t nameLen; 156 uint32_t value; 157 const char* description; 158 }; 159 160 static const char16_t referenceArray[] = 161 { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' }; 162 static const char16_t stringArray[] = 163 { 's', 't', 'r', 'i', 'n', 'g' }; 164 static const char16_t integerArray[] = 165 { 'i', 'n', 't', 'e', 'g', 'e', 'r' }; 166 static const char16_t booleanArray[] = 167 { 'b', 'o', 'o', 'l', 'e', 'a', 'n' }; 168 static const char16_t colorArray[] = 169 { 'c', 'o', 'l', 'o', 'r' }; 170 static const char16_t floatArray[] = 171 { 'f', 'l', 'o', 'a', 't' }; 172 static const char16_t dimensionArray[] = 173 { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' }; 174 static const char16_t fractionArray[] = 175 { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' }; 176 static const char16_t enumArray[] = 177 { 'e', 'n', 'u', 'm' }; 178 static const char16_t flagsArray[] = 179 { 'f', 'l', 'a', 'g', 's' }; 180 181 static const flag_entry gFormatFlags[] = { 182 { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE, 183 "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n" 184 "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."}, 185 { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING, 186 "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." }, 187 { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER, 188 "an integer value, such as \"<code>100</code>\"." }, 189 { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN, 190 "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." }, 191 { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR, 192 "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n" 193 "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." }, 194 { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT, 195 "a floating point value, such as \"<code>1.2</code>\"."}, 196 { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION, 197 "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n" 198 "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n" 199 "in (inches), mm (millimeters)." }, 200 { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION, 201 "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n" 202 "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n" 203 "some parent container." }, 204 { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL }, 205 { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL }, 206 { NULL, 0, 0, NULL } 207 }; 208 209 static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' }; 210 211 static const flag_entry l10nRequiredFlags[] = { 212 { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL }, 213 { NULL, 0, 0, NULL } 214 }; 215 216 static const char16_t nulStr[] = { 0 }; 217 218 static uint32_t parse_flags(const char16_t* str, size_t len, 219 const flag_entry* flags, bool* outError = NULL) 220 { 221 while (len > 0 && isspace(*str)) { 222 str++; 223 len--; 224 } 225 while (len > 0 && isspace(str[len-1])) { 226 len--; 227 } 228 229 const char16_t* const end = str + len; 230 uint32_t value = 0; 231 232 while (str < end) { 233 const char16_t* div = str; 234 while (div < end && *div != '|') { 235 div++; 236 } 237 238 const flag_entry* cur = flags; 239 while (cur->name) { 240 if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) { 241 value |= cur->value; 242 break; 243 } 244 cur++; 245 } 246 247 if (!cur->name) { 248 if (outError) *outError = true; 249 return 0; 250 } 251 252 str = div < end ? div+1 : div; 253 } 254 255 if (outError) *outError = false; 256 return value; 257 } 258 259 static String16 mayOrMust(int type, int flags) 260 { 261 if ((type&(~flags)) == 0) { 262 return String16("<p>Must"); 263 } 264 265 return String16("<p>May"); 266 } 267 268 static void appendTypeInfo(ResourceTable* outTable, const String16& pkg, 269 const String16& typeName, const String16& ident, int type, 270 const flag_entry* flags) 271 { 272 bool hadType = false; 273 while (flags->name) { 274 if ((type&flags->value) != 0 && flags->description != NULL) { 275 String16 fullMsg(mayOrMust(type, flags->value)); 276 fullMsg.append(String16(" be ")); 277 fullMsg.append(String16(flags->description)); 278 outTable->appendTypeComment(pkg, typeName, ident, fullMsg); 279 hadType = true; 280 } 281 flags++; 282 } 283 if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) { 284 outTable->appendTypeComment(pkg, typeName, ident, 285 String16("<p>This may also be a reference to a resource (in the form\n" 286 "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n" 287 "theme attribute (in the form\n" 288 "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n" 289 "containing a value of this type.")); 290 } 291 } 292 293 struct PendingAttribute 294 { 295 const String16 myPackage; 296 const SourcePos sourcePos; 297 const bool appendComment; 298 int32_t type; 299 String16 ident; 300 String16 comment; 301 bool hasErrors; 302 bool added; 303 304 PendingAttribute(String16 _package, const sp<AaptFile>& in, 305 ResXMLTree& block, bool _appendComment) 306 : myPackage(_package) 307 , sourcePos(in->getPrintableSource(), block.getLineNumber()) 308 , appendComment(_appendComment) 309 , type(ResTable_map::TYPE_ANY) 310 , hasErrors(false) 311 , added(false) 312 { 313 } 314 315 status_t createIfNeeded(ResourceTable* outTable) 316 { 317 if (added || hasErrors) { 318 return NO_ERROR; 319 } 320 added = true; 321 322 if (!outTable->makeAttribute(myPackage, ident, sourcePos, type, comment, appendComment)) { 323 hasErrors = true; 324 return UNKNOWN_ERROR; 325 } 326 return NO_ERROR; 327 } 328 }; 329 330 static status_t compileAttribute(const sp<AaptFile>& in, 331 ResXMLTree& block, 332 const String16& myPackage, 333 ResourceTable* outTable, 334 String16* outIdent = NULL, 335 bool inStyleable = false) 336 { 337 PendingAttribute attr(myPackage, in, block, inStyleable); 338 339 const String16 attr16("attr"); 340 const String16 id16("id"); 341 342 // Attribute type constants. 343 const String16 enum16("enum"); 344 const String16 flag16("flag"); 345 346 ResXMLTree::event_code_t code; 347 size_t len; 348 status_t err; 349 350 ssize_t identIdx = block.indexOfAttribute(NULL, "name"); 351 if (identIdx >= 0) { 352 attr.ident = String16(block.getAttributeStringValue(identIdx, &len)); 353 if (outIdent) { 354 *outIdent = attr.ident; 355 } 356 } else { 357 attr.sourcePos.error("A 'name' attribute is required for <attr>\n"); 358 attr.hasErrors = true; 359 } 360 361 attr.comment = String16( 362 block.getComment(&len) ? block.getComment(&len) : nulStr); 363 364 ssize_t typeIdx = block.indexOfAttribute(NULL, "format"); 365 if (typeIdx >= 0) { 366 String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len)); 367 attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags); 368 if (attr.type == 0) { 369 attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n", 370 String8(typeStr).string()); 371 attr.hasErrors = true; 372 } 373 attr.createIfNeeded(outTable); 374 } else if (!inStyleable) { 375 // Attribute definitions outside of styleables always define the 376 // attribute as a generic value. 377 attr.createIfNeeded(outTable); 378 } 379 380 //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type); 381 382 ssize_t minIdx = block.indexOfAttribute(NULL, "min"); 383 if (minIdx >= 0) { 384 String16 val = String16(block.getAttributeStringValue(minIdx, &len)); 385 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) { 386 attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n", 387 String8(val).string()); 388 attr.hasErrors = true; 389 } 390 attr.createIfNeeded(outTable); 391 if (!attr.hasErrors) { 392 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident, 393 String16(""), String16("^min"), String16(val), NULL, NULL); 394 if (err != NO_ERROR) { 395 attr.hasErrors = true; 396 } 397 } 398 } 399 400 ssize_t maxIdx = block.indexOfAttribute(NULL, "max"); 401 if (maxIdx >= 0) { 402 String16 val = String16(block.getAttributeStringValue(maxIdx, &len)); 403 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) { 404 attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n", 405 String8(val).string()); 406 attr.hasErrors = true; 407 } 408 attr.createIfNeeded(outTable); 409 if (!attr.hasErrors) { 410 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident, 411 String16(""), String16("^max"), String16(val), NULL, NULL); 412 attr.hasErrors = true; 413 } 414 } 415 416 if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) { 417 attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n"); 418 attr.hasErrors = true; 419 } 420 421 ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization"); 422 if (l10nIdx >= 0) { 423 const char16_t* str = block.getAttributeStringValue(l10nIdx, &len); 424 bool error; 425 uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error); 426 if (error) { 427 attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n", 428 String8(str).string()); 429 attr.hasErrors = true; 430 } 431 attr.createIfNeeded(outTable); 432 if (!attr.hasErrors) { 433 char buf[11]; 434 sprintf(buf, "%d", l10n_required); 435 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident, 436 String16(""), String16("^l10n"), String16(buf), NULL, NULL); 437 if (err != NO_ERROR) { 438 attr.hasErrors = true; 439 } 440 } 441 } 442 443 String16 enumOrFlagsComment; 444 445 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 446 if (code == ResXMLTree::START_TAG) { 447 uint32_t localType = 0; 448 if (strcmp16(block.getElementName(&len), enum16.string()) == 0) { 449 localType = ResTable_map::TYPE_ENUM; 450 } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) { 451 localType = ResTable_map::TYPE_FLAGS; 452 } else { 453 SourcePos(in->getPrintableSource(), block.getLineNumber()) 454 .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n", 455 String8(block.getElementName(&len)).string()); 456 return UNKNOWN_ERROR; 457 } 458 459 attr.createIfNeeded(outTable); 460 461 if (attr.type == ResTable_map::TYPE_ANY) { 462 // No type was explicitly stated, so supplying enum tags 463 // implicitly creates an enum or flag. 464 attr.type = 0; 465 } 466 467 if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) { 468 // Wasn't originally specified as an enum, so update its type. 469 attr.type |= localType; 470 if (!attr.hasErrors) { 471 char numberStr[16]; 472 sprintf(numberStr, "%d", attr.type); 473 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), 474 myPackage, attr16, attr.ident, String16(""), 475 String16("^type"), String16(numberStr), NULL, NULL, true); 476 if (err != NO_ERROR) { 477 attr.hasErrors = true; 478 } 479 } 480 } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) { 481 if (localType == ResTable_map::TYPE_ENUM) { 482 SourcePos(in->getPrintableSource(), block.getLineNumber()) 483 .error("<enum> attribute can not be used inside a flags format\n"); 484 attr.hasErrors = true; 485 } else { 486 SourcePos(in->getPrintableSource(), block.getLineNumber()) 487 .error("<flag> attribute can not be used inside a enum format\n"); 488 attr.hasErrors = true; 489 } 490 } 491 492 String16 itemIdent; 493 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name"); 494 if (itemIdentIdx >= 0) { 495 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len)); 496 } else { 497 SourcePos(in->getPrintableSource(), block.getLineNumber()) 498 .error("A 'name' attribute is required for <enum> or <flag>\n"); 499 attr.hasErrors = true; 500 } 501 502 String16 value; 503 ssize_t valueIdx = block.indexOfAttribute(NULL, "value"); 504 if (valueIdx >= 0) { 505 value = String16(block.getAttributeStringValue(valueIdx, &len)); 506 } else { 507 SourcePos(in->getPrintableSource(), block.getLineNumber()) 508 .error("A 'value' attribute is required for <enum> or <flag>\n"); 509 attr.hasErrors = true; 510 } 511 if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) { 512 SourcePos(in->getPrintableSource(), block.getLineNumber()) 513 .error("Tag <enum> or <flag> 'value' attribute must be a number," 514 " not \"%s\"\n", 515 String8(value).string()); 516 attr.hasErrors = true; 517 } 518 519 if (!attr.hasErrors) { 520 if (enumOrFlagsComment.size() == 0) { 521 enumOrFlagsComment.append(mayOrMust(attr.type, 522 ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)); 523 enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM) 524 ? String16(" be one of the following constant values.") 525 : String16(" be one or more (separated by '|') of the following constant values.")); 526 enumOrFlagsComment.append(String16("</p>\n<table>\n" 527 "<colgroup align=\"left\" />\n" 528 "<colgroup align=\"left\" />\n" 529 "<colgroup align=\"left\" />\n" 530 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>")); 531 } 532 533 enumOrFlagsComment.append(String16("\n<tr><td><code>")); 534 enumOrFlagsComment.append(itemIdent); 535 enumOrFlagsComment.append(String16("</code></td><td>")); 536 enumOrFlagsComment.append(value); 537 enumOrFlagsComment.append(String16("</td><td>")); 538 if (block.getComment(&len)) { 539 enumOrFlagsComment.append(String16(block.getComment(&len))); 540 } 541 enumOrFlagsComment.append(String16("</td></tr>")); 542 543 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), 544 myPackage, 545 attr16, attr.ident, String16(""), 546 itemIdent, value, NULL, NULL, false, true); 547 if (err != NO_ERROR) { 548 attr.hasErrors = true; 549 } 550 } 551 } else if (code == ResXMLTree::END_TAG) { 552 if (strcmp16(block.getElementName(&len), attr16.string()) == 0) { 553 break; 554 } 555 if ((attr.type&ResTable_map::TYPE_ENUM) != 0) { 556 if (strcmp16(block.getElementName(&len), enum16.string()) != 0) { 557 SourcePos(in->getPrintableSource(), block.getLineNumber()) 558 .error("Found tag </%s> where </enum> is expected\n", 559 String8(block.getElementName(&len)).string()); 560 return UNKNOWN_ERROR; 561 } 562 } else { 563 if (strcmp16(block.getElementName(&len), flag16.string()) != 0) { 564 SourcePos(in->getPrintableSource(), block.getLineNumber()) 565 .error("Found tag </%s> where </flag> is expected\n", 566 String8(block.getElementName(&len)).string()); 567 return UNKNOWN_ERROR; 568 } 569 } 570 } 571 } 572 573 if (!attr.hasErrors && attr.added) { 574 appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags); 575 } 576 577 if (!attr.hasErrors && enumOrFlagsComment.size() > 0) { 578 enumOrFlagsComment.append(String16("\n</table>")); 579 outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment); 580 } 581 582 583 return NO_ERROR; 584 } 585 586 bool localeIsDefined(const ResTable_config& config) 587 { 588 return config.locale == 0; 589 } 590 591 status_t parseAndAddBag(Bundle* bundle, 592 const sp<AaptFile>& in, 593 ResXMLTree* block, 594 const ResTable_config& config, 595 const String16& myPackage, 596 const String16& curType, 597 const String16& ident, 598 const String16& parentIdent, 599 const String16& itemIdent, 600 int32_t curFormat, 601 bool isFormatted, 602 const String16& /* product */, 603 PseudolocalizationMethod pseudolocalize, 604 const bool overwrite, 605 ResourceTable* outTable) 606 { 607 status_t err; 608 const String16 item16("item"); 609 610 String16 str; 611 Vector<StringPool::entry_style_span> spans; 612 err = parseStyledString(bundle, in->getPrintableSource().string(), 613 block, item16, &str, &spans, isFormatted, 614 pseudolocalize); 615 if (err != NO_ERROR) { 616 return err; 617 } 618 619 if (kIsDebug) { 620 printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d " 621 " pid=%s, bag=%s, id=%s: %s\n", 622 config.language[0], config.language[1], 623 config.country[0], config.country[1], 624 config.orientation, config.density, 625 String8(parentIdent).string(), 626 String8(ident).string(), 627 String8(itemIdent).string(), 628 String8(str).string()); 629 } 630 631 err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()), 632 myPackage, curType, ident, parentIdent, itemIdent, str, 633 &spans, &config, overwrite, false, curFormat); 634 return err; 635 } 636 637 /* 638 * Returns true if needle is one of the elements in the comma-separated list 639 * haystack, false otherwise. 640 */ 641 bool isInProductList(const String16& needle, const String16& haystack) { 642 const char16_t *needle2 = needle.string(); 643 const char16_t *haystack2 = haystack.string(); 644 size_t needlesize = needle.size(); 645 646 while (*haystack2 != '\0') { 647 if (strncmp16(haystack2, needle2, needlesize) == 0) { 648 if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') { 649 return true; 650 } 651 } 652 653 while (*haystack2 != '\0' && *haystack2 != ',') { 654 haystack2++; 655 } 656 if (*haystack2 == ',') { 657 haystack2++; 658 } 659 } 660 661 return false; 662 } 663 664 /* 665 * A simple container that holds a resource type and name. It is ordered first by type then 666 * by name. 667 */ 668 struct type_ident_pair_t { 669 String16 type; 670 String16 ident; 671 672 type_ident_pair_t() { }; 673 type_ident_pair_t(const String16& t, const String16& i) : type(t), ident(i) { } 674 type_ident_pair_t(const type_ident_pair_t& o) : type(o.type), ident(o.ident) { } 675 inline bool operator < (const type_ident_pair_t& o) const { 676 int cmp = compare_type(type, o.type); 677 if (cmp < 0) { 678 return true; 679 } else if (cmp > 0) { 680 return false; 681 } else { 682 return strictly_order_type(ident, o.ident); 683 } 684 } 685 }; 686 687 688 status_t parseAndAddEntry(Bundle* bundle, 689 const sp<AaptFile>& in, 690 ResXMLTree* block, 691 const ResTable_config& config, 692 const String16& myPackage, 693 const String16& curType, 694 const String16& ident, 695 const String16& curTag, 696 bool curIsStyled, 697 int32_t curFormat, 698 bool isFormatted, 699 const String16& product, 700 PseudolocalizationMethod pseudolocalize, 701 const bool overwrite, 702 KeyedVector<type_ident_pair_t, bool>* skippedResourceNames, 703 ResourceTable* outTable) 704 { 705 status_t err; 706 707 String16 str; 708 Vector<StringPool::entry_style_span> spans; 709 err = parseStyledString(bundle, in->getPrintableSource().string(), block, 710 curTag, &str, curIsStyled ? &spans : NULL, 711 isFormatted, pseudolocalize); 712 713 if (err < NO_ERROR) { 714 return err; 715 } 716 717 /* 718 * If a product type was specified on the command line 719 * and also in the string, and the two are not the same, 720 * return without adding the string. 721 */ 722 723 const char *bundleProduct = bundle->getProduct(); 724 if (bundleProduct == NULL) { 725 bundleProduct = ""; 726 } 727 728 if (product.size() != 0) { 729 /* 730 * If the command-line-specified product is empty, only "default" 731 * matches. Other variants are skipped. This is so generation 732 * of the R.java file when the product is not known is predictable. 733 */ 734 735 if (bundleProduct[0] == '\0') { 736 if (strcmp16(String16("default").string(), product.string()) != 0) { 737 /* 738 * This string has a product other than 'default'. Do not add it, 739 * but record it so that if we do not see the same string with 740 * product 'default' or no product, then report an error. 741 */ 742 skippedResourceNames->replaceValueFor( 743 type_ident_pair_t(curType, ident), true); 744 return NO_ERROR; 745 } 746 } else { 747 /* 748 * The command-line product is not empty. 749 * If the product for this string is on the command-line list, 750 * it matches. "default" also matches, but only if nothing 751 * else has matched already. 752 */ 753 754 if (isInProductList(product, String16(bundleProduct))) { 755 ; 756 } else if (strcmp16(String16("default").string(), product.string()) == 0 && 757 !outTable->hasBagOrEntry(myPackage, curType, ident, config)) { 758 ; 759 } else { 760 return NO_ERROR; 761 } 762 } 763 } 764 765 if (kIsDebug) { 766 printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n", 767 config.language[0], config.language[1], 768 config.country[0], config.country[1], 769 config.orientation, config.density, 770 String8(ident).string(), String8(str).string()); 771 } 772 773 err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()), 774 myPackage, curType, ident, str, &spans, &config, 775 false, curFormat, overwrite); 776 777 return err; 778 } 779 780 status_t compileResourceFile(Bundle* bundle, 781 const sp<AaptAssets>& assets, 782 const sp<AaptFile>& in, 783 const ResTable_config& defParams, 784 const bool overwrite, 785 ResourceTable* outTable) 786 { 787 ResXMLTree block; 788 status_t err = parseXMLResource(in, &block, false, true); 789 if (err != NO_ERROR) { 790 return err; 791 } 792 793 // Top-level tag. 794 const String16 resources16("resources"); 795 796 // Identifier declaration tags. 797 const String16 declare_styleable16("declare-styleable"); 798 const String16 attr16("attr"); 799 800 // Data creation organizational tags. 801 const String16 string16("string"); 802 const String16 drawable16("drawable"); 803 const String16 color16("color"); 804 const String16 bool16("bool"); 805 const String16 integer16("integer"); 806 const String16 dimen16("dimen"); 807 const String16 fraction16("fraction"); 808 const String16 style16("style"); 809 const String16 plurals16("plurals"); 810 const String16 array16("array"); 811 const String16 string_array16("string-array"); 812 const String16 integer_array16("integer-array"); 813 const String16 public16("public"); 814 const String16 public_padding16("public-padding"); 815 const String16 private_symbols16("private-symbols"); 816 const String16 java_symbol16("java-symbol"); 817 const String16 add_resource16("add-resource"); 818 const String16 skip16("skip"); 819 const String16 eat_comment16("eat-comment"); 820 821 // Data creation tags. 822 const String16 bag16("bag"); 823 const String16 item16("item"); 824 825 // Attribute type constants. 826 const String16 enum16("enum"); 827 828 // plural values 829 const String16 other16("other"); 830 const String16 quantityOther16("^other"); 831 const String16 zero16("zero"); 832 const String16 quantityZero16("^zero"); 833 const String16 one16("one"); 834 const String16 quantityOne16("^one"); 835 const String16 two16("two"); 836 const String16 quantityTwo16("^two"); 837 const String16 few16("few"); 838 const String16 quantityFew16("^few"); 839 const String16 many16("many"); 840 const String16 quantityMany16("^many"); 841 842 // useful attribute names and special values 843 const String16 name16("name"); 844 const String16 translatable16("translatable"); 845 const String16 formatted16("formatted"); 846 const String16 false16("false"); 847 848 const String16 myPackage(assets->getPackage()); 849 850 bool hasErrors = false; 851 852 bool fileIsTranslatable = true; 853 if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) { 854 fileIsTranslatable = false; 855 } 856 857 DefaultKeyedVector<String16, uint32_t> nextPublicId(0); 858 859 // Stores the resource names that were skipped. Typically this happens when 860 // AAPT is invoked without a product specified and a resource has no 861 // 'default' product attribute. 862 KeyedVector<type_ident_pair_t, bool> skippedResourceNames; 863 864 ResXMLTree::event_code_t code; 865 do { 866 code = block.next(); 867 } while (code == ResXMLTree::START_NAMESPACE); 868 869 size_t len; 870 if (code != ResXMLTree::START_TAG) { 871 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 872 "No start tag found\n"); 873 return UNKNOWN_ERROR; 874 } 875 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) { 876 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 877 "Invalid start tag %s\n", String8(block.getElementName(&len)).string()); 878 return UNKNOWN_ERROR; 879 } 880 881 ResTable_config curParams(defParams); 882 883 ResTable_config pseudoParams(curParams); 884 pseudoParams.language[0] = 'e'; 885 pseudoParams.language[1] = 'n'; 886 pseudoParams.country[0] = 'X'; 887 pseudoParams.country[1] = 'A'; 888 889 ResTable_config pseudoBidiParams(curParams); 890 pseudoBidiParams.language[0] = 'a'; 891 pseudoBidiParams.language[1] = 'r'; 892 pseudoBidiParams.country[0] = 'X'; 893 pseudoBidiParams.country[1] = 'B'; 894 895 // We should skip resources for pseudolocales if they were 896 // already added automatically. This is a fix for a transition period when 897 // manually pseudolocalized resources may be expected. 898 // TODO: remove this check after next SDK version release. 899 if ((bundle->getPseudolocalize() & PSEUDO_ACCENTED && 900 curParams.locale == pseudoParams.locale) || 901 (bundle->getPseudolocalize() & PSEUDO_BIDI && 902 curParams.locale == pseudoBidiParams.locale)) { 903 SourcePos(in->getPrintableSource(), 0).warning( 904 "Resource file %s is skipped as pseudolocalization" 905 " was done automatically.", 906 in->getPrintableSource().string()); 907 return NO_ERROR; 908 } 909 910 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 911 if (code == ResXMLTree::START_TAG) { 912 const String16* curTag = NULL; 913 String16 curType; 914 String16 curName; 915 int32_t curFormat = ResTable_map::TYPE_ANY; 916 bool curIsBag = false; 917 bool curIsBagReplaceOnOverwrite = false; 918 bool curIsStyled = false; 919 bool curIsPseudolocalizable = false; 920 bool curIsFormatted = fileIsTranslatable; 921 bool localHasErrors = false; 922 923 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { 924 while ((code=block.next()) != ResXMLTree::END_DOCUMENT 925 && code != ResXMLTree::BAD_DOCUMENT) { 926 if (code == ResXMLTree::END_TAG) { 927 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { 928 break; 929 } 930 } 931 } 932 continue; 933 934 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { 935 while ((code=block.next()) != ResXMLTree::END_DOCUMENT 936 && code != ResXMLTree::BAD_DOCUMENT) { 937 if (code == ResXMLTree::END_TAG) { 938 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { 939 break; 940 } 941 } 942 } 943 continue; 944 945 } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) { 946 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); 947 948 String16 type; 949 ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); 950 if (typeIdx < 0) { 951 srcPos.error("A 'type' attribute is required for <public>\n"); 952 hasErrors = localHasErrors = true; 953 } 954 type = String16(block.getAttributeStringValue(typeIdx, &len)); 955 956 String16 name; 957 ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); 958 if (nameIdx < 0) { 959 srcPos.error("A 'name' attribute is required for <public>\n"); 960 hasErrors = localHasErrors = true; 961 } 962 name = String16(block.getAttributeStringValue(nameIdx, &len)); 963 964 uint32_t ident = 0; 965 ssize_t identIdx = block.indexOfAttribute(NULL, "id"); 966 if (identIdx >= 0) { 967 const char16_t* identStr = block.getAttributeStringValue(identIdx, &len); 968 Res_value identValue; 969 if (!ResTable::stringToInt(identStr, len, &identValue)) { 970 srcPos.error("Given 'id' attribute is not an integer: %s\n", 971 String8(block.getAttributeStringValue(identIdx, &len)).string()); 972 hasErrors = localHasErrors = true; 973 } else { 974 ident = identValue.data; 975 nextPublicId.replaceValueFor(type, ident+1); 976 } 977 } else if (nextPublicId.indexOfKey(type) < 0) { 978 srcPos.error("No 'id' attribute supplied <public>," 979 " and no previous id defined in this file.\n"); 980 hasErrors = localHasErrors = true; 981 } else if (!localHasErrors) { 982 ident = nextPublicId.valueFor(type); 983 nextPublicId.replaceValueFor(type, ident+1); 984 } 985 986 if (!localHasErrors) { 987 err = outTable->addPublic(srcPos, myPackage, type, name, ident); 988 if (err < NO_ERROR) { 989 hasErrors = localHasErrors = true; 990 } 991 } 992 if (!localHasErrors) { 993 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); 994 if (symbols != NULL) { 995 symbols = symbols->addNestedSymbol(String8(type), srcPos); 996 } 997 if (symbols != NULL) { 998 symbols->makeSymbolPublic(String8(name), srcPos); 999 String16 comment( 1000 block.getComment(&len) ? block.getComment(&len) : nulStr); 1001 symbols->appendComment(String8(name), comment, srcPos); 1002 } else { 1003 srcPos.error("Unable to create symbols!\n"); 1004 hasErrors = localHasErrors = true; 1005 } 1006 } 1007 1008 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 1009 if (code == ResXMLTree::END_TAG) { 1010 if (strcmp16(block.getElementName(&len), public16.string()) == 0) { 1011 break; 1012 } 1013 } 1014 } 1015 continue; 1016 1017 } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) { 1018 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); 1019 1020 String16 type; 1021 ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); 1022 if (typeIdx < 0) { 1023 srcPos.error("A 'type' attribute is required for <public-padding>\n"); 1024 hasErrors = localHasErrors = true; 1025 } 1026 type = String16(block.getAttributeStringValue(typeIdx, &len)); 1027 1028 String16 name; 1029 ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); 1030 if (nameIdx < 0) { 1031 srcPos.error("A 'name' attribute is required for <public-padding>\n"); 1032 hasErrors = localHasErrors = true; 1033 } 1034 name = String16(block.getAttributeStringValue(nameIdx, &len)); 1035 1036 uint32_t start = 0; 1037 ssize_t startIdx = block.indexOfAttribute(NULL, "start"); 1038 if (startIdx >= 0) { 1039 const char16_t* startStr = block.getAttributeStringValue(startIdx, &len); 1040 Res_value startValue; 1041 if (!ResTable::stringToInt(startStr, len, &startValue)) { 1042 srcPos.error("Given 'start' attribute is not an integer: %s\n", 1043 String8(block.getAttributeStringValue(startIdx, &len)).string()); 1044 hasErrors = localHasErrors = true; 1045 } else { 1046 start = startValue.data; 1047 } 1048 } else if (nextPublicId.indexOfKey(type) < 0) { 1049 srcPos.error("No 'start' attribute supplied <public-padding>," 1050 " and no previous id defined in this file.\n"); 1051 hasErrors = localHasErrors = true; 1052 } else if (!localHasErrors) { 1053 start = nextPublicId.valueFor(type); 1054 } 1055 1056 uint32_t end = 0; 1057 ssize_t endIdx = block.indexOfAttribute(NULL, "end"); 1058 if (endIdx >= 0) { 1059 const char16_t* endStr = block.getAttributeStringValue(endIdx, &len); 1060 Res_value endValue; 1061 if (!ResTable::stringToInt(endStr, len, &endValue)) { 1062 srcPos.error("Given 'end' attribute is not an integer: %s\n", 1063 String8(block.getAttributeStringValue(endIdx, &len)).string()); 1064 hasErrors = localHasErrors = true; 1065 } else { 1066 end = endValue.data; 1067 } 1068 } else { 1069 srcPos.error("No 'end' attribute supplied <public-padding>\n"); 1070 hasErrors = localHasErrors = true; 1071 } 1072 1073 if (end >= start) { 1074 nextPublicId.replaceValueFor(type, end+1); 1075 } else { 1076 srcPos.error("Padding start '%ul' is after end '%ul'\n", 1077 start, end); 1078 hasErrors = localHasErrors = true; 1079 } 1080 1081 String16 comment( 1082 block.getComment(&len) ? block.getComment(&len) : nulStr); 1083 for (uint32_t curIdent=start; curIdent<=end; curIdent++) { 1084 if (localHasErrors) { 1085 break; 1086 } 1087 String16 curName(name); 1088 char buf[64]; 1089 sprintf(buf, "%d", (int)(end-curIdent+1)); 1090 curName.append(String16(buf)); 1091 1092 err = outTable->addEntry(srcPos, myPackage, type, curName, 1093 String16("padding"), NULL, &curParams, false, 1094 ResTable_map::TYPE_STRING, overwrite); 1095 if (err < NO_ERROR) { 1096 hasErrors = localHasErrors = true; 1097 break; 1098 } 1099 err = outTable->addPublic(srcPos, myPackage, type, 1100 curName, curIdent); 1101 if (err < NO_ERROR) { 1102 hasErrors = localHasErrors = true; 1103 break; 1104 } 1105 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); 1106 if (symbols != NULL) { 1107 symbols = symbols->addNestedSymbol(String8(type), srcPos); 1108 } 1109 if (symbols != NULL) { 1110 symbols->makeSymbolPublic(String8(curName), srcPos); 1111 symbols->appendComment(String8(curName), comment, srcPos); 1112 } else { 1113 srcPos.error("Unable to create symbols!\n"); 1114 hasErrors = localHasErrors = true; 1115 } 1116 } 1117 1118 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 1119 if (code == ResXMLTree::END_TAG) { 1120 if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) { 1121 break; 1122 } 1123 } 1124 } 1125 continue; 1126 1127 } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) { 1128 String16 pkg; 1129 ssize_t pkgIdx = block.indexOfAttribute(NULL, "package"); 1130 if (pkgIdx < 0) { 1131 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1132 "A 'package' attribute is required for <private-symbols>\n"); 1133 hasErrors = localHasErrors = true; 1134 } 1135 pkg = String16(block.getAttributeStringValue(pkgIdx, &len)); 1136 if (!localHasErrors) { 1137 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( 1138 "<private-symbols> is deprecated. Use the command line flag " 1139 "--private-symbols instead.\n"); 1140 if (assets->havePrivateSymbols()) { 1141 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( 1142 "private symbol package already specified. Ignoring...\n"); 1143 } else { 1144 assets->setSymbolsPrivatePackage(String8(pkg)); 1145 } 1146 } 1147 1148 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 1149 if (code == ResXMLTree::END_TAG) { 1150 if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) { 1151 break; 1152 } 1153 } 1154 } 1155 continue; 1156 1157 } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) { 1158 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); 1159 1160 String16 type; 1161 ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); 1162 if (typeIdx < 0) { 1163 srcPos.error("A 'type' attribute is required for <public>\n"); 1164 hasErrors = localHasErrors = true; 1165 } 1166 type = String16(block.getAttributeStringValue(typeIdx, &len)); 1167 1168 String16 name; 1169 ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); 1170 if (nameIdx < 0) { 1171 srcPos.error("A 'name' attribute is required for <public>\n"); 1172 hasErrors = localHasErrors = true; 1173 } 1174 name = String16(block.getAttributeStringValue(nameIdx, &len)); 1175 1176 sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R")); 1177 if (symbols != NULL) { 1178 symbols = symbols->addNestedSymbol(String8(type), srcPos); 1179 } 1180 if (symbols != NULL) { 1181 symbols->makeSymbolJavaSymbol(String8(name), srcPos); 1182 String16 comment( 1183 block.getComment(&len) ? block.getComment(&len) : nulStr); 1184 symbols->appendComment(String8(name), comment, srcPos); 1185 } else { 1186 srcPos.error("Unable to create symbols!\n"); 1187 hasErrors = localHasErrors = true; 1188 } 1189 1190 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 1191 if (code == ResXMLTree::END_TAG) { 1192 if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) { 1193 break; 1194 } 1195 } 1196 } 1197 continue; 1198 1199 1200 } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { 1201 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); 1202 1203 String16 typeName; 1204 ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); 1205 if (typeIdx < 0) { 1206 srcPos.error("A 'type' attribute is required for <add-resource>\n"); 1207 hasErrors = localHasErrors = true; 1208 } 1209 typeName = String16(block.getAttributeStringValue(typeIdx, &len)); 1210 1211 String16 name; 1212 ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); 1213 if (nameIdx < 0) { 1214 srcPos.error("A 'name' attribute is required for <add-resource>\n"); 1215 hasErrors = localHasErrors = true; 1216 } 1217 name = String16(block.getAttributeStringValue(nameIdx, &len)); 1218 1219 outTable->canAddEntry(srcPos, myPackage, typeName, name); 1220 1221 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 1222 if (code == ResXMLTree::END_TAG) { 1223 if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { 1224 break; 1225 } 1226 } 1227 } 1228 continue; 1229 1230 } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) { 1231 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); 1232 1233 String16 ident; 1234 ssize_t identIdx = block.indexOfAttribute(NULL, "name"); 1235 if (identIdx < 0) { 1236 srcPos.error("A 'name' attribute is required for <declare-styleable>\n"); 1237 hasErrors = localHasErrors = true; 1238 } 1239 ident = String16(block.getAttributeStringValue(identIdx, &len)); 1240 1241 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); 1242 if (!localHasErrors) { 1243 if (symbols != NULL) { 1244 symbols = symbols->addNestedSymbol(String8("styleable"), srcPos); 1245 } 1246 sp<AaptSymbols> styleSymbols = symbols; 1247 if (symbols != NULL) { 1248 symbols = symbols->addNestedSymbol(String8(ident), srcPos); 1249 } 1250 if (symbols == NULL) { 1251 srcPos.error("Unable to create symbols!\n"); 1252 return UNKNOWN_ERROR; 1253 } 1254 1255 String16 comment( 1256 block.getComment(&len) ? block.getComment(&len) : nulStr); 1257 styleSymbols->appendComment(String8(ident), comment, srcPos); 1258 } else { 1259 symbols = NULL; 1260 } 1261 1262 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { 1263 if (code == ResXMLTree::START_TAG) { 1264 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { 1265 while ((code=block.next()) != ResXMLTree::END_DOCUMENT 1266 && code != ResXMLTree::BAD_DOCUMENT) { 1267 if (code == ResXMLTree::END_TAG) { 1268 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { 1269 break; 1270 } 1271 } 1272 } 1273 continue; 1274 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { 1275 while ((code=block.next()) != ResXMLTree::END_DOCUMENT 1276 && code != ResXMLTree::BAD_DOCUMENT) { 1277 if (code == ResXMLTree::END_TAG) { 1278 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { 1279 break; 1280 } 1281 } 1282 } 1283 continue; 1284 } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) { 1285 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1286 "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n", 1287 String8(block.getElementName(&len)).string()); 1288 return UNKNOWN_ERROR; 1289 } 1290 1291 String16 comment( 1292 block.getComment(&len) ? block.getComment(&len) : nulStr); 1293 String16 itemIdent; 1294 err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true); 1295 if (err != NO_ERROR) { 1296 hasErrors = localHasErrors = true; 1297 } 1298 1299 if (symbols != NULL) { 1300 SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber()); 1301 symbols->addSymbol(String8(itemIdent), 0, srcPos); 1302 symbols->appendComment(String8(itemIdent), comment, srcPos); 1303 //printf("Attribute %s comment: %s\n", String8(itemIdent).string(), 1304 // String8(comment).string()); 1305 } 1306 } else if (code == ResXMLTree::END_TAG) { 1307 if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) { 1308 break; 1309 } 1310 1311 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1312 "Found tag </%s> where </attr> is expected\n", 1313 String8(block.getElementName(&len)).string()); 1314 return UNKNOWN_ERROR; 1315 } 1316 } 1317 continue; 1318 1319 } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) { 1320 err = compileAttribute(in, block, myPackage, outTable, NULL); 1321 if (err != NO_ERROR) { 1322 hasErrors = true; 1323 } 1324 continue; 1325 1326 } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) { 1327 curTag = &item16; 1328 ssize_t attri = block.indexOfAttribute(NULL, "type"); 1329 if (attri >= 0) { 1330 curType = String16(block.getAttributeStringValue(attri, &len)); 1331 ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); 1332 if (nameIdx >= 0) { 1333 curName = String16(block.getAttributeStringValue(nameIdx, &len)); 1334 } 1335 ssize_t formatIdx = block.indexOfAttribute(NULL, "format"); 1336 if (formatIdx >= 0) { 1337 String16 formatStr = String16(block.getAttributeStringValue( 1338 formatIdx, &len)); 1339 curFormat = parse_flags(formatStr.string(), formatStr.size(), 1340 gFormatFlags); 1341 if (curFormat == 0) { 1342 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1343 "Tag <item> 'format' attribute value \"%s\" not valid\n", 1344 String8(formatStr).string()); 1345 hasErrors = localHasErrors = true; 1346 } 1347 } 1348 } else { 1349 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1350 "A 'type' attribute is required for <item>\n"); 1351 hasErrors = localHasErrors = true; 1352 } 1353 curIsStyled = true; 1354 } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) { 1355 // Note the existence and locale of every string we process 1356 char rawLocale[RESTABLE_MAX_LOCALE_LEN]; 1357 curParams.getBcp47Locale(rawLocale); 1358 String8 locale(rawLocale); 1359 String16 name; 1360 String16 translatable; 1361 String16 formatted; 1362 1363 size_t n = block.getAttributeCount(); 1364 for (size_t i = 0; i < n; i++) { 1365 size_t length; 1366 const char16_t* attr = block.getAttributeName(i, &length); 1367 if (strcmp16(attr, name16.string()) == 0) { 1368 name.setTo(block.getAttributeStringValue(i, &length)); 1369 } else if (strcmp16(attr, translatable16.string()) == 0) { 1370 translatable.setTo(block.getAttributeStringValue(i, &length)); 1371 } else if (strcmp16(attr, formatted16.string()) == 0) { 1372 formatted.setTo(block.getAttributeStringValue(i, &length)); 1373 } 1374 } 1375 1376 if (name.size() > 0) { 1377 if (locale.size() == 0) { 1378 outTable->addDefaultLocalization(name); 1379 } 1380 if (translatable == false16) { 1381 curIsFormatted = false; 1382 // Untranslatable strings must only exist in the default [empty] locale 1383 if (locale.size() > 0) { 1384 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( 1385 "string '%s' marked untranslatable but exists in locale '%s'\n", 1386 String8(name).string(), 1387 locale.string()); 1388 // hasErrors = localHasErrors = true; 1389 } else { 1390 // Intentionally empty block: 1391 // 1392 // Don't add untranslatable strings to the localization table; that 1393 // way if we later see localizations of them, they'll be flagged as 1394 // having no default translation. 1395 } 1396 } else { 1397 outTable->addLocalization( 1398 name, 1399 locale, 1400 SourcePos(in->getPrintableSource(), block.getLineNumber())); 1401 } 1402 1403 if (formatted == false16) { 1404 curIsFormatted = false; 1405 } 1406 } 1407 1408 curTag = &string16; 1409 curType = string16; 1410 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; 1411 curIsStyled = true; 1412 curIsPseudolocalizable = fileIsTranslatable && (translatable != false16); 1413 } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) { 1414 curTag = &drawable16; 1415 curType = drawable16; 1416 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR; 1417 } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) { 1418 curTag = &color16; 1419 curType = color16; 1420 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR; 1421 } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) { 1422 curTag = &bool16; 1423 curType = bool16; 1424 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN; 1425 } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) { 1426 curTag = &integer16; 1427 curType = integer16; 1428 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER; 1429 } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) { 1430 curTag = &dimen16; 1431 curType = dimen16; 1432 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION; 1433 } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) { 1434 curTag = &fraction16; 1435 curType = fraction16; 1436 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION; 1437 } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) { 1438 curTag = &bag16; 1439 curIsBag = true; 1440 ssize_t attri = block.indexOfAttribute(NULL, "type"); 1441 if (attri >= 0) { 1442 curType = String16(block.getAttributeStringValue(attri, &len)); 1443 } else { 1444 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1445 "A 'type' attribute is required for <bag>\n"); 1446 hasErrors = localHasErrors = true; 1447 } 1448 } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) { 1449 curTag = &style16; 1450 curType = style16; 1451 curIsBag = true; 1452 } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) { 1453 curTag = &plurals16; 1454 curType = plurals16; 1455 curIsBag = true; 1456 curIsPseudolocalizable = fileIsTranslatable; 1457 } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) { 1458 curTag = &array16; 1459 curType = array16; 1460 curIsBag = true; 1461 curIsBagReplaceOnOverwrite = true; 1462 ssize_t formatIdx = block.indexOfAttribute(NULL, "format"); 1463 if (formatIdx >= 0) { 1464 String16 formatStr = String16(block.getAttributeStringValue( 1465 formatIdx, &len)); 1466 curFormat = parse_flags(formatStr.string(), formatStr.size(), 1467 gFormatFlags); 1468 if (curFormat == 0) { 1469 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1470 "Tag <array> 'format' attribute value \"%s\" not valid\n", 1471 String8(formatStr).string()); 1472 hasErrors = localHasErrors = true; 1473 } 1474 } 1475 } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) { 1476 // Check whether these strings need valid formats. 1477 // (simplified form of what string16 does above) 1478 bool isTranslatable = false; 1479 size_t n = block.getAttributeCount(); 1480 1481 // Pseudolocalizable by default, unless this string array isn't 1482 // translatable. 1483 for (size_t i = 0; i < n; i++) { 1484 size_t length; 1485 const char16_t* attr = block.getAttributeName(i, &length); 1486 if (strcmp16(attr, formatted16.string()) == 0) { 1487 const char16_t* value = block.getAttributeStringValue(i, &length); 1488 if (strcmp16(value, false16.string()) == 0) { 1489 curIsFormatted = false; 1490 } 1491 } else if (strcmp16(attr, translatable16.string()) == 0) { 1492 const char16_t* value = block.getAttributeStringValue(i, &length); 1493 if (strcmp16(value, false16.string()) == 0) { 1494 isTranslatable = false; 1495 } 1496 } 1497 } 1498 1499 curTag = &string_array16; 1500 curType = array16; 1501 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; 1502 curIsBag = true; 1503 curIsBagReplaceOnOverwrite = true; 1504 curIsPseudolocalizable = isTranslatable && fileIsTranslatable; 1505 } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) { 1506 curTag = &integer_array16; 1507 curType = array16; 1508 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER; 1509 curIsBag = true; 1510 curIsBagReplaceOnOverwrite = true; 1511 } else { 1512 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1513 "Found tag %s where item is expected\n", 1514 String8(block.getElementName(&len)).string()); 1515 return UNKNOWN_ERROR; 1516 } 1517 1518 String16 ident; 1519 ssize_t identIdx = block.indexOfAttribute(NULL, "name"); 1520 if (identIdx >= 0) { 1521 ident = String16(block.getAttributeStringValue(identIdx, &len)); 1522 } else { 1523 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1524 "A 'name' attribute is required for <%s>\n", 1525 String8(*curTag).string()); 1526 hasErrors = localHasErrors = true; 1527 } 1528 1529 String16 product; 1530 identIdx = block.indexOfAttribute(NULL, "product"); 1531 if (identIdx >= 0) { 1532 product = String16(block.getAttributeStringValue(identIdx, &len)); 1533 } 1534 1535 String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr); 1536 1537 if (curIsBag) { 1538 // Figure out the parent of this bag... 1539 String16 parentIdent; 1540 ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent"); 1541 if (parentIdentIdx >= 0) { 1542 parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len)); 1543 } else { 1544 ssize_t sep = ident.findLast('.'); 1545 if (sep >= 0) { 1546 parentIdent.setTo(ident, sep); 1547 } 1548 } 1549 1550 if (!localHasErrors) { 1551 err = outTable->startBag(SourcePos(in->getPrintableSource(), 1552 block.getLineNumber()), myPackage, curType, ident, 1553 parentIdent, &curParams, 1554 overwrite, curIsBagReplaceOnOverwrite); 1555 if (err != NO_ERROR) { 1556 hasErrors = localHasErrors = true; 1557 } 1558 } 1559 1560 ssize_t elmIndex = 0; 1561 char elmIndexStr[14]; 1562 while ((code=block.next()) != ResXMLTree::END_DOCUMENT 1563 && code != ResXMLTree::BAD_DOCUMENT) { 1564 1565 if (code == ResXMLTree::START_TAG) { 1566 if (strcmp16(block.getElementName(&len), item16.string()) != 0) { 1567 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1568 "Tag <%s> can not appear inside <%s>, only <item>\n", 1569 String8(block.getElementName(&len)).string(), 1570 String8(*curTag).string()); 1571 return UNKNOWN_ERROR; 1572 } 1573 1574 String16 itemIdent; 1575 if (curType == array16) { 1576 sprintf(elmIndexStr, "^index_%d", (int)elmIndex++); 1577 itemIdent = String16(elmIndexStr); 1578 } else if (curType == plurals16) { 1579 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity"); 1580 if (itemIdentIdx >= 0) { 1581 String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len)); 1582 if (quantity16 == other16) { 1583 itemIdent = quantityOther16; 1584 } 1585 else if (quantity16 == zero16) { 1586 itemIdent = quantityZero16; 1587 } 1588 else if (quantity16 == one16) { 1589 itemIdent = quantityOne16; 1590 } 1591 else if (quantity16 == two16) { 1592 itemIdent = quantityTwo16; 1593 } 1594 else if (quantity16 == few16) { 1595 itemIdent = quantityFew16; 1596 } 1597 else if (quantity16 == many16) { 1598 itemIdent = quantityMany16; 1599 } 1600 else { 1601 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1602 "Illegal 'quantity' attribute is <item> inside <plurals>\n"); 1603 hasErrors = localHasErrors = true; 1604 } 1605 } else { 1606 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1607 "A 'quantity' attribute is required for <item> inside <plurals>\n"); 1608 hasErrors = localHasErrors = true; 1609 } 1610 } else { 1611 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name"); 1612 if (itemIdentIdx >= 0) { 1613 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len)); 1614 } else { 1615 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1616 "A 'name' attribute is required for <item>\n"); 1617 hasErrors = localHasErrors = true; 1618 } 1619 } 1620 1621 ResXMLParser::ResXMLPosition parserPosition; 1622 block.getPosition(&parserPosition); 1623 1624 err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType, 1625 ident, parentIdent, itemIdent, curFormat, curIsFormatted, 1626 product, NO_PSEUDOLOCALIZATION, overwrite, outTable); 1627 if (err == NO_ERROR) { 1628 if (curIsPseudolocalizable && localeIsDefined(curParams) 1629 && bundle->getPseudolocalize() > 0) { 1630 // pseudolocalize here 1631 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) == 1632 PSEUDO_ACCENTED) { 1633 block.setPosition(parserPosition); 1634 err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage, 1635 curType, ident, parentIdent, itemIdent, curFormat, 1636 curIsFormatted, product, PSEUDO_ACCENTED, 1637 overwrite, outTable); 1638 } 1639 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) == 1640 PSEUDO_BIDI) { 1641 block.setPosition(parserPosition); 1642 err = parseAndAddBag(bundle, in, &block, pseudoBidiParams, myPackage, 1643 curType, ident, parentIdent, itemIdent, curFormat, 1644 curIsFormatted, product, PSEUDO_BIDI, 1645 overwrite, outTable); 1646 } 1647 } 1648 } 1649 if (err != NO_ERROR) { 1650 hasErrors = localHasErrors = true; 1651 } 1652 } else if (code == ResXMLTree::END_TAG) { 1653 if (strcmp16(block.getElementName(&len), curTag->string()) != 0) { 1654 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1655 "Found tag </%s> where </%s> is expected\n", 1656 String8(block.getElementName(&len)).string(), 1657 String8(*curTag).string()); 1658 return UNKNOWN_ERROR; 1659 } 1660 break; 1661 } 1662 } 1663 } else { 1664 ResXMLParser::ResXMLPosition parserPosition; 1665 block.getPosition(&parserPosition); 1666 1667 err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident, 1668 *curTag, curIsStyled, curFormat, curIsFormatted, 1669 product, NO_PSEUDOLOCALIZATION, overwrite, &skippedResourceNames, outTable); 1670 1671 if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR? 1672 hasErrors = localHasErrors = true; 1673 } 1674 else if (err == NO_ERROR) { 1675 if (curType == string16 && !curParams.language[0] && !curParams.country[0]) { 1676 outTable->addDefaultLocalization(curName); 1677 } 1678 if (curIsPseudolocalizable && localeIsDefined(curParams) 1679 && bundle->getPseudolocalize() > 0) { 1680 // pseudolocalize here 1681 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) == 1682 PSEUDO_ACCENTED) { 1683 block.setPosition(parserPosition); 1684 err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType, 1685 ident, *curTag, curIsStyled, curFormat, 1686 curIsFormatted, product, 1687 PSEUDO_ACCENTED, overwrite, &skippedResourceNames, outTable); 1688 } 1689 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) == 1690 PSEUDO_BIDI) { 1691 block.setPosition(parserPosition); 1692 err = parseAndAddEntry(bundle, in, &block, pseudoBidiParams, 1693 myPackage, curType, ident, *curTag, curIsStyled, curFormat, 1694 curIsFormatted, product, 1695 PSEUDO_BIDI, overwrite, &skippedResourceNames, outTable); 1696 } 1697 if (err != NO_ERROR) { 1698 hasErrors = localHasErrors = true; 1699 } 1700 } 1701 } 1702 } 1703 1704 #if 0 1705 if (comment.size() > 0) { 1706 printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(), 1707 String8(curType).string(), String8(ident).string(), 1708 String8(comment).string()); 1709 } 1710 #endif 1711 if (!localHasErrors) { 1712 outTable->appendComment(myPackage, curType, ident, comment, false); 1713 } 1714 } 1715 else if (code == ResXMLTree::END_TAG) { 1716 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) { 1717 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1718 "Unexpected end tag %s\n", String8(block.getElementName(&len)).string()); 1719 return UNKNOWN_ERROR; 1720 } 1721 } 1722 else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) { 1723 } 1724 else if (code == ResXMLTree::TEXT) { 1725 if (isWhitespace(block.getText(&len))) { 1726 continue; 1727 } 1728 SourcePos(in->getPrintableSource(), block.getLineNumber()).error( 1729 "Found text \"%s\" where item tag is expected\n", 1730 String8(block.getText(&len)).string()); 1731 return UNKNOWN_ERROR; 1732 } 1733 } 1734 1735 // For every resource defined, there must be exist one variant with a product attribute 1736 // set to 'default' (or no product attribute at all). 1737 // We check to see that for every resource that was ignored because of a mismatched 1738 // product attribute, some product variant of that resource was processed. 1739 for (size_t i = 0; i < skippedResourceNames.size(); i++) { 1740 if (skippedResourceNames[i]) { 1741 const type_ident_pair_t& p = skippedResourceNames.keyAt(i); 1742 if (!outTable->hasBagOrEntry(myPackage, p.type, p.ident)) { 1743 const char* bundleProduct = 1744 (bundle->getProduct() == NULL) ? "" : bundle->getProduct(); 1745 fprintf(stderr, "In resource file %s: %s\n", 1746 in->getPrintableSource().string(), 1747 curParams.toString().string()); 1748 1749 fprintf(stderr, "\t%s '%s' does not match product %s.\n" 1750 "\tYou may have forgotten to include a 'default' product variant" 1751 " of the resource.\n", 1752 String8(p.type).string(), String8(p.ident).string(), 1753 bundleProduct[0] == 0 ? "default" : bundleProduct); 1754 return UNKNOWN_ERROR; 1755 } 1756 } 1757 } 1758 1759 return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; 1760 } 1761 1762 ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type) 1763 : mAssetsPackage(assetsPackage) 1764 , mPackageType(type) 1765 , mTypeIdOffset(0) 1766 , mNumLocal(0) 1767 , mBundle(bundle) 1768 { 1769 ssize_t packageId = -1; 1770 switch (mPackageType) { 1771 case App: 1772 case AppFeature: 1773 packageId = 0x7f; 1774 break; 1775 1776 case System: 1777 packageId = 0x01; 1778 break; 1779 1780 case SharedLibrary: 1781 packageId = 0x00; 1782 break; 1783 1784 default: 1785 assert(0); 1786 break; 1787 } 1788 sp<Package> package = new Package(mAssetsPackage, packageId); 1789 mPackages.add(assetsPackage, package); 1790 mOrderedPackages.add(package); 1791 1792 // Every resource table always has one first entry, the bag attributes. 1793 const SourcePos unknown(String8("????"), 0); 1794 getType(mAssetsPackage, String16("attr"), unknown); 1795 } 1796 1797 static uint32_t findLargestTypeIdForPackage(const ResTable& table, const String16& packageName) { 1798 const size_t basePackageCount = table.getBasePackageCount(); 1799 for (size_t i = 0; i < basePackageCount; i++) { 1800 if (packageName == table.getBasePackageName(i)) { 1801 return table.getLastTypeIdForPackage(i); 1802 } 1803 } 1804 return 0; 1805 } 1806 1807 status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets) 1808 { 1809 status_t err = assets->buildIncludedResources(bundle); 1810 if (err != NO_ERROR) { 1811 return err; 1812 } 1813 1814 mAssets = assets; 1815 mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage); 1816 1817 const String8& featureAfter = bundle->getFeatureAfterPackage(); 1818 if (!featureAfter.isEmpty()) { 1819 AssetManager featureAssetManager; 1820 if (!featureAssetManager.addAssetPath(featureAfter, NULL)) { 1821 fprintf(stderr, "ERROR: Feature package '%s' not found.\n", 1822 featureAfter.string()); 1823 return UNKNOWN_ERROR; 1824 } 1825 1826 const ResTable& featureTable = featureAssetManager.getResources(false); 1827 mTypeIdOffset = std::max(mTypeIdOffset, 1828 findLargestTypeIdForPackage(featureTable, mAssetsPackage)); 1829 } 1830 1831 return NO_ERROR; 1832 } 1833 1834 status_t ResourceTable::addPublic(const SourcePos& sourcePos, 1835 const String16& package, 1836 const String16& type, 1837 const String16& name, 1838 const uint32_t ident) 1839 { 1840 uint32_t rid = mAssets->getIncludedResources() 1841 .identifierForName(name.string(), name.size(), 1842 type.string(), type.size(), 1843 package.string(), package.size()); 1844 if (rid != 0) { 1845 sourcePos.error("Error declaring public resource %s/%s for included package %s\n", 1846 String8(type).string(), String8(name).string(), 1847 String8(package).string()); 1848 return UNKNOWN_ERROR; 1849 } 1850 1851 sp<Type> t = getType(package, type, sourcePos); 1852 if (t == NULL) { 1853 return UNKNOWN_ERROR; 1854 } 1855 return t->addPublic(sourcePos, name, ident); 1856 } 1857 1858 status_t ResourceTable::addEntry(const SourcePos& sourcePos, 1859 const String16& package, 1860 const String16& type, 1861 const String16& name, 1862 const String16& value, 1863 const Vector<StringPool::entry_style_span>* style, 1864 const ResTable_config* params, 1865 const bool doSetIndex, 1866 const int32_t format, 1867 const bool overwrite) 1868 { 1869 uint32_t rid = mAssets->getIncludedResources() 1870 .identifierForName(name.string(), name.size(), 1871 type.string(), type.size(), 1872 package.string(), package.size()); 1873 if (rid != 0) { 1874 sourcePos.error("Resource entry %s/%s is already defined in package %s.", 1875 String8(type).string(), String8(name).string(), String8(package).string()); 1876 return UNKNOWN_ERROR; 1877 } 1878 1879 sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite, 1880 params, doSetIndex); 1881 if (e == NULL) { 1882 return UNKNOWN_ERROR; 1883 } 1884 status_t err = e->setItem(sourcePos, value, style, format, overwrite); 1885 if (err == NO_ERROR) { 1886 mNumLocal++; 1887 } 1888 return err; 1889 } 1890 1891 status_t ResourceTable::startBag(const SourcePos& sourcePos, 1892 const String16& package, 1893 const String16& type, 1894 const String16& name, 1895 const String16& bagParent, 1896 const ResTable_config* params, 1897 bool overlay, 1898 bool replace, bool /* isId */) 1899 { 1900 status_t result = NO_ERROR; 1901 1902 // Check for adding entries in other packages... for now we do 1903 // nothing. We need to do the right thing here to support skinning. 1904 uint32_t rid = mAssets->getIncludedResources() 1905 .identifierForName(name.string(), name.size(), 1906 type.string(), type.size(), 1907 package.string(), package.size()); 1908 if (rid != 0) { 1909 sourcePos.error("Resource entry %s/%s is already defined in package %s.", 1910 String8(type).string(), String8(name).string(), String8(package).string()); 1911 return UNKNOWN_ERROR; 1912 } 1913 1914 if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) { 1915 bool canAdd = false; 1916 sp<Package> p = mPackages.valueFor(package); 1917 if (p != NULL) { 1918 sp<Type> t = p->getTypes().valueFor(type); 1919 if (t != NULL) { 1920 if (t->getCanAddEntries().indexOf(name) >= 0) { 1921 canAdd = true; 1922 } 1923 } 1924 } 1925 if (!canAdd) { 1926 sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n", 1927 String8(name).string()); 1928 return UNKNOWN_ERROR; 1929 } 1930 } 1931 sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params); 1932 if (e == NULL) { 1933 return UNKNOWN_ERROR; 1934 } 1935 1936 // If a parent is explicitly specified, set it. 1937 if (bagParent.size() > 0) { 1938 e->setParent(bagParent); 1939 } 1940 1941 if ((result = e->makeItABag(sourcePos)) != NO_ERROR) { 1942 return result; 1943 } 1944 1945 if (overlay && replace) { 1946 return e->emptyBag(sourcePos); 1947 } 1948 return result; 1949 } 1950 1951 status_t ResourceTable::addBag(const SourcePos& sourcePos, 1952 const String16& package, 1953 const String16& type, 1954 const String16& name, 1955 const String16& bagParent, 1956 const String16& bagKey, 1957 const String16& value, 1958 const Vector<StringPool::entry_style_span>* style, 1959 const ResTable_config* params, 1960 bool replace, bool isId, const int32_t format) 1961 { 1962 // Check for adding entries in other packages... for now we do 1963 // nothing. We need to do the right thing here to support skinning. 1964 uint32_t rid = mAssets->getIncludedResources() 1965 .identifierForName(name.string(), name.size(), 1966 type.string(), type.size(), 1967 package.string(), package.size()); 1968 if (rid != 0) { 1969 return NO_ERROR; 1970 } 1971 1972 #if 0 1973 if (name == String16("left")) { 1974 printf("Adding bag left: file=%s, line=%d, type=%s\n", 1975 sourcePos.file.striing(), sourcePos.line, String8(type).string()); 1976 } 1977 #endif 1978 sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params); 1979 if (e == NULL) { 1980 return UNKNOWN_ERROR; 1981 } 1982 1983 // If a parent is explicitly specified, set it. 1984 if (bagParent.size() > 0) { 1985 e->setParent(bagParent); 1986 } 1987 1988 const bool first = e->getBag().indexOfKey(bagKey) < 0; 1989 status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format); 1990 if (err == NO_ERROR && first) { 1991 mNumLocal++; 1992 } 1993 return err; 1994 } 1995 1996 bool ResourceTable::hasBagOrEntry(const String16& package, 1997 const String16& type, 1998 const String16& name) const 1999 { 2000 // First look for this in the included resources... 2001 uint32_t rid = mAssets->getIncludedResources() 2002 .identifierForName(name.string(), name.size(), 2003 type.string(), type.size(), 2004 package.string(), package.size()); 2005 if (rid != 0) { 2006 return true; 2007 } 2008 2009 sp<Package> p = mPackages.valueFor(package); 2010 if (p != NULL) { 2011 sp<Type> t = p->getTypes().valueFor(type); 2012 if (t != NULL) { 2013 sp<ConfigList> c = t->getConfigs().valueFor(name); 2014 if (c != NULL) return true; 2015 } 2016 } 2017 2018 return false; 2019 } 2020 2021 bool ResourceTable::hasBagOrEntry(const String16& package, 2022 const String16& type, 2023 const String16& name, 2024 const ResTable_config& config) const 2025 { 2026 // First look for this in the included resources... 2027 uint32_t rid = mAssets->getIncludedResources() 2028 .identifierForName(name.string(), name.size(), 2029 type.string(), type.size(), 2030 package.string(), package.size()); 2031 if (rid != 0) { 2032 return true; 2033 } 2034 2035 sp<Package> p = mPackages.valueFor(package); 2036 if (p != NULL) { 2037 sp<Type> t = p->getTypes().valueFor(type); 2038 if (t != NULL) { 2039 sp<ConfigList> c = t->getConfigs().valueFor(name); 2040 if (c != NULL) { 2041 sp<Entry> e = c->getEntries().valueFor(config); 2042 if (e != NULL) { 2043 return true; 2044 } 2045 } 2046 } 2047 } 2048 2049 return false; 2050 } 2051 2052 bool ResourceTable::hasBagOrEntry(const String16& ref, 2053 const String16* defType, 2054 const String16* defPackage) 2055 { 2056 String16 package, type, name; 2057 if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name, 2058 defType, defPackage ? defPackage:&mAssetsPackage, NULL)) { 2059 return false; 2060 } 2061 return hasBagOrEntry(package, type, name); 2062 } 2063 2064 bool ResourceTable::appendComment(const String16& package, 2065 const String16& type, 2066 const String16& name, 2067 const String16& comment, 2068 bool onlyIfEmpty) 2069 { 2070 if (comment.size() <= 0) { 2071 return true; 2072 } 2073 2074 sp<Package> p = mPackages.valueFor(package); 2075 if (p != NULL) { 2076 sp<Type> t = p->getTypes().valueFor(type); 2077 if (t != NULL) { 2078 sp<ConfigList> c = t->getConfigs().valueFor(name); 2079 if (c != NULL) { 2080 c->appendComment(comment, onlyIfEmpty); 2081 return true; 2082 } 2083 } 2084 } 2085 return false; 2086 } 2087 2088 bool ResourceTable::appendTypeComment(const String16& package, 2089 const String16& type, 2090 const String16& name, 2091 const String16& comment) 2092 { 2093 if (comment.size() <= 0) { 2094 return true; 2095 } 2096 2097 sp<Package> p = mPackages.valueFor(package); 2098 if (p != NULL) { 2099 sp<Type> t = p->getTypes().valueFor(type); 2100 if (t != NULL) { 2101 sp<ConfigList> c = t->getConfigs().valueFor(name); 2102 if (c != NULL) { 2103 c->appendTypeComment(comment); 2104 return true; 2105 } 2106 } 2107 } 2108 return false; 2109 } 2110 2111 bool ResourceTable::makeAttribute(const String16& package, 2112 const String16& name, 2113 const SourcePos& source, 2114 int32_t format, 2115 const String16& comment, 2116 bool shouldAppendComment) { 2117 const String16 attr16("attr"); 2118 2119 // First look for this in the included resources... 2120 uint32_t rid = mAssets->getIncludedResources() 2121 .identifierForName(name.string(), name.size(), 2122 attr16.string(), attr16.size(), 2123 package.string(), package.size()); 2124 if (rid != 0) { 2125 source.error("Attribute \"%s\" has already been defined", String8(name).string()); 2126 return false; 2127 } 2128 2129 sp<ResourceTable::Entry> entry = getEntry(package, attr16, name, source, false); 2130 if (entry == NULL) { 2131 source.error("Failed to create entry attr/%s", String8(name).string()); 2132 return false; 2133 } 2134 2135 if (entry->makeItABag(source) != NO_ERROR) { 2136 return false; 2137 } 2138 2139 const String16 formatKey16("^type"); 2140 const String16 formatValue16(String8::format("%d", format)); 2141 2142 ssize_t idx = entry->getBag().indexOfKey(formatKey16); 2143 if (idx >= 0) { 2144 // We have already set a format for this attribute, check if they are different. 2145 // We allow duplicate attribute definitions so long as they are identical. 2146 // This is to ensure inter-operation with libraries that define the same generic attribute. 2147 const Item& formatItem = entry->getBag().valueAt(idx); 2148 if ((format & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) || 2149 formatItem.value != formatValue16) { 2150 source.error("Attribute \"%s\" already defined with incompatible format.\n" 2151 "%s:%d: Original attribute defined here.", 2152 String8(name).string(), formatItem.sourcePos.file.string(), 2153 formatItem.sourcePos.line); 2154 return false; 2155 } 2156 } else { 2157 entry->addToBag(source, formatKey16, formatValue16); 2158 // Increment the number of resources we have. This is used to determine if we should 2159 // even generate a resource table. 2160 mNumLocal++; 2161 } 2162 appendComment(package, attr16, name, comment, shouldAppendComment); 2163 return true; 2164 } 2165 2166 void ResourceTable::canAddEntry(const SourcePos& pos, 2167 const String16& package, const String16& type, const String16& name) 2168 { 2169 sp<Type> t = getType(package, type, pos); 2170 if (t != NULL) { 2171 t->canAddEntry(name); 2172 } 2173 } 2174 2175 size_t ResourceTable::size() const { 2176 return mPackages.size(); 2177 } 2178 2179 size_t ResourceTable::numLocalResources() const { 2180 return mNumLocal; 2181 } 2182 2183 bool ResourceTable::hasResources() const { 2184 return mNumLocal > 0; 2185 } 2186 2187 sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, 2188 const bool isBase) 2189 { 2190 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8()); 2191 status_t err = flatten(bundle, filter, data, isBase); 2192 return err == NO_ERROR ? data : NULL; 2193 } 2194 2195 inline uint32_t ResourceTable::getResId(const sp<Package>& p, 2196 const sp<Type>& t, 2197 uint32_t nameId) 2198 { 2199 return makeResId(p->getAssignedId(), t->getIndex(), nameId); 2200 } 2201 2202 uint32_t ResourceTable::getResId(const String16& package, 2203 const String16& type, 2204 const String16& name, 2205 bool onlyPublic) const 2206 { 2207 uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic); 2208 if (id != 0) return id; // cache hit 2209 2210 // First look for this in the included resources... 2211 uint32_t specFlags = 0; 2212 uint32_t rid = mAssets->getIncludedResources() 2213 .identifierForName(name.string(), name.size(), 2214 type.string(), type.size(), 2215 package.string(), package.size(), 2216 &specFlags); 2217 if (rid != 0) { 2218 if (onlyPublic && (specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) { 2219 // If this is a feature split and the resource has the same 2220 // package name as us, then everything is public. 2221 if (mPackageType != AppFeature || mAssetsPackage != package) { 2222 return 0; 2223 } 2224 } 2225 2226 return ResourceIdCache::store(package, type, name, onlyPublic, rid); 2227 } 2228 2229 sp<Package> p = mPackages.valueFor(package); 2230 if (p == NULL) return 0; 2231 sp<Type> t = p->getTypes().valueFor(type); 2232 if (t == NULL) return 0; 2233 sp<ConfigList> c = t->getConfigs().valueFor(name); 2234 if (c == NULL) { 2235 if (type != String16("attr")) { 2236 return 0; 2237 } 2238 t = p->getTypes().valueFor(String16(kAttrPrivateType)); 2239 if (t == NULL) return 0; 2240 c = t->getConfigs().valueFor(name); 2241 if (c == NULL) return 0; 2242 } 2243 int32_t ei = c->getEntryIndex(); 2244 if (ei < 0) return 0; 2245 2246 return ResourceIdCache::store(package, type, name, onlyPublic, 2247 getResId(p, t, ei)); 2248 } 2249 2250 uint32_t ResourceTable::getResId(const String16& ref, 2251 const String16* defType, 2252 const String16* defPackage, 2253 const char** outErrorMsg, 2254 bool onlyPublic) const 2255 { 2256 String16 package, type, name; 2257 bool refOnlyPublic = true; 2258 if (!ResTable::expandResourceRef( 2259 ref.string(), ref.size(), &package, &type, &name, 2260 defType, defPackage ? defPackage:&mAssetsPackage, 2261 outErrorMsg, &refOnlyPublic)) { 2262 if (kIsDebug) { 2263 printf("Expanding resource: ref=%s\n", String8(ref).string()); 2264 printf("Expanding resource: defType=%s\n", 2265 defType ? String8(*defType).string() : "NULL"); 2266 printf("Expanding resource: defPackage=%s\n", 2267 defPackage ? String8(*defPackage).string() : "NULL"); 2268 printf("Expanding resource: ref=%s\n", String8(ref).string()); 2269 printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n", 2270 String8(package).string(), String8(type).string(), 2271 String8(name).string()); 2272 } 2273 return 0; 2274 } 2275 uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic); 2276 if (kIsDebug) { 2277 printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n", 2278 String8(package).string(), String8(type).string(), 2279 String8(name).string(), res); 2280 } 2281 if (res == 0) { 2282 if (outErrorMsg) 2283 *outErrorMsg = "No resource found that matches the given name"; 2284 } 2285 return res; 2286 } 2287 2288 bool ResourceTable::isValidResourceName(const String16& s) 2289 { 2290 const char16_t* p = s.string(); 2291 bool first = true; 2292 while (*p) { 2293 if ((*p >= 'a' && *p <= 'z') 2294 || (*p >= 'A' && *p <= 'Z') 2295 || *p == '_' 2296 || (!first && *p >= '0' && *p <= '9')) { 2297 first = false; 2298 p++; 2299 continue; 2300 } 2301 return false; 2302 } 2303 return true; 2304 } 2305 2306 bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool, 2307 const String16& str, 2308 bool preserveSpaces, bool coerceType, 2309 uint32_t attrID, 2310 const Vector<StringPool::entry_style_span>* style, 2311 String16* outStr, void* accessorCookie, 2312 uint32_t attrType, const String8* configTypeName, 2313 const ConfigDescription* config) 2314 { 2315 String16 finalStr; 2316 2317 bool res = true; 2318 if (style == NULL || style->size() == 0) { 2319 // Text is not styled so it can be any type... let's figure it out. 2320 res = mAssets->getIncludedResources() 2321 .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces, 2322 coerceType, attrID, NULL, &mAssetsPackage, this, 2323 accessorCookie, attrType); 2324 } else { 2325 // Styled text can only be a string, and while collecting the style 2326 // information we have already processed that string! 2327 outValue->size = sizeof(Res_value); 2328 outValue->res0 = 0; 2329 outValue->dataType = outValue->TYPE_STRING; 2330 outValue->data = 0; 2331 finalStr = str; 2332 } 2333 2334 if (!res) { 2335 return false; 2336 } 2337 2338 if (outValue->dataType == outValue->TYPE_STRING) { 2339 // Should do better merging styles. 2340 if (pool) { 2341 String8 configStr; 2342 if (config != NULL) { 2343 configStr = config->toString(); 2344 } else { 2345 configStr = "(null)"; 2346 } 2347 if (kIsDebug) { 2348 printf("Adding to pool string style #%zu config %s: %s\n", 2349 style != NULL ? style->size() : 0U, 2350 configStr.string(), String8(finalStr).string()); 2351 } 2352 if (style != NULL && style->size() > 0) { 2353 outValue->data = pool->add(finalStr, *style, configTypeName, config); 2354 } else { 2355 outValue->data = pool->add(finalStr, true, configTypeName, config); 2356 } 2357 } else { 2358 // Caller will fill this in later. 2359 outValue->data = 0; 2360 } 2361 2362 if (outStr) { 2363 *outStr = finalStr; 2364 } 2365 2366 } 2367 2368 return true; 2369 } 2370 2371 uint32_t ResourceTable::getCustomResource( 2372 const String16& package, const String16& type, const String16& name) const 2373 { 2374 //printf("getCustomResource: %s %s %s\n", String8(package).string(), 2375 // String8(type).string(), String8(name).string()); 2376 sp<Package> p = mPackages.valueFor(package); 2377 if (p == NULL) return 0; 2378 sp<Type> t = p->getTypes().valueFor(type); 2379 if (t == NULL) return 0; 2380 sp<ConfigList> c = t->getConfigs().valueFor(name); 2381 if (c == NULL) { 2382 if (type != String16("attr")) { 2383 return 0; 2384 } 2385 t = p->getTypes().valueFor(String16(kAttrPrivateType)); 2386 if (t == NULL) return 0; 2387 c = t->getConfigs().valueFor(name); 2388 if (c == NULL) return 0; 2389 } 2390 int32_t ei = c->getEntryIndex(); 2391 if (ei < 0) return 0; 2392 return getResId(p, t, ei); 2393 } 2394 2395 uint32_t ResourceTable::getCustomResourceWithCreation( 2396 const String16& package, const String16& type, const String16& name, 2397 const bool createIfNotFound) 2398 { 2399 uint32_t resId = getCustomResource(package, type, name); 2400 if (resId != 0 || !createIfNotFound) { 2401 return resId; 2402 } 2403 2404 if (mAssetsPackage != package) { 2405 mCurrentXmlPos.error("creating resource for external package %s: %s/%s.", 2406 String8(package).string(), String8(type).string(), String8(name).string()); 2407 if (package == String16("android")) { 2408 mCurrentXmlPos.printf("did you mean to use @+id instead of @+android:id?"); 2409 } 2410 return 0; 2411 } 2412 2413 String16 value("false"); 2414 status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true); 2415 if (status == NO_ERROR) { 2416 resId = getResId(package, type, name); 2417 return resId; 2418 } 2419 return 0; 2420 } 2421 2422 uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const 2423 { 2424 return origPackage; 2425 } 2426 2427 bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType) 2428 { 2429 //printf("getAttributeType #%08x\n", attrID); 2430 Res_value value; 2431 if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) { 2432 //printf("getAttributeType #%08x (%s): #%08x\n", attrID, 2433 // String8(getEntry(attrID)->getName()).string(), value.data); 2434 *outType = value.data; 2435 return true; 2436 } 2437 return false; 2438 } 2439 2440 bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin) 2441 { 2442 //printf("getAttributeMin #%08x\n", attrID); 2443 Res_value value; 2444 if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) { 2445 *outMin = value.data; 2446 return true; 2447 } 2448 return false; 2449 } 2450 2451 bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax) 2452 { 2453 //printf("getAttributeMax #%08x\n", attrID); 2454 Res_value value; 2455 if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) { 2456 *outMax = value.data; 2457 return true; 2458 } 2459 return false; 2460 } 2461 2462 uint32_t ResourceTable::getAttributeL10N(uint32_t attrID) 2463 { 2464 //printf("getAttributeL10N #%08x\n", attrID); 2465 Res_value value; 2466 if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) { 2467 return value.data; 2468 } 2469 return ResTable_map::L10N_NOT_REQUIRED; 2470 } 2471 2472 bool ResourceTable::getLocalizationSetting() 2473 { 2474 return mBundle->getRequireLocalization(); 2475 } 2476 2477 void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...) 2478 { 2479 if (accessorCookie != NULL && fmt != NULL) { 2480 AccessorCookie* ac = (AccessorCookie*)accessorCookie; 2481 int retval=0; 2482 char buf[1024]; 2483 va_list ap; 2484 va_start(ap, fmt); 2485 retval = vsnprintf(buf, sizeof(buf), fmt, ap); 2486 va_end(ap); 2487 ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n", 2488 buf, ac->attr.string(), ac->value.string()); 2489 } 2490 } 2491 2492 bool ResourceTable::getAttributeKeys( 2493 uint32_t attrID, Vector<String16>* outKeys) 2494 { 2495 sp<const Entry> e = getEntry(attrID); 2496 if (e != NULL) { 2497 const size_t N = e->getBag().size(); 2498 for (size_t i=0; i<N; i++) { 2499 const String16& key = e->getBag().keyAt(i); 2500 if (key.size() > 0 && key.string()[0] != '^') { 2501 outKeys->add(key); 2502 } 2503 } 2504 return true; 2505 } 2506 return false; 2507 } 2508 2509 bool ResourceTable::getAttributeEnum( 2510 uint32_t attrID, const char16_t* name, size_t nameLen, 2511 Res_value* outValue) 2512 { 2513 //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string()); 2514 String16 nameStr(name, nameLen); 2515 sp<const Entry> e = getEntry(attrID); 2516 if (e != NULL) { 2517 const size_t N = e->getBag().size(); 2518 for (size_t i=0; i<N; i++) { 2519 //printf("Comparing %s to %s\n", String8(name, nameLen).string(), 2520 // String8(e->getBag().keyAt(i)).string()); 2521 if (e->getBag().keyAt(i) == nameStr) { 2522 return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue); 2523 } 2524 } 2525 } 2526 return false; 2527 } 2528 2529 bool ResourceTable::getAttributeFlags( 2530 uint32_t attrID, const char16_t* name, size_t nameLen, 2531 Res_value* outValue) 2532 { 2533 outValue->dataType = Res_value::TYPE_INT_HEX; 2534 outValue->data = 0; 2535 2536 //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string()); 2537 String16 nameStr(name, nameLen); 2538 sp<const Entry> e = getEntry(attrID); 2539 if (e != NULL) { 2540 const size_t N = e->getBag().size(); 2541 2542 const char16_t* end = name + nameLen; 2543 const char16_t* pos = name; 2544 while (pos < end) { 2545 const char16_t* start = pos; 2546 while (pos < end && *pos != '|') { 2547 pos++; 2548 } 2549 2550 String16 nameStr(start, pos-start); 2551 size_t i; 2552 for (i=0; i<N; i++) { 2553 //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(), 2554 // String8(e->getBag().keyAt(i)).string()); 2555 if (e->getBag().keyAt(i) == nameStr) { 2556 Res_value val; 2557 bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val); 2558 if (!got) { 2559 return false; 2560 } 2561 //printf("Got value: 0x%08x\n", val.data); 2562 outValue->data |= val.data; 2563 break; 2564 } 2565 } 2566 2567 if (i >= N) { 2568 // Didn't find this flag identifier. 2569 return false; 2570 } 2571 pos++; 2572 } 2573 2574 return true; 2575 } 2576 return false; 2577 } 2578 2579 status_t ResourceTable::assignResourceIds() 2580 { 2581 const size_t N = mOrderedPackages.size(); 2582 size_t pi; 2583 status_t firstError = NO_ERROR; 2584 2585 // First generate all bag attributes and assign indices. 2586 for (pi=0; pi<N; pi++) { 2587 sp<Package> p = mOrderedPackages.itemAt(pi); 2588 if (p == NULL || p->getTypes().size() == 0) { 2589 // Empty, skip! 2590 continue; 2591 } 2592 2593 if (mPackageType == System) { 2594 p->movePrivateAttrs(); 2595 } 2596 2597 // This has no sense for packages being built as AppFeature (aka with a non-zero offset). 2598 status_t err = p->applyPublicTypeOrder(); 2599 if (err != NO_ERROR && firstError == NO_ERROR) { 2600 firstError = err; 2601 } 2602 2603 // Generate attributes... 2604 const size_t N = p->getOrderedTypes().size(); 2605 size_t ti; 2606 for (ti=0; ti<N; ti++) { 2607 sp<Type> t = p->getOrderedTypes().itemAt(ti); 2608 if (t == NULL) { 2609 continue; 2610 } 2611 const size_t N = t->getOrderedConfigs().size(); 2612 for (size_t ci=0; ci<N; ci++) { 2613 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); 2614 if (c == NULL) { 2615 continue; 2616 } 2617 const size_t N = c->getEntries().size(); 2618 for (size_t ei=0; ei<N; ei++) { 2619 sp<Entry> e = c->getEntries().valueAt(ei); 2620 if (e == NULL) { 2621 continue; 2622 } 2623 status_t err = e->generateAttributes(this, p->getName()); 2624 if (err != NO_ERROR && firstError == NO_ERROR) { 2625 firstError = err; 2626 } 2627 } 2628 } 2629 } 2630 2631 uint32_t typeIdOffset = 0; 2632 if (mPackageType == AppFeature && p->getName() == mAssetsPackage) { 2633 typeIdOffset = mTypeIdOffset; 2634 } 2635 2636 const SourcePos unknown(String8("????"), 0); 2637 sp<Type> attr = p->getType(String16("attr"), unknown); 2638 2639 // Force creation of ID if we are building feature splits. 2640 // Auto-generated ID resources won't apply the type ID offset correctly unless 2641 // the offset is applied here first. 2642 // b/30607637 2643 if (mPackageType == AppFeature && p->getName() == mAssetsPackage) { 2644 sp<Type> id = p->getType(String16("id"), unknown); 2645 } 2646 2647 // Assign indices... 2648 const size_t typeCount = p->getOrderedTypes().size(); 2649 for (size_t ti = 0; ti < typeCount; ti++) { 2650 sp<Type> t = p->getOrderedTypes().itemAt(ti); 2651 if (t == NULL) { 2652 continue; 2653 } 2654 2655 err = t->applyPublicEntryOrder(); 2656 if (err != NO_ERROR && firstError == NO_ERROR) { 2657 firstError = err; 2658 } 2659 2660 const size_t N = t->getOrderedConfigs().size(); 2661 t->setIndex(ti + 1 + typeIdOffset); 2662 2663 LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t, 2664 "First type is not attr!"); 2665 2666 for (size_t ei=0; ei<N; ei++) { 2667 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei); 2668 if (c == NULL) { 2669 continue; 2670 } 2671 c->setEntryIndex(ei); 2672 } 2673 } 2674 2675 2676 // Assign resource IDs to keys in bags... 2677 for (size_t ti = 0; ti < typeCount; ti++) { 2678 sp<Type> t = p->getOrderedTypes().itemAt(ti); 2679 if (t == NULL) { 2680 continue; 2681 } 2682 2683 const size_t N = t->getOrderedConfigs().size(); 2684 for (size_t ci=0; ci<N; ci++) { 2685 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); 2686 if (c == NULL) { 2687 continue; 2688 } 2689 //printf("Ordered config #%d: %p\n", ci, c.get()); 2690 const size_t N = c->getEntries().size(); 2691 for (size_t ei=0; ei<N; ei++) { 2692 sp<Entry> e = c->getEntries().valueAt(ei); 2693 if (e == NULL) { 2694 continue; 2695 } 2696 status_t err = e->assignResourceIds(this, p->getName()); 2697 if (err != NO_ERROR && firstError == NO_ERROR) { 2698 firstError = err; 2699 } 2700 } 2701 } 2702 } 2703 } 2704 return firstError; 2705 } 2706 2707 status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols, 2708 bool skipSymbolsWithoutDefaultLocalization) { 2709 const size_t N = mOrderedPackages.size(); 2710 const String8 defaultLocale; 2711 const String16 stringType("string"); 2712 size_t pi; 2713 2714 for (pi=0; pi<N; pi++) { 2715 sp<Package> p = mOrderedPackages.itemAt(pi); 2716 if (p->getTypes().size() == 0) { 2717 // Empty, skip! 2718 continue; 2719 } 2720 2721 const size_t N = p->getOrderedTypes().size(); 2722 size_t ti; 2723 2724 for (ti=0; ti<N; ti++) { 2725 sp<Type> t = p->getOrderedTypes().itemAt(ti); 2726 if (t == NULL) { 2727 continue; 2728 } 2729 2730 const size_t N = t->getOrderedConfigs().size(); 2731 sp<AaptSymbols> typeSymbols; 2732 if (t->getName() == String16(kAttrPrivateType)) { 2733 typeSymbols = outSymbols->addNestedSymbol(String8("attr"), t->getPos()); 2734 } else { 2735 typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); 2736 } 2737 2738 if (typeSymbols == NULL) { 2739 return UNKNOWN_ERROR; 2740 } 2741 2742 for (size_t ci=0; ci<N; ci++) { 2743 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); 2744 if (c == NULL) { 2745 continue; 2746 } 2747 uint32_t rid = getResId(p, t, ci); 2748 if (rid == 0) { 2749 return UNKNOWN_ERROR; 2750 } 2751 if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) { 2752 2753 if (skipSymbolsWithoutDefaultLocalization && 2754 t->getName() == stringType) { 2755 2756 // Don't generate symbols for strings without a default localization. 2757 if (mHasDefaultLocalization.find(c->getName()) 2758 == mHasDefaultLocalization.end()) { 2759 // printf("Skip symbol [%08x] %s\n", rid, 2760 // String8(c->getName()).string()); 2761 continue; 2762 } 2763 } 2764 2765 typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos()); 2766 2767 String16 comment(c->getComment()); 2768 typeSymbols->appendComment(String8(c->getName()), comment, c->getPos()); 2769 //printf("Type symbol [%08x] %s comment: %s\n", rid, 2770 // String8(c->getName()).string(), String8(comment).string()); 2771 comment = c->getTypeComment(); 2772 typeSymbols->appendTypeComment(String8(c->getName()), comment); 2773 } 2774 } 2775 } 2776 } 2777 return NO_ERROR; 2778 } 2779 2780 2781 void 2782 ResourceTable::addLocalization(const String16& name, const String8& locale, const SourcePos& src) 2783 { 2784 mLocalizations[name][locale] = src; 2785 } 2786 2787 void 2788 ResourceTable::addDefaultLocalization(const String16& name) 2789 { 2790 mHasDefaultLocalization.insert(name); 2791 } 2792 2793 2794 /*! 2795 * Flag various sorts of localization problems. '+' indicates checks already implemented; 2796 * '-' indicates checks that will be implemented in the future. 2797 * 2798 * + A localized string for which no default-locale version exists => warning 2799 * + A string for which no version in an explicitly-requested locale exists => warning 2800 * + A localized translation of an translateable="false" string => warning 2801 * - A localized string not provided in every locale used by the table 2802 */ 2803 status_t 2804 ResourceTable::validateLocalizations(void) 2805 { 2806 status_t err = NO_ERROR; 2807 const String8 defaultLocale; 2808 2809 // For all strings... 2810 for (const auto& nameIter : mLocalizations) { 2811 const std::map<String8, SourcePos>& configSrcMap = nameIter.second; 2812 2813 // Look for strings with no default localization 2814 if (configSrcMap.count(defaultLocale) == 0) { 2815 SourcePos().warning("string '%s' has no default translation.", 2816 String8(nameIter.first).string()); 2817 if (mBundle->getVerbose()) { 2818 for (const auto& locale : configSrcMap) { 2819 locale.second.printf("locale %s found", locale.first.string()); 2820 } 2821 } 2822 // !!! TODO: throw an error here in some circumstances 2823 } 2824 2825 // Check that all requested localizations are present for this string 2826 if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) { 2827 const char* allConfigs = mBundle->getConfigurations().string(); 2828 const char* start = allConfigs; 2829 const char* comma; 2830 2831 std::set<String8> missingConfigs; 2832 AaptLocaleValue locale; 2833 do { 2834 String8 config; 2835 comma = strchr(start, ','); 2836 if (comma != NULL) { 2837 config.setTo(start, comma - start); 2838 start = comma + 1; 2839 } else { 2840 config.setTo(start); 2841 } 2842 2843 if (!locale.initFromFilterString(config)) { 2844 continue; 2845 } 2846 2847 // don't bother with the pseudolocale "en_XA" or "ar_XB" 2848 if (config != "en_XA" && config != "ar_XB") { 2849 if (configSrcMap.find(config) == configSrcMap.end()) { 2850 // okay, no specific localization found. it's possible that we are 2851 // requiring a specific regional localization [e.g. de_DE] but there is an 2852 // available string in the generic language localization [e.g. de]; 2853 // consider that string to have fulfilled the localization requirement. 2854 String8 region(config.string(), 2); 2855 if (configSrcMap.find(region) == configSrcMap.end() && 2856 configSrcMap.count(defaultLocale) == 0) { 2857 missingConfigs.insert(config); 2858 } 2859 } 2860 } 2861 } while (comma != NULL); 2862 2863 if (!missingConfigs.empty()) { 2864 String8 configStr; 2865 for (const auto& iter : missingConfigs) { 2866 configStr.appendFormat(" %s", iter.string()); 2867 } 2868 SourcePos().warning("string '%s' is missing %u required localizations:%s", 2869 String8(nameIter.first).string(), 2870 (unsigned int)missingConfigs.size(), 2871 configStr.string()); 2872 } 2873 } 2874 } 2875 2876 return err; 2877 } 2878 2879 status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, 2880 const sp<AaptFile>& dest, 2881 const bool isBase) 2882 { 2883 const ConfigDescription nullConfig; 2884 2885 const size_t N = mOrderedPackages.size(); 2886 size_t pi; 2887 2888 const static String16 mipmap16("mipmap"); 2889 2890 bool useUTF8 = !bundle->getUTF16StringsOption(); 2891 2892 // The libraries this table references. 2893 Vector<sp<Package> > libraryPackages; 2894 const ResTable& table = mAssets->getIncludedResources(); 2895 const size_t basePackageCount = table.getBasePackageCount(); 2896 for (size_t i = 0; i < basePackageCount; i++) { 2897 size_t packageId = table.getBasePackageId(i); 2898 String16 packageName(table.getBasePackageName(i)); 2899 if (packageId > 0x01 && packageId != 0x7f && 2900 packageName != String16("android")) { 2901 libraryPackages.add(sp<Package>(new Package(packageName, packageId))); 2902 } 2903 } 2904 2905 // Iterate through all data, collecting all values (strings, 2906 // references, etc). 2907 StringPool valueStrings(useUTF8); 2908 Vector<sp<Entry> > allEntries; 2909 for (pi=0; pi<N; pi++) { 2910 sp<Package> p = mOrderedPackages.itemAt(pi); 2911 if (p->getTypes().size() == 0) { 2912 continue; 2913 } 2914 2915 StringPool typeStrings(useUTF8); 2916 StringPool keyStrings(useUTF8); 2917 2918 ssize_t stringsAdded = 0; 2919 const size_t N = p->getOrderedTypes().size(); 2920 for (size_t ti=0; ti<N; ti++) { 2921 sp<Type> t = p->getOrderedTypes().itemAt(ti); 2922 if (t == NULL) { 2923 typeStrings.add(String16("<empty>"), false); 2924 stringsAdded++; 2925 continue; 2926 } 2927 2928 while (stringsAdded < t->getIndex() - 1) { 2929 typeStrings.add(String16("<empty>"), false); 2930 stringsAdded++; 2931 } 2932 2933 const String16 typeName(t->getName()); 2934 typeStrings.add(typeName, false); 2935 stringsAdded++; 2936 2937 // This is a hack to tweak the sorting order of the final strings, 2938 // to put stuff that is generally not language-specific first. 2939 String8 configTypeName(typeName); 2940 if (configTypeName == "drawable" || configTypeName == "layout" 2941 || configTypeName == "color" || configTypeName == "anim" 2942 || configTypeName == "interpolator" || configTypeName == "animator" 2943 || configTypeName == "xml" || configTypeName == "menu" 2944 || configTypeName == "mipmap" || configTypeName == "raw") { 2945 configTypeName = "1complex"; 2946 } else { 2947 configTypeName = "2value"; 2948 } 2949 2950 // mipmaps don't get filtered, so they will 2951 // allways end up in the base. Make sure they 2952 // don't end up in a split. 2953 if (typeName == mipmap16 && !isBase) { 2954 continue; 2955 } 2956 2957 const bool filterable = (typeName != mipmap16); 2958 2959 const size_t N = t->getOrderedConfigs().size(); 2960 for (size_t ci=0; ci<N; ci++) { 2961 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); 2962 if (c == NULL) { 2963 continue; 2964 } 2965 const size_t N = c->getEntries().size(); 2966 for (size_t ei=0; ei<N; ei++) { 2967 ConfigDescription config = c->getEntries().keyAt(ei); 2968 if (filterable && !filter->match(config)) { 2969 continue; 2970 } 2971 sp<Entry> e = c->getEntries().valueAt(ei); 2972 if (e == NULL) { 2973 continue; 2974 } 2975 e->setNameIndex(keyStrings.add(e->getName(), true)); 2976 2977 // If this entry has no values for other configs, 2978 // and is the default config, then it is special. Otherwise 2979 // we want to add it with the config info. 2980 ConfigDescription* valueConfig = NULL; 2981 if (N != 1 || config == nullConfig) { 2982 valueConfig = &config; 2983 } 2984 2985 status_t err = e->prepareFlatten(&valueStrings, this, 2986 &configTypeName, &config); 2987 if (err != NO_ERROR) { 2988 return err; 2989 } 2990 allEntries.add(e); 2991 } 2992 } 2993 } 2994 2995 p->setTypeStrings(typeStrings.createStringBlock()); 2996 p->setKeyStrings(keyStrings.createStringBlock()); 2997 } 2998 2999 if (bundle->getOutputAPKFile() != NULL) { 3000 // Now we want to sort the value strings for better locality. This will 3001 // cause the positions of the strings to change, so we need to go back 3002 // through out resource entries and update them accordingly. Only need 3003 // to do this if actually writing the output file. 3004 valueStrings.sortByConfig(); 3005 for (pi=0; pi<allEntries.size(); pi++) { 3006 allEntries[pi]->remapStringValue(&valueStrings); 3007 } 3008 } 3009 3010 ssize_t strAmt = 0; 3011 3012 // Now build the array of package chunks. 3013 Vector<sp<AaptFile> > flatPackages; 3014 for (pi=0; pi<N; pi++) { 3015 sp<Package> p = mOrderedPackages.itemAt(pi); 3016 if (p->getTypes().size() == 0) { 3017 // Empty, skip! 3018 continue; 3019 } 3020 3021 const size_t N = p->getTypeStrings().size(); 3022 3023 const size_t baseSize = sizeof(ResTable_package); 3024 3025 // Start the package data. 3026 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8()); 3027 ResTable_package* header = (ResTable_package*)data->editData(baseSize); 3028 if (header == NULL) { 3029 fprintf(stderr, "ERROR: out of memory creating ResTable_package\n"); 3030 return NO_MEMORY; 3031 } 3032 memset(header, 0, sizeof(*header)); 3033 header->header.type = htods(RES_TABLE_PACKAGE_TYPE); 3034 header->header.headerSize = htods(sizeof(*header)); 3035 header->id = htodl(static_cast<uint32_t>(p->getAssignedId())); 3036 strcpy16_htod(header->name, p->getName().string()); 3037 3038 // Write the string blocks. 3039 const size_t typeStringsStart = data->getSize(); 3040 sp<AaptFile> strFile = p->getTypeStringsData(); 3041 ssize_t amt = data->writeData(strFile->getData(), strFile->getSize()); 3042 if (kPrintStringMetrics) { 3043 fprintf(stderr, "**** type strings: %zd\n", SSIZE(amt)); 3044 } 3045 strAmt += amt; 3046 if (amt < 0) { 3047 return amt; 3048 } 3049 const size_t keyStringsStart = data->getSize(); 3050 strFile = p->getKeyStringsData(); 3051 amt = data->writeData(strFile->getData(), strFile->getSize()); 3052 if (kPrintStringMetrics) { 3053 fprintf(stderr, "**** key strings: %zd\n", SSIZE(amt)); 3054 } 3055 strAmt += amt; 3056 if (amt < 0) { 3057 return amt; 3058 } 3059 3060 if (isBase) { 3061 status_t err = flattenLibraryTable(data, libraryPackages); 3062 if (err != NO_ERROR) { 3063 fprintf(stderr, "ERROR: failed to write library table\n"); 3064 return err; 3065 } 3066 } 3067 3068 // Build the type chunks inside of this package. 3069 for (size_t ti=0; ti<N; ti++) { 3070 // Retrieve them in the same order as the type string block. 3071 size_t len; 3072 String16 typeName(p->getTypeStrings().stringAt(ti, &len)); 3073 sp<Type> t = p->getTypes().valueFor(typeName); 3074 LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"), 3075 "Type name %s not found", 3076 String8(typeName).string()); 3077 if (t == NULL) { 3078 continue; 3079 } 3080 const bool filterable = (typeName != mipmap16); 3081 const bool skipEntireType = (typeName == mipmap16 && !isBase); 3082 3083 const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0; 3084 3085 // Until a non-NO_ENTRY value has been written for a resource, 3086 // that resource is invalid; validResources[i] represents 3087 // the item at t->getOrderedConfigs().itemAt(i). 3088 Vector<bool> validResources; 3089 validResources.insertAt(false, 0, N); 3090 3091 // First write the typeSpec chunk, containing information about 3092 // each resource entry in this type. 3093 { 3094 const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N; 3095 const size_t typeSpecStart = data->getSize(); 3096 ResTable_typeSpec* tsHeader = (ResTable_typeSpec*) 3097 (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart); 3098 if (tsHeader == NULL) { 3099 fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n"); 3100 return NO_MEMORY; 3101 } 3102 memset(tsHeader, 0, sizeof(*tsHeader)); 3103 tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE); 3104 tsHeader->header.headerSize = htods(sizeof(*tsHeader)); 3105 tsHeader->header.size = htodl(typeSpecSize); 3106 tsHeader->id = ti+1; 3107 tsHeader->entryCount = htodl(N); 3108 3109 uint32_t* typeSpecFlags = (uint32_t*) 3110 (((uint8_t*)data->editData()) 3111 + typeSpecStart + sizeof(ResTable_typeSpec)); 3112 memset(typeSpecFlags, 0, sizeof(uint32_t)*N); 3113 3114 for (size_t ei=0; ei<N; ei++) { 3115 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); 3116 if (cl == NULL) { 3117 continue; 3118 } 3119 3120 if (cl->getPublic()) { 3121 typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC); 3122 } 3123 3124 if (skipEntireType) { 3125 continue; 3126 } 3127 3128 const size_t CN = cl->getEntries().size(); 3129 for (size_t ci=0; ci<CN; ci++) { 3130 if (filterable && !filter->match(cl->getEntries().keyAt(ci))) { 3131 continue; 3132 } 3133 for (size_t cj=ci+1; cj<CN; cj++) { 3134 if (filterable && !filter->match(cl->getEntries().keyAt(cj))) { 3135 continue; 3136 } 3137 typeSpecFlags[ei] |= htodl( 3138 cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj))); 3139 } 3140 } 3141 } 3142 } 3143 3144 if (skipEntireType) { 3145 continue; 3146 } 3147 3148 // We need to write one type chunk for each configuration for 3149 // which we have entries in this type. 3150 SortedVector<ConfigDescription> uniqueConfigs; 3151 if (t != NULL) { 3152 uniqueConfigs = t->getUniqueConfigs(); 3153 } 3154 3155 const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N; 3156 3157 const size_t NC = uniqueConfigs.size(); 3158 for (size_t ci=0; ci<NC; ci++) { 3159 const ConfigDescription& config = uniqueConfigs[ci]; 3160 3161 if (kIsDebug) { 3162 printf("Writing config %zu config: imsi:%d/%d lang:%c%c cnt:%c%c " 3163 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " 3164 "sw%ddp w%ddp h%ddp layout:%d\n", 3165 ti + 1, 3166 config.mcc, config.mnc, 3167 config.language[0] ? config.language[0] : '-', 3168 config.language[1] ? config.language[1] : '-', 3169 config.country[0] ? config.country[0] : '-', 3170 config.country[1] ? config.country[1] : '-', 3171 config.orientation, 3172 config.uiMode, 3173 config.touchscreen, 3174 config.density, 3175 config.keyboard, 3176 config.inputFlags, 3177 config.navigation, 3178 config.screenWidth, 3179 config.screenHeight, 3180 config.smallestScreenWidthDp, 3181 config.screenWidthDp, 3182 config.screenHeightDp, 3183 config.screenLayout); 3184 } 3185 3186 if (filterable && !filter->match(config)) { 3187 continue; 3188 } 3189 3190 const size_t typeStart = data->getSize(); 3191 3192 ResTable_type* tHeader = (ResTable_type*) 3193 (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart); 3194 if (tHeader == NULL) { 3195 fprintf(stderr, "ERROR: out of memory creating ResTable_type\n"); 3196 return NO_MEMORY; 3197 } 3198 3199 memset(tHeader, 0, sizeof(*tHeader)); 3200 tHeader->header.type = htods(RES_TABLE_TYPE_TYPE); 3201 tHeader->header.headerSize = htods(sizeof(*tHeader)); 3202 tHeader->id = ti+1; 3203 tHeader->entryCount = htodl(N); 3204 tHeader->entriesStart = htodl(typeSize); 3205 tHeader->config = config; 3206 if (kIsDebug) { 3207 printf("Writing type %zu config: imsi:%d/%d lang:%c%c cnt:%c%c " 3208 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " 3209 "sw%ddp w%ddp h%ddp layout:%d\n", 3210 ti + 1, 3211 tHeader->config.mcc, tHeader->config.mnc, 3212 tHeader->config.language[0] ? tHeader->config.language[0] : '-', 3213 tHeader->config.language[1] ? tHeader->config.language[1] : '-', 3214 tHeader->config.country[0] ? tHeader->config.country[0] : '-', 3215 tHeader->config.country[1] ? tHeader->config.country[1] : '-', 3216 tHeader->config.orientation, 3217 tHeader->config.uiMode, 3218 tHeader->config.touchscreen, 3219 tHeader->config.density, 3220 tHeader->config.keyboard, 3221 tHeader->config.inputFlags, 3222 tHeader->config.navigation, 3223 tHeader->config.screenWidth, 3224 tHeader->config.screenHeight, 3225 tHeader->config.smallestScreenWidthDp, 3226 tHeader->config.screenWidthDp, 3227 tHeader->config.screenHeightDp, 3228 tHeader->config.screenLayout); 3229 } 3230 tHeader->config.swapHtoD(); 3231 3232 // Build the entries inside of this type. 3233 for (size_t ei=0; ei<N; ei++) { 3234 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); 3235 sp<Entry> e = NULL; 3236 if (cl != NULL) { 3237 e = cl->getEntries().valueFor(config); 3238 } 3239 3240 // Set the offset for this entry in its type. 3241 uint32_t* index = (uint32_t*) 3242 (((uint8_t*)data->editData()) 3243 + typeStart + sizeof(ResTable_type)); 3244 if (e != NULL) { 3245 index[ei] = htodl(data->getSize()-typeStart-typeSize); 3246 3247 // Create the entry. 3248 ssize_t amt = e->flatten(bundle, data, cl->getPublic()); 3249 if (amt < 0) { 3250 return amt; 3251 } 3252 validResources.editItemAt(ei) = true; 3253 } else { 3254 index[ei] = htodl(ResTable_type::NO_ENTRY); 3255 } 3256 } 3257 3258 // Fill in the rest of the type information. 3259 tHeader = (ResTable_type*) 3260 (((uint8_t*)data->editData()) + typeStart); 3261 tHeader->header.size = htodl(data->getSize()-typeStart); 3262 } 3263 3264 // If we're building splits, then each invocation of the flattening 3265 // step will have 'missing' entries. Don't warn/error for this case. 3266 if (bundle->getSplitConfigurations().isEmpty()) { 3267 bool missing_entry = false; 3268 const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ? 3269 "error" : "warning"; 3270 for (size_t i = 0; i < N; ++i) { 3271 if (!validResources[i]) { 3272 sp<ConfigList> c = t->getOrderedConfigs().itemAt(i); 3273 if (c != NULL) { 3274 fprintf(stderr, "%s: no entries written for %s/%s (0x%08zx)\n", log_prefix, 3275 String8(typeName).string(), String8(c->getName()).string(), 3276 Res_MAKEID(p->getAssignedId() - 1, ti, i)); 3277 } 3278 missing_entry = true; 3279 } 3280 } 3281 if (bundle->getErrorOnMissingConfigEntry() && missing_entry) { 3282 fprintf(stderr, "Error: Missing entries, quit!\n"); 3283 return NOT_ENOUGH_DATA; 3284 } 3285 } 3286 } 3287 3288 // Fill in the rest of the package information. 3289 header = (ResTable_package*)data->editData(); 3290 header->header.size = htodl(data->getSize()); 3291 header->typeStrings = htodl(typeStringsStart); 3292 header->lastPublicType = htodl(p->getTypeStrings().size()); 3293 header->keyStrings = htodl(keyStringsStart); 3294 header->lastPublicKey = htodl(p->getKeyStrings().size()); 3295 3296 flatPackages.add(data); 3297 } 3298 3299 // And now write out the final chunks. 3300 const size_t dataStart = dest->getSize(); 3301 3302 { 3303 // blah 3304 ResTable_header header; 3305 memset(&header, 0, sizeof(header)); 3306 header.header.type = htods(RES_TABLE_TYPE); 3307 header.header.headerSize = htods(sizeof(header)); 3308 header.packageCount = htodl(flatPackages.size()); 3309 status_t err = dest->writeData(&header, sizeof(header)); 3310 if (err != NO_ERROR) { 3311 fprintf(stderr, "ERROR: out of memory creating ResTable_header\n"); 3312 return err; 3313 } 3314 } 3315 3316 ssize_t strStart = dest->getSize(); 3317 status_t err = valueStrings.writeStringBlock(dest); 3318 if (err != NO_ERROR) { 3319 return err; 3320 } 3321 3322 ssize_t amt = (dest->getSize()-strStart); 3323 strAmt += amt; 3324 if (kPrintStringMetrics) { 3325 fprintf(stderr, "**** value strings: %zd\n", SSIZE(amt)); 3326 fprintf(stderr, "**** total strings: %zd\n", SSIZE(strAmt)); 3327 } 3328 3329 for (pi=0; pi<flatPackages.size(); pi++) { 3330 err = dest->writeData(flatPackages[pi]->getData(), 3331 flatPackages[pi]->getSize()); 3332 if (err != NO_ERROR) { 3333 fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n"); 3334 return err; 3335 } 3336 } 3337 3338 ResTable_header* header = (ResTable_header*) 3339 (((uint8_t*)dest->getData()) + dataStart); 3340 header->header.size = htodl(dest->getSize() - dataStart); 3341 3342 if (kPrintStringMetrics) { 3343 fprintf(stderr, "**** total resource table size: %zu / %zu%% strings\n", 3344 dest->getSize(), (size_t)(strAmt*100)/dest->getSize()); 3345 } 3346 3347 return NO_ERROR; 3348 } 3349 3350 status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) { 3351 // Write out the library table if necessary 3352 if (libs.size() > 0) { 3353 if (kIsDebug) { 3354 fprintf(stderr, "Writing library reference table\n"); 3355 } 3356 3357 const size_t libStart = dest->getSize(); 3358 const size_t count = libs.size(); 3359 ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange( 3360 libStart, sizeof(ResTable_lib_header)); 3361 3362 memset(libHeader, 0, sizeof(*libHeader)); 3363 libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE); 3364 libHeader->header.headerSize = htods(sizeof(*libHeader)); 3365 libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count)); 3366 libHeader->count = htodl(count); 3367 3368 // Write the library entries 3369 for (size_t i = 0; i < count; i++) { 3370 const size_t entryStart = dest->getSize(); 3371 sp<Package> libPackage = libs[i]; 3372 if (kIsDebug) { 3373 fprintf(stderr, " Entry %s -> 0x%02x\n", 3374 String8(libPackage->getName()).string(), 3375 (uint8_t)libPackage->getAssignedId()); 3376 } 3377 3378 ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange( 3379 entryStart, sizeof(ResTable_lib_entry)); 3380 memset(entry, 0, sizeof(*entry)); 3381 entry->packageId = htodl(libPackage->getAssignedId()); 3382 strcpy16_htod(entry->packageName, libPackage->getName().string()); 3383 } 3384 } 3385 return NO_ERROR; 3386 } 3387 3388 void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp) 3389 { 3390 fprintf(fp, 3391 "<!-- This file contains <public> resource definitions for all\n" 3392 " resources that were generated from the source data. -->\n" 3393 "\n" 3394 "<resources>\n"); 3395 3396 writePublicDefinitions(package, fp, true); 3397 writePublicDefinitions(package, fp, false); 3398 3399 fprintf(fp, 3400 "\n" 3401 "</resources>\n"); 3402 } 3403 3404 void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub) 3405 { 3406 bool didHeader = false; 3407 3408 sp<Package> pkg = mPackages.valueFor(package); 3409 if (pkg != NULL) { 3410 const size_t NT = pkg->getOrderedTypes().size(); 3411 for (size_t i=0; i<NT; i++) { 3412 sp<Type> t = pkg->getOrderedTypes().itemAt(i); 3413 if (t == NULL) { 3414 continue; 3415 } 3416 3417 bool didType = false; 3418 3419 const size_t NC = t->getOrderedConfigs().size(); 3420 for (size_t j=0; j<NC; j++) { 3421 sp<ConfigList> c = t->getOrderedConfigs().itemAt(j); 3422 if (c == NULL) { 3423 continue; 3424 } 3425 3426 if (c->getPublic() != pub) { 3427 continue; 3428 } 3429 3430 if (!didType) { 3431 fprintf(fp, "\n"); 3432 didType = true; 3433 } 3434 if (!didHeader) { 3435 if (pub) { 3436 fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n"); 3437 fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n"); 3438 } else { 3439 fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n"); 3440 fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n"); 3441 } 3442 didHeader = true; 3443 } 3444 if (!pub) { 3445 const size_t NE = c->getEntries().size(); 3446 for (size_t k=0; k<NE; k++) { 3447 const SourcePos& pos = c->getEntries().valueAt(k)->getPos(); 3448 if (pos.file != "") { 3449 fprintf(fp," <!-- Declared at %s:%d -->\n", 3450 pos.file.string(), pos.line); 3451 } 3452 } 3453 } 3454 fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n", 3455 String8(t->getName()).string(), 3456 String8(c->getName()).string(), 3457 getResId(pkg, t, c->getEntryIndex())); 3458 } 3459 } 3460 } 3461 } 3462 3463 ResourceTable::Item::Item(const SourcePos& _sourcePos, 3464 bool _isId, 3465 const String16& _value, 3466 const Vector<StringPool::entry_style_span>* _style, 3467 int32_t _format) 3468 : sourcePos(_sourcePos) 3469 , isId(_isId) 3470 , value(_value) 3471 , format(_format) 3472 , bagKeyId(0) 3473 , evaluating(false) 3474 { 3475 if (_style) { 3476 style = *_style; 3477 } 3478 } 3479 3480 ResourceTable::Entry::Entry(const Entry& entry) 3481 : RefBase() 3482 , mName(entry.mName) 3483 , mParent(entry.mParent) 3484 , mType(entry.mType) 3485 , mItem(entry.mItem) 3486 , mItemFormat(entry.mItemFormat) 3487 , mBag(entry.mBag) 3488 , mNameIndex(entry.mNameIndex) 3489 , mParentId(entry.mParentId) 3490 , mPos(entry.mPos) {} 3491 3492 ResourceTable::Entry& ResourceTable::Entry::operator=(const Entry& entry) { 3493 mName = entry.mName; 3494 mParent = entry.mParent; 3495 mType = entry.mType; 3496 mItem = entry.mItem; 3497 mItemFormat = entry.mItemFormat; 3498 mBag = entry.mBag; 3499 mNameIndex = entry.mNameIndex; 3500 mParentId = entry.mParentId; 3501 mPos = entry.mPos; 3502 return *this; 3503 } 3504 3505 status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos) 3506 { 3507 if (mType == TYPE_BAG) { 3508 return NO_ERROR; 3509 } 3510 if (mType == TYPE_UNKNOWN) { 3511 mType = TYPE_BAG; 3512 return NO_ERROR; 3513 } 3514 sourcePos.error("Resource entry %s is already defined as a single item.\n" 3515 "%s:%d: Originally defined here.\n", 3516 String8(mName).string(), 3517 mItem.sourcePos.file.string(), mItem.sourcePos.line); 3518 return UNKNOWN_ERROR; 3519 } 3520 3521 status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos, 3522 const String16& value, 3523 const Vector<StringPool::entry_style_span>* style, 3524 int32_t format, 3525 const bool overwrite) 3526 { 3527 Item item(sourcePos, false, value, style); 3528 3529 if (mType == TYPE_BAG) { 3530 if (mBag.size() == 0) { 3531 sourcePos.error("Resource entry %s is already defined as a bag.", 3532 String8(mName).string()); 3533 } else { 3534 const Item& item(mBag.valueAt(0)); 3535 sourcePos.error("Resource entry %s is already defined as a bag.\n" 3536 "%s:%d: Originally defined here.\n", 3537 String8(mName).string(), 3538 item.sourcePos.file.string(), item.sourcePos.line); 3539 } 3540 return UNKNOWN_ERROR; 3541 } 3542 if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) { 3543 sourcePos.error("Resource entry %s is already defined.\n" 3544 "%s:%d: Originally defined here.\n", 3545 String8(mName).string(), 3546 mItem.sourcePos.file.string(), mItem.sourcePos.line); 3547 return UNKNOWN_ERROR; 3548 } 3549 3550 mType = TYPE_ITEM; 3551 mItem = item; 3552 mItemFormat = format; 3553 return NO_ERROR; 3554 } 3555 3556 status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos, 3557 const String16& key, const String16& value, 3558 const Vector<StringPool::entry_style_span>* style, 3559 bool replace, bool isId, int32_t format) 3560 { 3561 status_t err = makeItABag(sourcePos); 3562 if (err != NO_ERROR) { 3563 return err; 3564 } 3565 3566 Item item(sourcePos, isId, value, style, format); 3567 3568 // XXX NOTE: there is an error if you try to have a bag with two keys, 3569 // one an attr and one an id, with the same name. Not something we 3570 // currently ever have to worry about. 3571 ssize_t origKey = mBag.indexOfKey(key); 3572 if (origKey >= 0) { 3573 if (!replace) { 3574 const Item& item(mBag.valueAt(origKey)); 3575 sourcePos.error("Resource entry %s already has bag item %s.\n" 3576 "%s:%d: Originally defined here.\n", 3577 String8(mName).string(), String8(key).string(), 3578 item.sourcePos.file.string(), item.sourcePos.line); 3579 return UNKNOWN_ERROR; 3580 } 3581 //printf("Replacing %s with %s\n", 3582 // String8(mBag.valueFor(key).value).string(), String8(value).string()); 3583 mBag.replaceValueFor(key, item); 3584 } 3585 3586 mBag.add(key, item); 3587 return NO_ERROR; 3588 } 3589 3590 status_t ResourceTable::Entry::removeFromBag(const String16& key) { 3591 if (mType != Entry::TYPE_BAG) { 3592 return NO_ERROR; 3593 } 3594 3595 if (mBag.removeItem(key) >= 0) { 3596 return NO_ERROR; 3597 } 3598 return UNKNOWN_ERROR; 3599 } 3600 3601 status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos) 3602 { 3603 status_t err = makeItABag(sourcePos); 3604 if (err != NO_ERROR) { 3605 return err; 3606 } 3607 3608 mBag.clear(); 3609 return NO_ERROR; 3610 } 3611 3612 status_t ResourceTable::Entry::generateAttributes(ResourceTable* table, 3613 const String16& package) 3614 { 3615 const String16 attr16("attr"); 3616 const String16 id16("id"); 3617 const size_t N = mBag.size(); 3618 for (size_t i=0; i<N; i++) { 3619 const String16& key = mBag.keyAt(i); 3620 const Item& it = mBag.valueAt(i); 3621 if (it.isId) { 3622 if (!table->hasBagOrEntry(key, &id16, &package)) { 3623 String16 value("false"); 3624 if (kIsDebug) { 3625 fprintf(stderr, "Generating %s:id/%s\n", 3626 String8(package).string(), 3627 String8(key).string()); 3628 } 3629 status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package, 3630 id16, key, value); 3631 if (err != NO_ERROR) { 3632 return err; 3633 } 3634 } 3635 } else if (!table->hasBagOrEntry(key, &attr16, &package)) { 3636 3637 #if 1 3638 // fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n", 3639 // String8(key).string()); 3640 // const Item& item(mBag.valueAt(i)); 3641 // fprintf(stderr, "Referenced from file %s line %d\n", 3642 // item.sourcePos.file.string(), item.sourcePos.line); 3643 // return UNKNOWN_ERROR; 3644 #else 3645 char numberStr[16]; 3646 sprintf(numberStr, "%d", ResTable_map::TYPE_ANY); 3647 status_t err = table->addBag(SourcePos("<generated>", 0), package, 3648 attr16, key, String16(""), 3649 String16("^type"), 3650 String16(numberStr), NULL, NULL); 3651 if (err != NO_ERROR) { 3652 return err; 3653 } 3654 #endif 3655 } 3656 } 3657 return NO_ERROR; 3658 } 3659 3660 status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table, 3661 const String16& /* package */) 3662 { 3663 bool hasErrors = false; 3664 3665 if (mType == TYPE_BAG) { 3666 const char* errorMsg; 3667 const String16 style16("style"); 3668 const String16 attr16("attr"); 3669 const String16 id16("id"); 3670 mParentId = 0; 3671 if (mParent.size() > 0) { 3672 mParentId = table->getResId(mParent, &style16, NULL, &errorMsg); 3673 if (mParentId == 0) { 3674 mPos.error("Error retrieving parent for item: %s '%s'.\n", 3675 errorMsg, String8(mParent).string()); 3676 hasErrors = true; 3677 } 3678 } 3679 const size_t N = mBag.size(); 3680 for (size_t i=0; i<N; i++) { 3681 const String16& key = mBag.keyAt(i); 3682 Item& it = mBag.editValueAt(i); 3683 it.bagKeyId = table->getResId(key, 3684 it.isId ? &id16 : &attr16, NULL, &errorMsg); 3685 //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId); 3686 if (it.bagKeyId == 0) { 3687 it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg, 3688 String8(it.isId ? id16 : attr16).string(), 3689 String8(key).string()); 3690 hasErrors = true; 3691 } 3692 } 3693 } 3694 return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; 3695 } 3696 3697 status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table, 3698 const String8* configTypeName, const ConfigDescription* config) 3699 { 3700 if (mType == TYPE_ITEM) { 3701 Item& it = mItem; 3702 AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value)); 3703 if (!table->stringToValue(&it.parsedValue, strings, 3704 it.value, false, true, 0, 3705 &it.style, NULL, &ac, mItemFormat, 3706 configTypeName, config)) { 3707 return UNKNOWN_ERROR; 3708 } 3709 } else if (mType == TYPE_BAG) { 3710 const size_t N = mBag.size(); 3711 for (size_t i=0; i<N; i++) { 3712 const String16& key = mBag.keyAt(i); 3713 Item& it = mBag.editValueAt(i); 3714 AccessorCookie ac(it.sourcePos, String8(key), String8(it.value)); 3715 if (!table->stringToValue(&it.parsedValue, strings, 3716 it.value, false, true, it.bagKeyId, 3717 &it.style, NULL, &ac, it.format, 3718 configTypeName, config)) { 3719 return UNKNOWN_ERROR; 3720 } 3721 } 3722 } else { 3723 mPos.error("Error: entry %s is not a single item or a bag.\n", 3724 String8(mName).string()); 3725 return UNKNOWN_ERROR; 3726 } 3727 return NO_ERROR; 3728 } 3729 3730 status_t ResourceTable::Entry::remapStringValue(StringPool* strings) 3731 { 3732 if (mType == TYPE_ITEM) { 3733 Item& it = mItem; 3734 if (it.parsedValue.dataType == Res_value::TYPE_STRING) { 3735 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); 3736 } 3737 } else if (mType == TYPE_BAG) { 3738 const size_t N = mBag.size(); 3739 for (size_t i=0; i<N; i++) { 3740 Item& it = mBag.editValueAt(i); 3741 if (it.parsedValue.dataType == Res_value::TYPE_STRING) { 3742 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); 3743 } 3744 } 3745 } else { 3746 mPos.error("Error: entry %s is not a single item or a bag.\n", 3747 String8(mName).string()); 3748 return UNKNOWN_ERROR; 3749 } 3750 return NO_ERROR; 3751 } 3752 3753 ssize_t ResourceTable::Entry::flatten(Bundle* /* bundle */, const sp<AaptFile>& data, bool isPublic) 3754 { 3755 size_t amt = 0; 3756 ResTable_entry header; 3757 memset(&header, 0, sizeof(header)); 3758 header.size = htods(sizeof(header)); 3759 const type ty = mType; 3760 if (ty == TYPE_BAG) { 3761 header.flags |= htods(header.FLAG_COMPLEX); 3762 } 3763 if (isPublic) { 3764 header.flags |= htods(header.FLAG_PUBLIC); 3765 } 3766 header.key.index = htodl(mNameIndex); 3767 if (ty != TYPE_BAG) { 3768 status_t err = data->writeData(&header, sizeof(header)); 3769 if (err != NO_ERROR) { 3770 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n"); 3771 return err; 3772 } 3773 3774 const Item& it = mItem; 3775 Res_value par; 3776 memset(&par, 0, sizeof(par)); 3777 par.size = htods(it.parsedValue.size); 3778 par.dataType = it.parsedValue.dataType; 3779 par.res0 = it.parsedValue.res0; 3780 par.data = htodl(it.parsedValue.data); 3781 #if 0 3782 printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n", 3783 String8(mName).string(), it.parsedValue.dataType, 3784 it.parsedValue.data, par.res0); 3785 #endif 3786 err = data->writeData(&par, it.parsedValue.size); 3787 if (err != NO_ERROR) { 3788 fprintf(stderr, "ERROR: out of memory creating Res_value\n"); 3789 return err; 3790 } 3791 amt += it.parsedValue.size; 3792 } else { 3793 size_t N = mBag.size(); 3794 size_t i; 3795 // Create correct ordering of items. 3796 KeyedVector<uint32_t, const Item*> items; 3797 for (i=0; i<N; i++) { 3798 const Item& it = mBag.valueAt(i); 3799 items.add(it.bagKeyId, &it); 3800 } 3801 N = items.size(); 3802 3803 ResTable_map_entry mapHeader; 3804 memcpy(&mapHeader, &header, sizeof(header)); 3805 mapHeader.size = htods(sizeof(mapHeader)); 3806 mapHeader.parent.ident = htodl(mParentId); 3807 mapHeader.count = htodl(N); 3808 status_t err = data->writeData(&mapHeader, sizeof(mapHeader)); 3809 if (err != NO_ERROR) { 3810 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n"); 3811 return err; 3812 } 3813 3814 for (i=0; i<N; i++) { 3815 const Item& it = *items.valueAt(i); 3816 ResTable_map map; 3817 map.name.ident = htodl(it.bagKeyId); 3818 map.value.size = htods(it.parsedValue.size); 3819 map.value.dataType = it.parsedValue.dataType; 3820 map.value.res0 = it.parsedValue.res0; 3821 map.value.data = htodl(it.parsedValue.data); 3822 err = data->writeData(&map, sizeof(map)); 3823 if (err != NO_ERROR) { 3824 fprintf(stderr, "ERROR: out of memory creating Res_value\n"); 3825 return err; 3826 } 3827 amt += sizeof(map); 3828 } 3829 } 3830 return amt; 3831 } 3832 3833 void ResourceTable::ConfigList::appendComment(const String16& comment, 3834 bool onlyIfEmpty) 3835 { 3836 if (comment.size() <= 0) { 3837 return; 3838 } 3839 if (onlyIfEmpty && mComment.size() > 0) { 3840 return; 3841 } 3842 if (mComment.size() > 0) { 3843 mComment.append(String16("\n")); 3844 } 3845 mComment.append(comment); 3846 } 3847 3848 void ResourceTable::ConfigList::appendTypeComment(const String16& comment) 3849 { 3850 if (comment.size() <= 0) { 3851 return; 3852 } 3853 if (mTypeComment.size() > 0) { 3854 mTypeComment.append(String16("\n")); 3855 } 3856 mTypeComment.append(comment); 3857 } 3858 3859 status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos, 3860 const String16& name, 3861 const uint32_t ident) 3862 { 3863 #if 0 3864 int32_t entryIdx = Res_GETENTRY(ident); 3865 if (entryIdx < 0) { 3866 sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n", 3867 String8(mName).string(), String8(name).string(), ident); 3868 return UNKNOWN_ERROR; 3869 } 3870 #endif 3871 3872 int32_t typeIdx = Res_GETTYPE(ident); 3873 if (typeIdx >= 0) { 3874 typeIdx++; 3875 if (mPublicIndex > 0 && mPublicIndex != typeIdx) { 3876 sourcePos.error("Public resource %s/%s has conflicting type codes for its" 3877 " public identifiers (0x%x vs 0x%x).\n", 3878 String8(mName).string(), String8(name).string(), 3879 mPublicIndex, typeIdx); 3880 return UNKNOWN_ERROR; 3881 } 3882 mPublicIndex = typeIdx; 3883 } 3884 3885 if (mFirstPublicSourcePos == NULL) { 3886 mFirstPublicSourcePos = new SourcePos(sourcePos); 3887 } 3888 3889 if (mPublic.indexOfKey(name) < 0) { 3890 mPublic.add(name, Public(sourcePos, String16(), ident)); 3891 } else { 3892 Public& p = mPublic.editValueFor(name); 3893 if (p.ident != ident) { 3894 sourcePos.error("Public resource %s/%s has conflicting public identifiers" 3895 " (0x%08x vs 0x%08x).\n" 3896 "%s:%d: Originally defined here.\n", 3897 String8(mName).string(), String8(name).string(), p.ident, ident, 3898 p.sourcePos.file.string(), p.sourcePos.line); 3899 return UNKNOWN_ERROR; 3900 } 3901 } 3902 3903 return NO_ERROR; 3904 } 3905 3906 void ResourceTable::Type::canAddEntry(const String16& name) 3907 { 3908 mCanAddEntries.add(name); 3909 } 3910 3911 sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry, 3912 const SourcePos& sourcePos, 3913 const ResTable_config* config, 3914 bool doSetIndex, 3915 bool overlay, 3916 bool autoAddOverlay) 3917 { 3918 int pos = -1; 3919 sp<ConfigList> c = mConfigs.valueFor(entry); 3920 if (c == NULL) { 3921 if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) { 3922 sourcePos.error("Resource at %s appears in overlay but not" 3923 " in the base package; use <add-resource> to add.\n", 3924 String8(entry).string()); 3925 return NULL; 3926 } 3927 c = new ConfigList(entry, sourcePos); 3928 mConfigs.add(entry, c); 3929 pos = (int)mOrderedConfigs.size(); 3930 mOrderedConfigs.add(c); 3931 if (doSetIndex) { 3932 c->setEntryIndex(pos); 3933 } 3934 } 3935 3936 ConfigDescription cdesc; 3937 if (config) cdesc = *config; 3938 3939 sp<Entry> e = c->getEntries().valueFor(cdesc); 3940 if (e == NULL) { 3941 if (kIsDebug) { 3942 if (config != NULL) { 3943 printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c " 3944 "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " 3945 "sw%ddp w%ddp h%ddp layout:%d\n", 3946 sourcePos.file.string(), sourcePos.line, 3947 config->mcc, config->mnc, 3948 config->language[0] ? config->language[0] : '-', 3949 config->language[1] ? config->language[1] : '-', 3950 config->country[0] ? config->country[0] : '-', 3951 config->country[1] ? config->country[1] : '-', 3952 config->orientation, 3953 config->touchscreen, 3954 config->density, 3955 config->keyboard, 3956 config->inputFlags, 3957 config->navigation, 3958 config->screenWidth, 3959 config->screenHeight, 3960 config->smallestScreenWidthDp, 3961 config->screenWidthDp, 3962 config->screenHeightDp, 3963 config->screenLayout); 3964 } else { 3965 printf("New entry at %s:%d: NULL config\n", 3966 sourcePos.file.string(), sourcePos.line); 3967 } 3968 } 3969 e = new Entry(entry, sourcePos); 3970 c->addEntry(cdesc, e); 3971 /* 3972 if (doSetIndex) { 3973 if (pos < 0) { 3974 for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) { 3975 if (mOrderedConfigs[pos] == c) { 3976 break; 3977 } 3978 } 3979 if (pos >= (int)mOrderedConfigs.size()) { 3980 sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry"); 3981 return NULL; 3982 } 3983 } 3984 e->setEntryIndex(pos); 3985 } 3986 */ 3987 } 3988 3989 return e; 3990 } 3991 3992 sp<ResourceTable::ConfigList> ResourceTable::Type::removeEntry(const String16& entry) { 3993 ssize_t idx = mConfigs.indexOfKey(entry); 3994 if (idx < 0) { 3995 return NULL; 3996 } 3997 3998 sp<ConfigList> removed = mConfigs.valueAt(idx); 3999 mConfigs.removeItemsAt(idx); 4000 4001 Vector<sp<ConfigList> >::iterator iter = std::find( 4002 mOrderedConfigs.begin(), mOrderedConfigs.end(), removed); 4003 if (iter != mOrderedConfigs.end()) { 4004 mOrderedConfigs.erase(iter); 4005 } 4006 4007 mPublic.removeItem(entry); 4008 return removed; 4009 } 4010 4011 SortedVector<ConfigDescription> ResourceTable::Type::getUniqueConfigs() const { 4012 SortedVector<ConfigDescription> unique; 4013 const size_t entryCount = mOrderedConfigs.size(); 4014 for (size_t i = 0; i < entryCount; i++) { 4015 if (mOrderedConfigs[i] == NULL) { 4016 continue; 4017 } 4018 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configs = 4019 mOrderedConfigs[i]->getEntries(); 4020 const size_t configCount = configs.size(); 4021 for (size_t j = 0; j < configCount; j++) { 4022 unique.add(configs.keyAt(j)); 4023 } 4024 } 4025 return unique; 4026 } 4027 4028 status_t ResourceTable::Type::applyPublicEntryOrder() 4029 { 4030 size_t N = mOrderedConfigs.size(); 4031 Vector<sp<ConfigList> > origOrder(mOrderedConfigs); 4032 bool hasError = false; 4033 4034 size_t i; 4035 for (i=0; i<N; i++) { 4036 mOrderedConfigs.replaceAt(NULL, i); 4037 } 4038 4039 const size_t NP = mPublic.size(); 4040 //printf("Ordering %d configs from %d public defs\n", N, NP); 4041 size_t j; 4042 for (j=0; j<NP; j++) { 4043 const String16& name = mPublic.keyAt(j); 4044 const Public& p = mPublic.valueAt(j); 4045 int32_t idx = Res_GETENTRY(p.ident); 4046 //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n", 4047 // String8(mName).string(), String8(name).string(), p.ident, N); 4048 bool found = false; 4049 for (i=0; i<N; i++) { 4050 sp<ConfigList> e = origOrder.itemAt(i); 4051 //printf("#%d: \"%s\"\n", i, String8(e->getName()).string()); 4052 if (e->getName() == name) { 4053 if (idx >= (int32_t)mOrderedConfigs.size()) { 4054 mOrderedConfigs.resize(idx + 1); 4055 } 4056 4057 if (mOrderedConfigs.itemAt(idx) == NULL) { 4058 e->setPublic(true); 4059 e->setPublicSourcePos(p.sourcePos); 4060 mOrderedConfigs.replaceAt(e, idx); 4061 origOrder.removeAt(i); 4062 N--; 4063 found = true; 4064 break; 4065 } else { 4066 sp<ConfigList> oe = mOrderedConfigs.itemAt(idx); 4067 4068 p.sourcePos.error("Multiple entry names declared for public entry" 4069 " identifier 0x%x in type %s (%s vs %s).\n" 4070 "%s:%d: Originally defined here.", 4071 idx+1, String8(mName).string(), 4072 String8(oe->getName()).string(), 4073 String8(name).string(), 4074 oe->getPublicSourcePos().file.string(), 4075 oe->getPublicSourcePos().line); 4076 hasError = true; 4077 } 4078 } 4079 } 4080 4081 if (!found) { 4082 p.sourcePos.error("Public symbol %s/%s declared here is not defined.", 4083 String8(mName).string(), String8(name).string()); 4084 hasError = true; 4085 } 4086 } 4087 4088 //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size()); 4089 4090 if (N != origOrder.size()) { 4091 printf("Internal error: remaining private symbol count mismatch\n"); 4092 N = origOrder.size(); 4093 } 4094 4095 j = 0; 4096 for (i=0; i<N; i++) { 4097 const sp<ConfigList>& e = origOrder.itemAt(i); 4098 // There will always be enough room for the remaining entries. 4099 while (mOrderedConfigs.itemAt(j) != NULL) { 4100 j++; 4101 } 4102 mOrderedConfigs.replaceAt(e, j); 4103 j++; 4104 } 4105 4106 return hasError ? STATUST(UNKNOWN_ERROR) : NO_ERROR; 4107 } 4108 4109 ResourceTable::Package::Package(const String16& name, size_t packageId) 4110 : mName(name), mPackageId(packageId), 4111 mTypeStringsMapping(0xffffffff), 4112 mKeyStringsMapping(0xffffffff) 4113 { 4114 } 4115 4116 sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type, 4117 const SourcePos& sourcePos, 4118 bool doSetIndex) 4119 { 4120 sp<Type> t = mTypes.valueFor(type); 4121 if (t == NULL) { 4122 t = new Type(type, sourcePos); 4123 mTypes.add(type, t); 4124 mOrderedTypes.add(t); 4125 if (doSetIndex) { 4126 // For some reason the type's index is set to one plus the index 4127 // in the mOrderedTypes list, rather than just the index. 4128 t->setIndex(mOrderedTypes.size()); 4129 } 4130 } 4131 return t; 4132 } 4133 4134 status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data) 4135 { 4136 status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping); 4137 if (err != NO_ERROR) { 4138 fprintf(stderr, "ERROR: Type string data is corrupt!\n"); 4139 return err; 4140 } 4141 4142 // Retain a reference to the new data after we've successfully replaced 4143 // all uses of the old reference (in setStrings() ). 4144 mTypeStringsData = data; 4145 return NO_ERROR; 4146 } 4147 4148 status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data) 4149 { 4150 status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping); 4151 if (err != NO_ERROR) { 4152 fprintf(stderr, "ERROR: Key string data is corrupt!\n"); 4153 return err; 4154 } 4155 4156 // Retain a reference to the new data after we've successfully replaced 4157 // all uses of the old reference (in setStrings() ). 4158 mKeyStringsData = data; 4159 return NO_ERROR; 4160 } 4161 4162 status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data, 4163 ResStringPool* strings, 4164 DefaultKeyedVector<String16, uint32_t>* mappings) 4165 { 4166 if (data->getData() == NULL) { 4167 return UNKNOWN_ERROR; 4168 } 4169 4170 status_t err = strings->setTo(data->getData(), data->getSize()); 4171 if (err == NO_ERROR) { 4172 const size_t N = strings->size(); 4173 for (size_t i=0; i<N; i++) { 4174 size_t len; 4175 mappings->add(String16(strings->stringAt(i, &len)), i); 4176 } 4177 } 4178 return err; 4179 } 4180 4181 status_t ResourceTable::Package::applyPublicTypeOrder() 4182 { 4183 size_t N = mOrderedTypes.size(); 4184 Vector<sp<Type> > origOrder(mOrderedTypes); 4185 4186 size_t i; 4187 for (i=0; i<N; i++) { 4188 mOrderedTypes.replaceAt(NULL, i); 4189 } 4190 4191 for (i=0; i<N; i++) { 4192 sp<Type> t = origOrder.itemAt(i); 4193 int32_t idx = t->getPublicIndex(); 4194 if (idx > 0) { 4195 idx--; 4196 while (idx >= (int32_t)mOrderedTypes.size()) { 4197 mOrderedTypes.add(); 4198 } 4199 if (mOrderedTypes.itemAt(idx) != NULL) { 4200 sp<Type> ot = mOrderedTypes.itemAt(idx); 4201 t->getFirstPublicSourcePos().error("Multiple type names declared for public type" 4202 " identifier 0x%x (%s vs %s).\n" 4203 "%s:%d: Originally defined here.", 4204 idx, String8(ot->getName()).string(), 4205 String8(t->getName()).string(), 4206 ot->getFirstPublicSourcePos().file.string(), 4207 ot->getFirstPublicSourcePos().line); 4208 return UNKNOWN_ERROR; 4209 } 4210 mOrderedTypes.replaceAt(t, idx); 4211 origOrder.removeAt(i); 4212 i--; 4213 N--; 4214 } 4215 } 4216 4217 size_t j=0; 4218 for (i=0; i<N; i++) { 4219 const sp<Type>& t = origOrder.itemAt(i); 4220 // There will always be enough room for the remaining types. 4221 while (mOrderedTypes.itemAt(j) != NULL) { 4222 j++; 4223 } 4224 mOrderedTypes.replaceAt(t, j); 4225 } 4226 4227 return NO_ERROR; 4228 } 4229 4230 void ResourceTable::Package::movePrivateAttrs() { 4231 sp<Type> attr = mTypes.valueFor(String16("attr")); 4232 if (attr == NULL) { 4233 // Nothing to do. 4234 return; 4235 } 4236 4237 Vector<sp<ConfigList> > privateAttrs; 4238 4239 bool hasPublic = false; 4240 const Vector<sp<ConfigList> >& configs = attr->getOrderedConfigs(); 4241 const size_t configCount = configs.size(); 4242 for (size_t i = 0; i < configCount; i++) { 4243 if (configs[i] == NULL) { 4244 continue; 4245 } 4246 4247 if (attr->isPublic(configs[i]->getName())) { 4248 hasPublic = true; 4249 } else { 4250 privateAttrs.add(configs[i]); 4251 } 4252 } 4253 4254 // Only if we have public attributes do we create a separate type for 4255 // private attributes. 4256 if (!hasPublic) { 4257 return; 4258 } 4259 4260 // Create a new type for private attributes. 4261 sp<Type> privateAttrType = getType(String16(kAttrPrivateType), SourcePos()); 4262 4263 const size_t privateAttrCount = privateAttrs.size(); 4264 for (size_t i = 0; i < privateAttrCount; i++) { 4265 const sp<ConfigList>& cl = privateAttrs[i]; 4266 4267 // Remove the private attributes from their current type. 4268 attr->removeEntry(cl->getName()); 4269 4270 // Add it to the new type. 4271 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = cl->getEntries(); 4272 const size_t entryCount = entries.size(); 4273 for (size_t j = 0; j < entryCount; j++) { 4274 const sp<Entry>& oldEntry = entries[j]; 4275 sp<Entry> entry = privateAttrType->getEntry( 4276 cl->getName(), oldEntry->getPos(), &entries.keyAt(j)); 4277 *entry = *oldEntry; 4278 } 4279 4280 // Move the symbols to the new type. 4281 4282 } 4283 } 4284 4285 sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) 4286 { 4287 if (package != mAssetsPackage) { 4288 return NULL; 4289 } 4290 return mPackages.valueFor(package); 4291 } 4292 4293 sp<ResourceTable::Type> ResourceTable::getType(const String16& package, 4294 const String16& type, 4295 const SourcePos& sourcePos, 4296 bool doSetIndex) 4297 { 4298 sp<Package> p = getPackage(package); 4299 if (p == NULL) { 4300 return NULL; 4301 } 4302 return p->getType(type, sourcePos, doSetIndex); 4303 } 4304 4305 sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package, 4306 const String16& type, 4307 const String16& name, 4308 const SourcePos& sourcePos, 4309 bool overlay, 4310 const ResTable_config* config, 4311 bool doSetIndex) 4312 { 4313 sp<Type> t = getType(package, type, sourcePos, doSetIndex); 4314 if (t == NULL) { 4315 return NULL; 4316 } 4317 return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay()); 4318 } 4319 4320 sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package, 4321 const String16& type, const String16& name) const 4322 { 4323 const size_t packageCount = mOrderedPackages.size(); 4324 for (size_t pi = 0; pi < packageCount; pi++) { 4325 const sp<Package>& p = mOrderedPackages[pi]; 4326 if (p == NULL || p->getName() != package) { 4327 continue; 4328 } 4329 4330 const Vector<sp<Type> >& types = p->getOrderedTypes(); 4331 const size_t typeCount = types.size(); 4332 for (size_t ti = 0; ti < typeCount; ti++) { 4333 const sp<Type>& t = types[ti]; 4334 if (t == NULL || t->getName() != type) { 4335 continue; 4336 } 4337 4338 const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs(); 4339 const size_t configCount = configs.size(); 4340 for (size_t ci = 0; ci < configCount; ci++) { 4341 const sp<ConfigList>& cl = configs[ci]; 4342 if (cl == NULL || cl->getName() != name) { 4343 continue; 4344 } 4345 4346 return cl; 4347 } 4348 } 4349 } 4350 return NULL; 4351 } 4352 4353 sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID, 4354 const ResTable_config* config) const 4355 { 4356 size_t pid = Res_GETPACKAGE(resID)+1; 4357 const size_t N = mOrderedPackages.size(); 4358 sp<Package> p; 4359 for (size_t i = 0; i < N; i++) { 4360 sp<Package> check = mOrderedPackages[i]; 4361 if (check->getAssignedId() == pid) { 4362 p = check; 4363 break; 4364 } 4365 4366 } 4367 if (p == NULL) { 4368 fprintf(stderr, "warning: Package not found for resource #%08x\n", resID); 4369 return NULL; 4370 } 4371 4372 int tid = Res_GETTYPE(resID); 4373 if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) { 4374 fprintf(stderr, "warning: Type not found for resource #%08x\n", resID); 4375 return NULL; 4376 } 4377 sp<Type> t = p->getOrderedTypes()[tid]; 4378 4379 int eid = Res_GETENTRY(resID); 4380 if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) { 4381 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID); 4382 return NULL; 4383 } 4384 4385 sp<ConfigList> c = t->getOrderedConfigs()[eid]; 4386 if (c == NULL) { 4387 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID); 4388 return NULL; 4389 } 4390 4391 ConfigDescription cdesc; 4392 if (config) cdesc = *config; 4393 sp<Entry> e = c->getEntries().valueFor(cdesc); 4394 if (c == NULL) { 4395 fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID); 4396 return NULL; 4397 } 4398 4399 return e; 4400 } 4401 4402 const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const 4403 { 4404 sp<const Entry> e = getEntry(resID); 4405 if (e == NULL) { 4406 return NULL; 4407 } 4408 4409 const size_t N = e->getBag().size(); 4410 for (size_t i=0; i<N; i++) { 4411 const Item& it = e->getBag().valueAt(i); 4412 if (it.bagKeyId == 0) { 4413 fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n", 4414 String8(e->getName()).string(), 4415 String8(e->getBag().keyAt(i)).string()); 4416 } 4417 if (it.bagKeyId == attrID) { 4418 return ⁢ 4419 } 4420 } 4421 4422 return NULL; 4423 } 4424 4425 bool ResourceTable::getItemValue( 4426 uint32_t resID, uint32_t attrID, Res_value* outValue) 4427 { 4428 const Item* item = getItem(resID, attrID); 4429 4430 bool res = false; 4431 if (item != NULL) { 4432 if (item->evaluating) { 4433 sp<const Entry> e = getEntry(resID); 4434 const size_t N = e->getBag().size(); 4435 size_t i; 4436 for (i=0; i<N; i++) { 4437 if (&e->getBag().valueAt(i) == item) { 4438 break; 4439 } 4440 } 4441 fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n", 4442 String8(e->getName()).string(), 4443 String8(e->getBag().keyAt(i)).string()); 4444 return false; 4445 } 4446 item->evaluating = true; 4447 res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId); 4448 if (kIsDebug) { 4449 if (res) { 4450 printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n", 4451 resID, attrID, String8(getEntry(resID)->getName()).string(), 4452 outValue->dataType, outValue->data); 4453 } else { 4454 printf("getItemValue of #%08x[#%08x]: failed\n", 4455 resID, attrID); 4456 } 4457 } 4458 item->evaluating = false; 4459 } 4460 return res; 4461 } 4462 4463 /** 4464 * Returns the SDK version at which the attribute was 4465 * made public, or -1 if the resource ID is not an attribute 4466 * or is not public. 4467 */ 4468 int ResourceTable::getPublicAttributeSdkLevel(uint32_t attrId) const { 4469 if (Res_GETPACKAGE(attrId) + 1 != 0x01 || Res_GETTYPE(attrId) + 1 != 0x01) { 4470 return -1; 4471 } 4472 4473 uint32_t specFlags; 4474 if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) { 4475 return -1; 4476 } 4477 4478 if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) { 4479 return -1; 4480 } 4481 4482 const size_t entryId = Res_GETENTRY(attrId); 4483 if (entryId <= 0x021c) { 4484 return 1; 4485 } else if (entryId <= 0x021d) { 4486 return 2; 4487 } else if (entryId <= 0x0269) { 4488 return SDK_CUPCAKE; 4489 } else if (entryId <= 0x028d) { 4490 return SDK_DONUT; 4491 } else if (entryId <= 0x02ad) { 4492 return SDK_ECLAIR; 4493 } else if (entryId <= 0x02b3) { 4494 return SDK_ECLAIR_0_1; 4495 } else if (entryId <= 0x02b5) { 4496 return SDK_ECLAIR_MR1; 4497 } else if (entryId <= 0x02bd) { 4498 return SDK_FROYO; 4499 } else if (entryId <= 0x02cb) { 4500 return SDK_GINGERBREAD; 4501 } else if (entryId <= 0x0361) { 4502 return SDK_HONEYCOMB; 4503 } else if (entryId <= 0x0366) { 4504 return SDK_HONEYCOMB_MR1; 4505 } else if (entryId <= 0x03a6) { 4506 return SDK_HONEYCOMB_MR2; 4507 } else if (entryId <= 0x03ae) { 4508 return SDK_JELLY_BEAN; 4509 } else if (entryId <= 0x03cc) { 4510 return SDK_JELLY_BEAN_MR1; 4511 } else if (entryId <= 0x03da) { 4512 return SDK_JELLY_BEAN_MR2; 4513 } else if (entryId <= 0x03f1) { 4514 return SDK_KITKAT; 4515 } else if (entryId <= 0x03f6) { 4516 return SDK_KITKAT_WATCH; 4517 } else if (entryId <= 0x04ce) { 4518 return SDK_LOLLIPOP; 4519 } else { 4520 // Anything else is marked as defined in 4521 // SDK_LOLLIPOP_MR1 since after this 4522 // version no attribute compat work 4523 // needs to be done. 4524 return SDK_LOLLIPOP_MR1; 4525 } 4526 } 4527 4528 /** 4529 * First check the Manifest, then check the command line flag. 4530 */ 4531 static int getMinSdkVersion(const Bundle* bundle) { 4532 if (bundle->getManifestMinSdkVersion() != NULL && strlen(bundle->getManifestMinSdkVersion()) > 0) { 4533 return atoi(bundle->getManifestMinSdkVersion()); 4534 } else if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) { 4535 return atoi(bundle->getMinSdkVersion()); 4536 } 4537 return 0; 4538 } 4539 4540 bool ResourceTable::shouldGenerateVersionedResource( 4541 const sp<ResourceTable::ConfigList>& configList, 4542 const ConfigDescription& sourceConfig, 4543 const int sdkVersionToGenerate) { 4544 assert(sdkVersionToGenerate > sourceConfig.sdkVersion); 4545 assert(configList != NULL); 4546 const DefaultKeyedVector<ConfigDescription, sp<ResourceTable::Entry>>& entries 4547 = configList->getEntries(); 4548 ssize_t idx = entries.indexOfKey(sourceConfig); 4549 4550 // The source config came from this list, so it should be here. 4551 assert(idx >= 0); 4552 4553 // The next configuration either only varies in sdkVersion, or it is completely different 4554 // and therefore incompatible. If it is incompatible, we must generate the versioned resource. 4555 4556 // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other 4557 // qualifiers, so we need to iterate through the entire list to be sure there 4558 // are no higher sdk level versions of this resource. 4559 ConfigDescription tempConfig(sourceConfig); 4560 for (size_t i = static_cast<size_t>(idx) + 1; i < entries.size(); i++) { 4561 const ConfigDescription& nextConfig = entries.keyAt(i); 4562 tempConfig.sdkVersion = nextConfig.sdkVersion; 4563 if (tempConfig == nextConfig) { 4564 // The two configs are the same, check the sdk version. 4565 return sdkVersionToGenerate < nextConfig.sdkVersion; 4566 } 4567 } 4568 4569 // No match was found, so we should generate the versioned resource. 4570 return true; 4571 } 4572 4573 /** 4574 * Modifies the entries in the resource table to account for compatibility 4575 * issues with older versions of Android. 4576 * 4577 * This primarily handles the issue of private/public attribute clashes 4578 * in framework resources. 4579 * 4580 * AAPT has traditionally assigned resource IDs to public attributes, 4581 * and then followed those public definitions with private attributes. 4582 * 4583 * --- PUBLIC --- 4584 * | 0x01010234 | attr/color 4585 * | 0x01010235 | attr/background 4586 * 4587 * --- PRIVATE --- 4588 * | 0x01010236 | attr/secret 4589 * | 0x01010237 | attr/shhh 4590 * 4591 * Each release, when attributes are added, they take the place of the private 4592 * attributes and the private attributes are shifted down again. 4593 * 4594 * --- PUBLIC --- 4595 * | 0x01010234 | attr/color 4596 * | 0x01010235 | attr/background 4597 * | 0x01010236 | attr/shinyNewAttr 4598 * | 0x01010237 | attr/highlyValuedFeature 4599 * 4600 * --- PRIVATE --- 4601 * | 0x01010238 | attr/secret 4602 * | 0x01010239 | attr/shhh 4603 * 4604 * Platform code may look for private attributes set in a theme. If an app 4605 * compiled against a newer version of the platform uses a new public 4606 * attribute that happens to have the same ID as the private attribute 4607 * the older platform is expecting, then the behavior is undefined. 4608 * 4609 * We get around this by detecting any newly defined attributes (in L), 4610 * copy the resource into a -v21 qualified resource, and delete the 4611 * attribute from the original resource. This ensures that older platforms 4612 * don't see the new attribute, but when running on L+ platforms, the 4613 * attribute will be respected. 4614 */ 4615 status_t ResourceTable::modifyForCompat(const Bundle* bundle) { 4616 const int minSdk = getMinSdkVersion(bundle); 4617 if (minSdk >= SDK_LOLLIPOP_MR1) { 4618 // Lollipop MR1 and up handles public attributes differently, no 4619 // need to do any compat modifications. 4620 return NO_ERROR; 4621 } 4622 4623 const String16 attr16("attr"); 4624 4625 const size_t packageCount = mOrderedPackages.size(); 4626 for (size_t pi = 0; pi < packageCount; pi++) { 4627 sp<Package> p = mOrderedPackages.itemAt(pi); 4628 if (p == NULL || p->getTypes().size() == 0) { 4629 // Empty, skip! 4630 continue; 4631 } 4632 4633 const size_t typeCount = p->getOrderedTypes().size(); 4634 for (size_t ti = 0; ti < typeCount; ti++) { 4635 sp<Type> t = p->getOrderedTypes().itemAt(ti); 4636 if (t == NULL) { 4637 continue; 4638 } 4639 4640 const size_t configCount = t->getOrderedConfigs().size(); 4641 for (size_t ci = 0; ci < configCount; ci++) { 4642 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); 4643 if (c == NULL) { 4644 continue; 4645 } 4646 4647 Vector<key_value_pair_t<ConfigDescription, sp<Entry> > > entriesToAdd; 4648 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = 4649 c->getEntries(); 4650 const size_t entryCount = entries.size(); 4651 for (size_t ei = 0; ei < entryCount; ei++) { 4652 const sp<Entry>& e = entries.valueAt(ei); 4653 if (e == NULL || e->getType() != Entry::TYPE_BAG) { 4654 continue; 4655 } 4656 4657 const ConfigDescription& config = entries.keyAt(ei); 4658 if (config.sdkVersion >= SDK_LOLLIPOP_MR1) { 4659 continue; 4660 } 4661 4662 KeyedVector<int, Vector<String16> > attributesToRemove; 4663 const KeyedVector<String16, Item>& bag = e->getBag(); 4664 const size_t bagCount = bag.size(); 4665 for (size_t bi = 0; bi < bagCount; bi++) { 4666 const uint32_t attrId = getResId(bag.keyAt(bi), &attr16); 4667 const int sdkLevel = getPublicAttributeSdkLevel(attrId); 4668 if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) { 4669 AaptUtil::appendValue(attributesToRemove, sdkLevel, bag.keyAt(bi)); 4670 } 4671 } 4672 4673 if (attributesToRemove.isEmpty()) { 4674 continue; 4675 } 4676 4677 const size_t sdkCount = attributesToRemove.size(); 4678 for (size_t i = 0; i < sdkCount; i++) { 4679 const int sdkLevel = attributesToRemove.keyAt(i); 4680 4681 if (!shouldGenerateVersionedResource(c, config, sdkLevel)) { 4682 // There is a style that will override this generated one. 4683 continue; 4684 } 4685 4686 // Duplicate the entry under the same configuration 4687 // but with sdkVersion == sdkLevel. 4688 ConfigDescription newConfig(config); 4689 newConfig.sdkVersion = sdkLevel; 4690 4691 sp<Entry> newEntry = new Entry(*e); 4692 4693 // Remove all items that have a higher SDK level than 4694 // the one we are synthesizing. 4695 for (size_t j = 0; j < sdkCount; j++) { 4696 if (j == i) { 4697 continue; 4698 } 4699 4700 if (attributesToRemove.keyAt(j) > sdkLevel) { 4701 const size_t attrCount = attributesToRemove[j].size(); 4702 for (size_t k = 0; k < attrCount; k++) { 4703 newEntry->removeFromBag(attributesToRemove[j][k]); 4704 } 4705 } 4706 } 4707 4708 entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >( 4709 newConfig, newEntry)); 4710 } 4711 4712 // Remove the attribute from the original. 4713 for (size_t i = 0; i < attributesToRemove.size(); i++) { 4714 for (size_t j = 0; j < attributesToRemove[i].size(); j++) { 4715 e->removeFromBag(attributesToRemove[i][j]); 4716 } 4717 } 4718 } 4719 4720 const size_t entriesToAddCount = entriesToAdd.size(); 4721 for (size_t i = 0; i < entriesToAddCount; i++) { 4722 assert(entries.indexOfKey(entriesToAdd[i].key) < 0); 4723 4724 if (bundle->getVerbose()) { 4725 entriesToAdd[i].value->getPos() 4726 .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.", 4727 entriesToAdd[i].key.sdkVersion, 4728 String8(p->getName()).string(), 4729 String8(t->getName()).string(), 4730 String8(entriesToAdd[i].value->getName()).string(), 4731 entriesToAdd[i].key.toString().string()); 4732 } 4733 4734 sp<Entry> newEntry = t->getEntry(c->getName(), 4735 entriesToAdd[i].value->getPos(), 4736 &entriesToAdd[i].key); 4737 4738 *newEntry = *entriesToAdd[i].value; 4739 } 4740 } 4741 } 4742 } 4743 return NO_ERROR; 4744 } 4745 4746 const String16 kTransitionElements[] = { 4747 String16("fade"), 4748 String16("changeBounds"), 4749 String16("slide"), 4750 String16("explode"), 4751 String16("changeImageTransform"), 4752 String16("changeTransform"), 4753 String16("changeClipBounds"), 4754 String16("autoTransition"), 4755 String16("recolor"), 4756 String16("changeScroll"), 4757 String16("transitionSet"), 4758 String16("transition"), 4759 String16("transitionManager"), 4760 }; 4761 4762 static bool IsTransitionElement(const String16& name) { 4763 for (int i = 0, size = sizeof(kTransitionElements) / sizeof(kTransitionElements[0]); 4764 i < size; ++i) { 4765 if (name == kTransitionElements[i]) { 4766 return true; 4767 } 4768 } 4769 return false; 4770 } 4771 4772 bool ResourceTable::versionForCompat(const Bundle* bundle, const String16& resourceName, 4773 const sp<AaptFile>& target, const sp<XMLNode>& root) { 4774 XMLNode* node = root.get(); 4775 while (node->getType() != XMLNode::TYPE_ELEMENT) { 4776 // We're assuming the root element is what we're looking for, which can only be under a 4777 // bunch of namespace declarations. 4778 if (node->getChildren().size() != 1) { 4779 // Not sure what to do, bail. 4780 return false; 4781 } 4782 node = node->getChildren().itemAt(0).get(); 4783 } 4784 4785 if (node->getElementNamespace().size() != 0) { 4786 // Not something we care about. 4787 return false; 4788 } 4789 4790 int versionedSdk = 0; 4791 if (node->getElementName() == String16("adaptive-icon")) { 4792 versionedSdk = SDK_O; 4793 } 4794 4795 const int minSdkVersion = getMinSdkVersion(bundle); 4796 const ConfigDescription config(target->getGroupEntry().toParams()); 4797 if (versionedSdk <= minSdkVersion || versionedSdk <= config.sdkVersion) { 4798 return false; 4799 } 4800 4801 sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()), 4802 String16(target->getResourceType()), resourceName); 4803 if (!shouldGenerateVersionedResource(cl, config, versionedSdk)) { 4804 return false; 4805 } 4806 4807 // Remove the original entry. 4808 cl->removeEntry(config); 4809 4810 // We need to wholesale version this file. 4811 ConfigDescription newConfig(config); 4812 newConfig.sdkVersion = versionedSdk; 4813 sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), 4814 AaptGroupEntry(newConfig), target->getResourceType()); 4815 String8 resPath = String8::format("res/%s/%s.xml", 4816 newFile->getGroupEntry().toDirName(target->getResourceType()).string(), 4817 String8(resourceName).string()); 4818 resPath.convertToResPath(); 4819 4820 // Add a resource table entry. 4821 addEntry(SourcePos(), 4822 String16(mAssets->getPackage()), 4823 String16(target->getResourceType()), 4824 resourceName, 4825 String16(resPath), 4826 NULL, 4827 &newConfig); 4828 4829 // Schedule this to be compiled. 4830 CompileResourceWorkItem item; 4831 item.resourceName = resourceName; 4832 item.resPath = resPath; 4833 item.file = newFile; 4834 item.xmlRoot = root->clone(); 4835 item.needsCompiling = true; 4836 mWorkQueue.push(item); 4837 4838 // Now mark the old entry as deleted. 4839 return true; 4840 } 4841 4842 status_t ResourceTable::modifyForCompat(const Bundle* bundle, 4843 const String16& resourceName, 4844 const sp<AaptFile>& target, 4845 const sp<XMLNode>& root) { 4846 const String16 vector16("vector"); 4847 const String16 animatedVector16("animated-vector"); 4848 const String16 pathInterpolator16("pathInterpolator"); 4849 const String16 objectAnimator16("objectAnimator"); 4850 const String16 gradient16("gradient"); 4851 4852 const int minSdk = getMinSdkVersion(bundle); 4853 if (minSdk >= SDK_LOLLIPOP_MR1) { 4854 // Lollipop MR1 and up handles public attributes differently, no 4855 // need to do any compat modifications. 4856 return NO_ERROR; 4857 } 4858 4859 const ConfigDescription config(target->getGroupEntry().toParams()); 4860 if (target->getResourceType() == "" || config.sdkVersion >= SDK_LOLLIPOP_MR1) { 4861 // Skip resources that have no type (AndroidManifest.xml) or are already version qualified 4862 // with v21 or higher. 4863 return NO_ERROR; 4864 } 4865 4866 sp<XMLNode> newRoot = NULL; 4867 int sdkVersionToGenerate = SDK_LOLLIPOP_MR1; 4868 4869 Vector<sp<XMLNode> > nodesToVisit; 4870 nodesToVisit.push(root); 4871 while (!nodesToVisit.isEmpty()) { 4872 sp<XMLNode> node = nodesToVisit.top(); 4873 nodesToVisit.pop(); 4874 4875 if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 || 4876 node->getElementName() == animatedVector16 || 4877 node->getElementName() == objectAnimator16 || 4878 node->getElementName() == pathInterpolator16 || 4879 node->getElementName() == gradient16)) { 4880 // We were told not to version vector tags, so skip the children here. 4881 continue; 4882 } 4883 4884 if (bundle->getNoVersionTransitions() && (IsTransitionElement(node->getElementName()))) { 4885 // We were told not to version transition tags, so skip the children here. 4886 continue; 4887 } 4888 4889 const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes(); 4890 for (size_t i = 0; i < attrs.size(); i++) { 4891 const XMLNode::attribute_entry& attr = attrs[i]; 4892 const int sdkLevel = getPublicAttributeSdkLevel(attr.nameResId); 4893 if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) { 4894 if (newRoot == NULL) { 4895 newRoot = root->clone(); 4896 } 4897 4898 // Find the smallest sdk version that we need to synthesize for 4899 // and do that one. Subsequent versions will be processed on 4900 // the next pass. 4901 sdkVersionToGenerate = std::min(sdkLevel, sdkVersionToGenerate); 4902 4903 if (bundle->getVerbose()) { 4904 SourcePos(node->getFilename(), node->getStartLineNumber()).printf( 4905 "removing attribute %s%s%s from <%s>", 4906 String8(attr.ns).string(), 4907 (attr.ns.size() == 0 ? "" : ":"), 4908 String8(attr.name).string(), 4909 String8(node->getElementName()).string()); 4910 } 4911 node->removeAttribute(i); 4912 i--; 4913 } 4914 } 4915 4916 // Schedule a visit to the children. 4917 const Vector<sp<XMLNode> >& children = node->getChildren(); 4918 const size_t childCount = children.size(); 4919 for (size_t i = 0; i < childCount; i++) { 4920 nodesToVisit.push(children[i]); 4921 } 4922 } 4923 4924 if (newRoot == NULL) { 4925 return NO_ERROR; 4926 } 4927 4928 // Look to see if we already have an overriding v21 configuration. 4929 sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()), 4930 String16(target->getResourceType()), resourceName); 4931 if (shouldGenerateVersionedResource(cl, config, sdkVersionToGenerate)) { 4932 // We don't have an overriding entry for v21, so we must duplicate this one. 4933 ConfigDescription newConfig(config); 4934 newConfig.sdkVersion = sdkVersionToGenerate; 4935 sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), 4936 AaptGroupEntry(newConfig), target->getResourceType()); 4937 String8 resPath = String8::format("res/%s/%s.xml", 4938 newFile->getGroupEntry().toDirName(target->getResourceType()).string(), 4939 String8(resourceName).string()); 4940 resPath.convertToResPath(); 4941 4942 // Add a resource table entry. 4943 if (bundle->getVerbose()) { 4944 SourcePos(target->getSourceFile(), -1).printf( 4945 "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.", 4946 newConfig.sdkVersion, 4947 mAssets->getPackage().string(), 4948 newFile->getResourceType().string(), 4949 String8(resourceName).string(), 4950 newConfig.toString().string()); 4951 } 4952 4953 addEntry(SourcePos(), 4954 String16(mAssets->getPackage()), 4955 String16(target->getResourceType()), 4956 resourceName, 4957 String16(resPath), 4958 NULL, 4959 &newConfig); 4960 4961 // Schedule this to be compiled. 4962 CompileResourceWorkItem item; 4963 item.resourceName = resourceName; 4964 item.resPath = resPath; 4965 item.file = newFile; 4966 item.xmlRoot = newRoot; 4967 item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need 4968 // to do it again. 4969 mWorkQueue.push(item); 4970 } 4971 return NO_ERROR; 4972 } 4973 4974 void ResourceTable::getDensityVaryingResources( 4975 KeyedVector<Symbol, Vector<SymbolDefinition> >& resources) { 4976 const ConfigDescription nullConfig; 4977 4978 const size_t packageCount = mOrderedPackages.size(); 4979 for (size_t p = 0; p < packageCount; p++) { 4980 const Vector<sp<Type> >& types = mOrderedPackages[p]->getOrderedTypes(); 4981 const size_t typeCount = types.size(); 4982 for (size_t t = 0; t < typeCount; t++) { 4983 const sp<Type>& type = types[t]; 4984 if (type == NULL) { 4985 continue; 4986 } 4987 4988 const Vector<sp<ConfigList> >& configs = type->getOrderedConfigs(); 4989 const size_t configCount = configs.size(); 4990 for (size_t c = 0; c < configCount; c++) { 4991 const sp<ConfigList>& configList = configs[c]; 4992 if (configList == NULL) { 4993 continue; 4994 } 4995 4996 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configEntries 4997 = configList->getEntries(); 4998 const size_t configEntryCount = configEntries.size(); 4999 for (size_t ce = 0; ce < configEntryCount; ce++) { 5000 const sp<Entry>& entry = configEntries.valueAt(ce); 5001 if (entry == NULL) { 5002 continue; 5003 } 5004 5005 const ConfigDescription& config = configEntries.keyAt(ce); 5006 if (AaptConfig::isDensityOnly(config)) { 5007 // This configuration only varies with regards to density. 5008 const Symbol symbol( 5009 mOrderedPackages[p]->getName(), 5010 type->getName(), 5011 configList->getName(), 5012 getResId(mOrderedPackages[p], types[t], 5013 configList->getEntryIndex())); 5014 5015 5016 AaptUtil::appendValue(resources, symbol, 5017 SymbolDefinition(symbol, config, entry->getPos())); 5018 } 5019 } 5020 } 5021 } 5022 } 5023 } 5024 5025 static String16 buildNamespace(const String16& package) { 5026 return String16("http://schemas.android.com/apk/res/") + package; 5027 } 5028 5029 static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) { 5030 const Vector<sp<XMLNode> >& children = parent->getChildren(); 5031 sp<XMLNode> onlyChild; 5032 for (size_t i = 0; i < children.size(); i++) { 5033 if (children[i]->getType() != XMLNode::TYPE_CDATA) { 5034 if (onlyChild != NULL) { 5035 return NULL; 5036 } 5037 onlyChild = children[i]; 5038 } 5039 } 5040 return onlyChild; 5041 } 5042 5043 /** 5044 * Detects use of the `bundle' format and extracts nested resources into their own top level 5045 * resources. The bundle format looks like this: 5046 * 5047 * <!-- res/drawable/bundle.xml --> 5048 * <animated-vector xmlns:aapt="http://schemas.android.com/aapt"> 5049 * <aapt:attr name="android:drawable"> 5050 * <vector android:width="60dp" 5051 * android:height="60dp"> 5052 * <path android:name="v" 5053 * android:fillColor="#000000" 5054 * android:pathData="M300,70 l 0,-70 70,..." /> 5055 * </vector> 5056 * </aapt:attr> 5057 * </animated-vector> 5058 * 5059 * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children 5060 * into a new high-level resource, assigning it a name and ID. Then value of the `name` 5061 * attribute must be a resource attribute. That resource attribute is inserted into the parent 5062 * with the reference to the extracted resource as the value. 5063 * 5064 * <!-- res/drawable/bundle.xml --> 5065 * <animated-vector android:drawable="@drawable/bundle_1.xml"> 5066 * </animated-vector> 5067 * 5068 * <!-- res/drawable/bundle_1.xml --> 5069 * <vector android:width="60dp" 5070 * android:height="60dp"> 5071 * <path android:name="v" 5072 * android:fillColor="#000000" 5073 * android:pathData="M300,70 l 0,-70 70,..." /> 5074 * </vector> 5075 */ 5076 status_t ResourceTable::processBundleFormat(const Bundle* bundle, 5077 const String16& resourceName, 5078 const sp<AaptFile>& target, 5079 const sp<XMLNode>& root) { 5080 Vector<sp<XMLNode> > namespaces; 5081 if (root->getType() == XMLNode::TYPE_NAMESPACE) { 5082 namespaces.push(root); 5083 } 5084 return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces); 5085 } 5086 5087 status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle, 5088 const String16& resourceName, 5089 const sp<AaptFile>& target, 5090 const sp<XMLNode>& parent, 5091 Vector<sp<XMLNode> >* namespaces) { 5092 const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt"); 5093 const String16 kName16("name"); 5094 const String16 kAttr16("attr"); 5095 const String16 kAssetPackage16(mAssets->getPackage()); 5096 5097 Vector<sp<XMLNode> >& children = parent->getChildren(); 5098 for (size_t i = 0; i < children.size(); i++) { 5099 const sp<XMLNode>& child = children[i]; 5100 5101 if (child->getType() == XMLNode::TYPE_CDATA) { 5102 continue; 5103 } else if (child->getType() == XMLNode::TYPE_NAMESPACE) { 5104 namespaces->push(child); 5105 } 5106 5107 if (child->getElementNamespace() != kAaptNamespaceUri16 || 5108 child->getElementName() != kAttr16) { 5109 status_t result = processBundleFormatImpl(bundle, resourceName, target, child, 5110 namespaces); 5111 if (result != NO_ERROR) { 5112 return result; 5113 } 5114 5115 if (child->getType() == XMLNode::TYPE_NAMESPACE) { 5116 namespaces->pop(); 5117 } 5118 continue; 5119 } 5120 5121 // This is the <aapt:attr> tag. Look for the 'name' attribute. 5122 SourcePos source(child->getFilename(), child->getStartLineNumber()); 5123 5124 sp<XMLNode> nestedRoot = findOnlyChildElement(child); 5125 if (nestedRoot == NULL) { 5126 source.error("<%s:%s> must have exactly one child element", 5127 String8(child->getElementNamespace()).string(), 5128 String8(child->getElementName()).string()); 5129 return UNKNOWN_ERROR; 5130 } 5131 5132 // Find the special attribute 'parent-attr'. This attribute's value contains 5133 // the resource attribute for which this element should be assigned in the parent. 5134 const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16); 5135 if (attr == NULL) { 5136 source.error("inline resource definition must specify an attribute via 'name'"); 5137 return UNKNOWN_ERROR; 5138 } 5139 5140 // Parse the attribute name. 5141 const char* errorMsg = NULL; 5142 String16 attrPackage, attrType, attrName; 5143 bool result = ResTable::expandResourceRef(attr->string.string(), 5144 attr->string.size(), 5145 &attrPackage, &attrType, &attrName, 5146 &kAttr16, &kAssetPackage16, 5147 &errorMsg, NULL); 5148 if (!result) { 5149 source.error("invalid attribute name for 'name': %s", errorMsg); 5150 return UNKNOWN_ERROR; 5151 } 5152 5153 if (attrType != kAttr16) { 5154 // The value of the 'name' attribute must be an attribute reference. 5155 source.error("value of 'name' must be an attribute reference."); 5156 return UNKNOWN_ERROR; 5157 } 5158 5159 // Generate a name for this nested resource and try to add it to the table. 5160 // We do this in a loop because the name may be taken, in which case we will 5161 // increment a suffix until we succeed. 5162 String8 nestedResourceName; 5163 String8 nestedResourcePath; 5164 int suffix = 1; 5165 while (true) { 5166 // This child element will be extracted into its own resource file. 5167 // Generate a name and path for it from its parent. 5168 nestedResourceName = String8::format("%s_%d", 5169 String8(resourceName).string(), suffix++); 5170 nestedResourcePath = String8::format("res/%s/%s.xml", 5171 target->getGroupEntry().toDirName(target->getResourceType()) 5172 .string(), 5173 nestedResourceName.string()); 5174 5175 // Lookup or create the entry for this name. 5176 sp<Entry> entry = getEntry(kAssetPackage16, 5177 String16(target->getResourceType()), 5178 String16(nestedResourceName), 5179 source, 5180 false, 5181 &target->getGroupEntry().toParams(), 5182 true); 5183 if (entry == NULL) { 5184 return UNKNOWN_ERROR; 5185 } 5186 5187 if (entry->getType() == Entry::TYPE_UNKNOWN) { 5188 // The value for this resource has never been set, 5189 // meaning we're good! 5190 entry->setItem(source, String16(nestedResourcePath)); 5191 break; 5192 } 5193 5194 // We failed (name already exists), so try with a different name 5195 // (increment the suffix). 5196 } 5197 5198 if (bundle->getVerbose()) { 5199 source.printf("generating nested resource %s:%s/%s", 5200 mAssets->getPackage().string(), target->getResourceType().string(), 5201 nestedResourceName.string()); 5202 } 5203 5204 // Build the attribute reference and assign it to the parent. 5205 String16 nestedResourceRef = String16(String8::format("@%s:%s/%s", 5206 mAssets->getPackage().string(), target->getResourceType().string(), 5207 nestedResourceName.string())); 5208 5209 String16 attrNs = buildNamespace(attrPackage); 5210 if (parent->getAttribute(attrNs, attrName) != NULL) { 5211 SourcePos(parent->getFilename(), parent->getStartLineNumber()) 5212 .error("parent of nested resource already defines attribute '%s:%s'", 5213 String8(attrPackage).string(), String8(attrName).string()); 5214 return UNKNOWN_ERROR; 5215 } 5216 5217 // Add the reference to the inline resource. 5218 parent->addAttribute(attrNs, attrName, nestedResourceRef); 5219 5220 // Remove the <aapt:attr> child element from here. 5221 children.removeAt(i); 5222 i--; 5223 5224 // Append all namespace declarations that we've seen on this branch in the XML tree 5225 // to this resource. 5226 // We do this because the order of namespace declarations and prefix usage is determined 5227 // by the developer and we do not want to override any decisions. Be conservative. 5228 for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) { 5229 const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1); 5230 sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(), 5231 ns->getNamespaceUri()); 5232 newNs->addChild(nestedRoot); 5233 nestedRoot = newNs; 5234 } 5235 5236 // Schedule compilation of the nested resource. 5237 CompileResourceWorkItem workItem; 5238 workItem.resPath = nestedResourcePath; 5239 workItem.resourceName = String16(nestedResourceName); 5240 workItem.xmlRoot = nestedRoot; 5241 workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(), 5242 target->getResourceType()); 5243 mWorkQueue.push(workItem); 5244 } 5245 return NO_ERROR; 5246 } 5247