1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "format/binary/BinaryResourceParser.h" 18 19 #include <algorithm> 20 #include <map> 21 #include <string> 22 23 #include "android-base/logging.h" 24 #include "android-base/macros.h" 25 #include "android-base/stringprintf.h" 26 #include "androidfw/ResourceTypes.h" 27 #include "androidfw/TypeWrappers.h" 28 29 #include "ResourceTable.h" 30 #include "ResourceUtils.h" 31 #include "ResourceValues.h" 32 #include "Source.h" 33 #include "ValueVisitor.h" 34 #include "format/binary/ResChunkPullParser.h" 35 #include "util/Util.h" 36 37 using namespace android; 38 39 using ::android::base::StringPrintf; 40 41 namespace aapt { 42 43 namespace { 44 45 static std::u16string strcpy16_dtoh(const char16_t* src, size_t len) { 46 size_t utf16_len = strnlen16(src, len); 47 if (utf16_len == 0) { 48 return {}; 49 } 50 std::u16string dst; 51 dst.resize(utf16_len); 52 for (size_t i = 0; i < utf16_len; i++) { 53 dst[i] = util::DeviceToHost16(src[i]); 54 } 55 return dst; 56 } 57 58 // Visitor that converts a reference's resource ID to a resource name, given a mapping from 59 // resource ID to resource name. 60 class ReferenceIdToNameVisitor : public DescendingValueVisitor { 61 public: 62 using DescendingValueVisitor::Visit; 63 64 explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) 65 : mapping_(mapping) { 66 CHECK(mapping_ != nullptr); 67 } 68 69 void Visit(Reference* reference) override { 70 if (!reference->id || !reference->id.value().is_valid()) { 71 return; 72 } 73 74 ResourceId id = reference->id.value(); 75 auto cache_iter = mapping_->find(id); 76 if (cache_iter != mapping_->end()) { 77 reference->name = cache_iter->second; 78 } 79 } 80 81 private: 82 DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor); 83 84 const std::map<ResourceId, ResourceName>* mapping_; 85 }; 86 87 } // namespace 88 89 BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, 90 const Source& source, const void* data, size_t len, 91 io::IFileCollection* files) 92 : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) { 93 } 94 95 bool BinaryResourceParser::Parse() { 96 ResChunkPullParser parser(data_, data_len_); 97 98 if (!ResChunkPullParser::IsGoodEvent(parser.Next())) { 99 diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error()); 100 return false; 101 } 102 103 if (parser.chunk()->type != android::RES_TABLE_TYPE) { 104 diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x", 105 static_cast<int>(parser.chunk()->type))); 106 return false; 107 } 108 109 if (!ParseTable(parser.chunk())) { 110 return false; 111 } 112 113 if (parser.Next() != ResChunkPullParser::Event::kEndDocument) { 114 if (parser.event() == ResChunkPullParser::Event::kBadDocument) { 115 diag_->Warn(DiagMessage(source_) 116 << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error()); 117 } else { 118 diag_->Warn(DiagMessage(source_) 119 << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", 120 static_cast<int>(parser.chunk()->type))); 121 } 122 } 123 return true; 124 } 125 126 // Parses the resource table, which contains all the packages, types, and entries. 127 bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { 128 const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk); 129 if (!table_header) { 130 diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk"); 131 return false; 132 } 133 134 ResChunkPullParser parser(GetChunkData(&table_header->header), 135 GetChunkDataLen(&table_header->header)); 136 while (ResChunkPullParser::IsGoodEvent(parser.Next())) { 137 switch (util::DeviceToHost16(parser.chunk()->type)) { 138 case android::RES_STRING_POOL_TYPE: 139 if (value_pool_.getError() == NO_INIT) { 140 status_t err = 141 value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); 142 if (err != NO_ERROR) { 143 diag_->Error(DiagMessage(source_) 144 << "corrupt string pool in ResTable: " << value_pool_.getError()); 145 return false; 146 } 147 148 // Reserve some space for the strings we are going to add. 149 table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount()); 150 } else { 151 diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable"); 152 } 153 break; 154 155 case android::RES_TABLE_PACKAGE_TYPE: 156 if (!ParsePackage(parser.chunk())) { 157 return false; 158 } 159 break; 160 161 default: 162 diag_->Warn(DiagMessage(source_) 163 << "unexpected chunk type " 164 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); 165 break; 166 } 167 } 168 169 if (parser.event() == ResChunkPullParser::Event::kBadDocument) { 170 diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error()); 171 return false; 172 } 173 return true; 174 } 175 176 bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { 177 constexpr size_t kMinPackageSize = 178 sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset); 179 const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk); 180 if (!package_header) { 181 diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk"); 182 return false; 183 } 184 185 uint32_t package_id = util::DeviceToHost32(package_header->id); 186 if (package_id > std::numeric_limits<uint8_t>::max()) { 187 diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")"); 188 return false; 189 } 190 191 // Extract the package name. 192 std::u16string package_name = strcpy16_dtoh((const char16_t*)package_header->name, 193 arraysize(package_header->name)); 194 195 ResourceTablePackage* package = 196 table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id)); 197 if (!package) { 198 diag_->Error(DiagMessage(source_) 199 << "incompatible package '" << package_name << "' with ID " << package_id); 200 return false; 201 } 202 203 // There can be multiple packages in a table, so 204 // clear the type and key pool in case they were set from a previous package. 205 type_pool_.uninit(); 206 key_pool_.uninit(); 207 208 ResChunkPullParser parser(GetChunkData(&package_header->header), 209 GetChunkDataLen(&package_header->header)); 210 while (ResChunkPullParser::IsGoodEvent(parser.Next())) { 211 switch (util::DeviceToHost16(parser.chunk()->type)) { 212 case android::RES_STRING_POOL_TYPE: 213 if (type_pool_.getError() == NO_INIT) { 214 status_t err = 215 type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); 216 if (err != NO_ERROR) { 217 diag_->Error(DiagMessage(source_) << "corrupt type string pool in " 218 << "ResTable_package: " << type_pool_.getError()); 219 return false; 220 } 221 } else if (key_pool_.getError() == NO_INIT) { 222 status_t err = 223 key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); 224 if (err != NO_ERROR) { 225 diag_->Error(DiagMessage(source_) << "corrupt key string pool in " 226 << "ResTable_package: " << key_pool_.getError()); 227 return false; 228 } 229 } else { 230 diag_->Warn(DiagMessage(source_) << "unexpected string pool"); 231 } 232 break; 233 234 case android::RES_TABLE_TYPE_SPEC_TYPE: 235 if (!ParseTypeSpec(package, parser.chunk())) { 236 return false; 237 } 238 break; 239 240 case android::RES_TABLE_TYPE_TYPE: 241 if (!ParseType(package, parser.chunk())) { 242 return false; 243 } 244 break; 245 246 case android::RES_TABLE_LIBRARY_TYPE: 247 if (!ParseLibrary(parser.chunk())) { 248 return false; 249 } 250 break; 251 252 case android::RES_TABLE_OVERLAYABLE_TYPE: 253 if (!ParseOverlayable(parser.chunk())) { 254 return false; 255 } 256 break; 257 258 default: 259 diag_->Warn(DiagMessage(source_) 260 << "unexpected chunk type " 261 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); 262 break; 263 } 264 } 265 266 if (parser.event() == ResChunkPullParser::Event::kBadDocument) { 267 diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); 268 return false; 269 } 270 271 // Now go through the table and change local resource ID references to 272 // symbolic references. 273 ReferenceIdToNameVisitor visitor(&id_index_); 274 VisitAllValuesInTable(table_, &visitor); 275 return true; 276 } 277 278 bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, 279 const ResChunk_header* chunk) { 280 if (type_pool_.getError() != NO_ERROR) { 281 diag_->Error(DiagMessage(source_) << "missing type string pool"); 282 return false; 283 } 284 285 const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk); 286 if (!type_spec) { 287 diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk"); 288 return false; 289 } 290 291 if (type_spec->id == 0) { 292 diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id); 293 return false; 294 } 295 296 // The data portion of this chunk contains entry_count 32bit entries, 297 // each one representing a set of flags. 298 const size_t entry_count = dtohl(type_spec->entryCount); 299 300 // There can only be 2^16 entries in a type, because that is the ID 301 // space for entries (EEEE) in the resource ID 0xPPTTEEEE. 302 if (entry_count > std::numeric_limits<uint16_t>::max()) { 303 diag_->Error(DiagMessage(source_) 304 << "ResTable_typeSpec has too many entries (" << entry_count << ")"); 305 return false; 306 } 307 308 const size_t data_size = util::DeviceToHost32(type_spec->header.size) - 309 util::DeviceToHost16(type_spec->header.headerSize); 310 if (entry_count * sizeof(uint32_t) > data_size) { 311 diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries."); 312 return false; 313 } 314 315 // Record the type_spec_flags for later. We don't know resource names yet, and we need those 316 // to mark resources as overlayable. 317 const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>( 318 reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize)); 319 for (size_t i = 0; i < entry_count; i++) { 320 ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i)); 321 entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]); 322 } 323 return true; 324 } 325 326 bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, 327 const ResChunk_header* chunk) { 328 if (type_pool_.getError() != NO_ERROR) { 329 diag_->Error(DiagMessage(source_) << "missing type string pool"); 330 return false; 331 } 332 333 if (key_pool_.getError() != NO_ERROR) { 334 diag_->Error(DiagMessage(source_) << "missing key string pool"); 335 return false; 336 } 337 338 // Specify a manual size, because ResTable_type contains ResTable_config, which changes 339 // a lot and has its own code to handle variable size. 340 const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk); 341 if (!type) { 342 diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk"); 343 return false; 344 } 345 346 if (type->id == 0) { 347 diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id); 348 return false; 349 } 350 351 ConfigDescription config; 352 config.copyFromDtoH(type->config); 353 354 const std::string type_str = util::GetString(type_pool_, type->id - 1); 355 356 // Be lenient on the name of the type if the table is lenient on resource validation. 357 auto parsed_type = ResourceType::kUnknown; 358 if (const ResourceType* parsed = ParseResourceType(type_str)) { 359 parsed_type = *parsed; 360 } else if (table_->GetValidateResources()) { 361 diag_->Error(DiagMessage(source_) << "invalid type name '" << type_str << "' for type with ID " 362 << (int) type->id); 363 return false; 364 } 365 366 TypeVariant tv(type); 367 for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { 368 const ResTable_entry* entry = *it; 369 if (!entry) { 370 continue; 371 } 372 373 const ResourceName name(package->name, parsed_type, 374 util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); 375 376 const ResourceId res_id(package->id.value(), type->id, static_cast<uint16_t>(it.index())); 377 378 std::unique_ptr<Value> resource_value; 379 if (entry->flags & ResTable_entry::FLAG_COMPLEX) { 380 const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry); 381 382 // TODO(adamlesinski): Check that the entry count is valid. 383 resource_value = ParseMapEntry(name, config, mapEntry); 384 } else { 385 const Res_value* value = 386 (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size)); 387 resource_value = ParseValue(name, config, *value); 388 } 389 390 if (!resource_value) { 391 diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " (" 392 << res_id << ") with configuration '" << config << "'"); 393 return false; 394 } 395 396 if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value), 397 diag_)) { 398 return false; 399 } 400 401 if (entry->flags & ResTable_entry::FLAG_PUBLIC) { 402 Visibility visibility; 403 visibility.level = Visibility::Level::kPublic; 404 if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) { 405 return false; 406 } 407 408 // Erase the ID from the map once processed, so that we don't mark the same symbol more than 409 // once. 410 entry_type_spec_flags_.erase(res_id); 411 } 412 413 // Add this resource name->id mapping to the index so 414 // that we can resolve all ID references to name references. 415 auto cache_iter = id_index_.find(res_id); 416 if (cache_iter == id_index_.end()) { 417 id_index_.insert({res_id, name}); 418 } 419 } 420 return true; 421 } 422 423 bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) { 424 DynamicRefTable dynamic_ref_table; 425 if (dynamic_ref_table.load(reinterpret_cast<const ResTable_lib_header*>(chunk)) != NO_ERROR) { 426 return false; 427 } 428 429 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table.entries(); 430 const size_t count = entries.size(); 431 for (size_t i = 0; i < count; i++) { 432 table_->included_packages_[entries.valueAt(i)] = 433 util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string())); 434 } 435 return true; 436 } 437 438 bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { 439 const ResTable_overlayable_header* header = ConvertTo<ResTable_overlayable_header>(chunk); 440 if (!header) { 441 diag_->Error(DiagMessage(source_) << "corrupt ResTable_category_header chunk"); 442 return false; 443 } 444 445 auto overlayable = std::make_shared<Overlayable>(); 446 overlayable->name = util::Utf16ToUtf8(strcpy16_dtoh((const char16_t*)header->name, 447 arraysize(header->name))); 448 overlayable->actor = util::Utf16ToUtf8(strcpy16_dtoh((const char16_t*)header->actor, 449 arraysize(header->name))); 450 451 ResChunkPullParser parser(GetChunkData(chunk), 452 GetChunkDataLen(chunk)); 453 while (ResChunkPullParser::IsGoodEvent(parser.Next())) { 454 if (util::DeviceToHost16(parser.chunk()->type) == android::RES_TABLE_OVERLAYABLE_POLICY_TYPE) { 455 const ResTable_overlayable_policy_header* policy_header = 456 ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); 457 458 OverlayableItem::PolicyFlags policies = OverlayableItem::Policy::kNone; 459 if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { 460 policies |= OverlayableItem::Policy::kPublic; 461 } 462 if (policy_header->policy_flags 463 & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { 464 policies |= OverlayableItem::Policy::kSystem; 465 } 466 if (policy_header->policy_flags 467 & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { 468 policies |= OverlayableItem::Policy::kVendor; 469 } 470 if (policy_header->policy_flags 471 & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { 472 policies |= OverlayableItem::Policy::kProduct; 473 } 474 if (policy_header->policy_flags 475 & ResTable_overlayable_policy_header::POLICY_SIGNATURE) { 476 policies |= OverlayableItem::Policy::kSignature; 477 } 478 if (policy_header->policy_flags 479 & ResTable_overlayable_policy_header::POLICY_ODM_PARTITION) { 480 policies |= OverlayableItem::Policy::kOdm; 481 } 482 if (policy_header->policy_flags 483 & ResTable_overlayable_policy_header::POLICY_OEM_PARTITION) { 484 policies |= OverlayableItem::Policy::kOem; 485 } 486 487 const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( 488 ((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize)); 489 const ResTable_ref* const ref_end = ref_begin 490 + util::DeviceToHost32(policy_header->entry_count); 491 for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { 492 ResourceId res_id(util::DeviceToHost32(ref_iter->ident)); 493 const auto iter = id_index_.find(res_id); 494 495 // If the overlayable chunk comes before the type chunks, the resource ids and resource name 496 // pairing will not exist at this point. 497 if (iter == id_index_.cend()) { 498 diag_->Error(DiagMessage(source_) << "failed to find resource name for overlayable" 499 << " resource " << res_id); 500 return false; 501 } 502 503 OverlayableItem overlayable_item(overlayable); 504 overlayable_item.policies = policies; 505 if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) { 506 return false; 507 } 508 } 509 } 510 } 511 512 return true; 513 } 514 515 std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name, 516 const ConfigDescription& config, 517 const android::Res_value& value) { 518 std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_, 519 value, &table_->string_pool); 520 if (files_ != nullptr) { 521 FileReference* file_ref = ValueCast<FileReference>(item.get()); 522 if (file_ref != nullptr) { 523 file_ref->file = files_->FindFile(*file_ref->path); 524 if (file_ref->file == nullptr) { 525 diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config 526 << "' is a file reference to '" << *file_ref->path 527 << "' but no such path exists"); 528 } 529 } 530 } 531 return item; 532 } 533 534 std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef& name, 535 const ConfigDescription& config, 536 const ResTable_map_entry* map) { 537 switch (name.type) { 538 case ResourceType::kStyle: 539 return ParseStyle(name, config, map); 540 case ResourceType::kAttrPrivate: 541 // fallthrough 542 case ResourceType::kAttr: 543 return ParseAttr(name, config, map); 544 case ResourceType::kArray: 545 return ParseArray(name, config, map); 546 case ResourceType::kPlurals: 547 return ParsePlural(name, config, map); 548 case ResourceType::kId: 549 // Special case: An ID is not a bag, but some apps have defined the auto-generated 550 // IDs that come from declaring an enum value in an attribute as an empty map... 551 // We can ignore the value here. 552 return util::make_unique<Id>(); 553 default: 554 diag_->Error(DiagMessage() << "illegal map type '" << to_string(name.type) << "' (" 555 << (int)name.type << ")"); 556 break; 557 } 558 return {}; 559 } 560 561 std::unique_ptr<Style> BinaryResourceParser::ParseStyle(const ResourceNameRef& name, 562 const ConfigDescription& config, 563 const ResTable_map_entry* map) { 564 std::unique_ptr<Style> style = util::make_unique<Style>(); 565 if (util::DeviceToHost32(map->parent.ident) != 0) { 566 // The parent is a regular reference to a resource. 567 style->parent = Reference(util::DeviceToHost32(map->parent.ident)); 568 } 569 570 for (const ResTable_map& map_entry : map) { 571 if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) { 572 continue; 573 } 574 575 Style::Entry style_entry; 576 style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident)); 577 style_entry.value = ParseValue(name, config, map_entry.value); 578 if (!style_entry.value) { 579 return {}; 580 } 581 style->entries.push_back(std::move(style_entry)); 582 } 583 return style; 584 } 585 586 std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef& name, 587 const ConfigDescription& config, 588 const ResTable_map_entry* map) { 589 std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(); 590 attr->SetWeak((util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0); 591 592 // First we must discover what type of attribute this is. Find the type mask. 593 auto type_mask_iter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { 594 return util::DeviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE; 595 }); 596 597 if (type_mask_iter != end(map)) { 598 attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data); 599 } 600 601 for (const ResTable_map& map_entry : map) { 602 if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) { 603 switch (util::DeviceToHost32(map_entry.name.ident)) { 604 case ResTable_map::ATTR_MIN: 605 attr->min_int = static_cast<int32_t>(map_entry.value.data); 606 break; 607 case ResTable_map::ATTR_MAX: 608 attr->max_int = static_cast<int32_t>(map_entry.value.data); 609 break; 610 } 611 continue; 612 } 613 614 if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { 615 Attribute::Symbol symbol; 616 symbol.value = util::DeviceToHost32(map_entry.value.data); 617 symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident)); 618 attr->symbols.push_back(std::move(symbol)); 619 } 620 } 621 622 // TODO(adamlesinski): Find i80n, attributes. 623 return attr; 624 } 625 626 std::unique_ptr<Array> BinaryResourceParser::ParseArray(const ResourceNameRef& name, 627 const ConfigDescription& config, 628 const ResTable_map_entry* map) { 629 std::unique_ptr<Array> array = util::make_unique<Array>(); 630 for (const ResTable_map& map_entry : map) { 631 array->elements.push_back(ParseValue(name, config, map_entry.value)); 632 } 633 return array; 634 } 635 636 std::unique_ptr<Plural> BinaryResourceParser::ParsePlural(const ResourceNameRef& name, 637 const ConfigDescription& config, 638 const ResTable_map_entry* map) { 639 std::unique_ptr<Plural> plural = util::make_unique<Plural>(); 640 for (const ResTable_map& map_entry : map) { 641 std::unique_ptr<Item> item = ParseValue(name, config, map_entry.value); 642 if (!item) { 643 return {}; 644 } 645 646 switch (util::DeviceToHost32(map_entry.name.ident)) { 647 case ResTable_map::ATTR_ZERO: 648 plural->values[Plural::Zero] = std::move(item); 649 break; 650 case ResTable_map::ATTR_ONE: 651 plural->values[Plural::One] = std::move(item); 652 break; 653 case ResTable_map::ATTR_TWO: 654 plural->values[Plural::Two] = std::move(item); 655 break; 656 case ResTable_map::ATTR_FEW: 657 plural->values[Plural::Few] = std::move(item); 658 break; 659 case ResTable_map::ATTR_MANY: 660 plural->values[Plural::Many] = std::move(item); 661 break; 662 case ResTable_map::ATTR_OTHER: 663 plural->values[Plural::Other] = std::move(item); 664 break; 665 } 666 } 667 return plural; 668 } 669 670 } // namespace aapt 671