1 /* 2 * Copyright (C) 2017 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 "configuration/ConfigurationParser.h" 18 19 #include <algorithm> 20 #include <functional> 21 #include <map> 22 #include <memory> 23 #include <string> 24 #include <utility> 25 26 #include "android-base/file.h" 27 #include "android-base/logging.h" 28 29 #include "ConfigDescription.h" 30 #include "Diagnostics.h" 31 #include "ResourceUtils.h" 32 #include "configuration/ConfigurationParser.internal.h" 33 #include "io/File.h" 34 #include "io/FileSystem.h" 35 #include "io/StringStream.h" 36 #include "util/Files.h" 37 #include "util/Maybe.h" 38 #include "util/Util.h" 39 #include "xml/XmlActionExecutor.h" 40 #include "xml/XmlDom.h" 41 #include "xml/XmlUtil.h" 42 43 namespace aapt { 44 45 namespace { 46 47 using ::aapt::configuration::Abi; 48 using ::aapt::configuration::AndroidManifest; 49 using ::aapt::configuration::AndroidSdk; 50 using ::aapt::configuration::ConfiguredArtifact; 51 using ::aapt::configuration::DeviceFeature; 52 using ::aapt::configuration::Entry; 53 using ::aapt::configuration::ExtractConfiguration; 54 using ::aapt::configuration::GlTexture; 55 using ::aapt::configuration::Group; 56 using ::aapt::configuration::Locale; 57 using ::aapt::configuration::OrderedEntry; 58 using ::aapt::configuration::OutputArtifact; 59 using ::aapt::configuration::PostProcessingConfiguration; 60 using ::aapt::configuration::handler::AbiGroupTagHandler; 61 using ::aapt::configuration::handler::AndroidSdkTagHandler; 62 using ::aapt::configuration::handler::ArtifactFormatTagHandler; 63 using ::aapt::configuration::handler::ArtifactTagHandler; 64 using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler; 65 using ::aapt::configuration::handler::GlTextureGroupTagHandler; 66 using ::aapt::configuration::handler::LocaleGroupTagHandler; 67 using ::aapt::configuration::handler::ScreenDensityGroupTagHandler; 68 using ::aapt::io::IFile; 69 using ::aapt::io::RegularFile; 70 using ::aapt::io::StringInputStream; 71 using ::aapt::util::TrimWhitespace; 72 using ::aapt::xml::Element; 73 using ::aapt::xml::NodeCast; 74 using ::aapt::xml::XmlActionExecutor; 75 using ::aapt::xml::XmlActionExecutorPolicy; 76 using ::aapt::xml::XmlNodeAction; 77 using ::android::StringPiece; 78 using ::android::base::ReadFileToString; 79 80 const std::unordered_map<StringPiece, Abi> kStringToAbiMap = { 81 {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a}, 82 {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips}, 83 {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal}, 84 }; 85 const std::array<StringPiece, 8> kAbiToStringMap = { 86 {"armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64", "universal"}}; 87 88 constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt"; 89 90 /** A default noop diagnostics context. */ 91 class NoopDiagnostics : public IDiagnostics { 92 public: 93 void Log(Level level, DiagMessageActual& actualMsg) override {} 94 }; 95 NoopDiagnostics noop_; 96 97 /** Returns the value of the label attribute for a given element. */ 98 std::string GetLabel(const Element* element, IDiagnostics* diag) { 99 std::string label; 100 for (const auto& attr : element->attributes) { 101 if (attr.name == "label") { 102 label = attr.value; 103 break; 104 } 105 } 106 107 if (label.empty()) { 108 diag->Error(DiagMessage() << "No label found for element " << element->name); 109 } 110 return label; 111 } 112 113 /** Returns the value of the version-code-order attribute for a given element. */ 114 Maybe<int32_t> GetVersionCodeOrder(const Element* element, IDiagnostics* diag) { 115 const xml::Attribute* version = element->FindAttribute("", "version-code-order"); 116 if (version == nullptr) { 117 std::string label = GetLabel(element, diag); 118 diag->Error(DiagMessage() << "No version-code-order found for element '" << element->name 119 << "' with label '" << label << "'"); 120 return {}; 121 } 122 return std::stoi(version->value); 123 } 124 125 /** XML node visitor that removes all of the namespace URIs from the node and all children. */ 126 class NamespaceVisitor : public xml::Visitor { 127 public: 128 void Visit(xml::Element* node) override { 129 node->namespace_uri.clear(); 130 VisitChildren(node); 131 } 132 }; 133 134 /** Copies the values referenced in a configuration group to the target list. */ 135 template <typename T> 136 bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups, 137 std::vector<T>* target) { 138 // If there was no item configured, there is nothing to do and no error. 139 if (!name) { 140 return true; 141 } 142 143 // If the group could not be found, then something is wrong. 144 auto group = groups.find(name.value()); 145 if (group == groups.end()) { 146 return false; 147 } 148 149 for (const T& item : group->second.entry) { 150 target->push_back(item); 151 } 152 return true; 153 } 154 155 /** 156 * Attempts to replace the placeholder in the name string with the provided value. Returns true on 157 * success, or false if the either the placeholder is not found in the name, or the value is not 158 * present and the placeholder was. 159 */ 160 bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value, 161 std::string* name, IDiagnostics* diag) { 162 size_t offset = name->find(placeholder.data()); 163 bool found = (offset != std::string::npos); 164 165 // Make sure the placeholder was present if the desired value is present. 166 if (!found) { 167 if (value) { 168 diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); 169 return false; 170 } 171 return true; 172 } 173 174 DCHECK(found) << "Missing return path for placeholder not found"; 175 176 // Make sure the placeholder was not present if the desired value was not present. 177 if (!value) { 178 diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); 179 return false; 180 } 181 182 name->replace(offset, placeholder.length(), value.value().data()); 183 184 // Make sure there was only one instance of the placeholder. 185 if (name->find(placeholder.data()) != std::string::npos) { 186 diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder); 187 return false; 188 } 189 return true; 190 } 191 192 /** 193 * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the 194 * element was successfully processed, otherwise returns false. 195 */ 196 using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config, 197 xml::Element* element, IDiagnostics* diag)>; 198 199 /** Binds an ActionHandler to the current configuration being populated. */ 200 xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfiguration* config, 201 const ActionHandler& handler) { 202 return [config, handler](xml::Element* root_element, SourcePathDiagnostics* diag) { 203 return handler(config, root_element, diag); 204 }; 205 } 206 207 /** Converts a ConfiguredArtifact into an OutputArtifact. */ 208 Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, 209 const std::string& apk_name, 210 const PostProcessingConfiguration& config, 211 IDiagnostics* diag) { 212 if (!artifact.name && !config.artifact_format) { 213 diag->Error( 214 DiagMessage() << "Artifact does not have a name and no global name template defined"); 215 return {}; 216 } 217 218 Maybe<std::string> artifact_name = 219 (artifact.name) ? artifact.Name(apk_name, diag) 220 : artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag); 221 222 if (!artifact_name) { 223 diag->Error(DiagMessage() << "Could not determine split APK artifact name"); 224 return {}; 225 } 226 227 OutputArtifact output_artifact; 228 output_artifact.name = artifact_name.value(); 229 230 SourcePathDiagnostics src_diag{{output_artifact.name}, diag}; 231 bool has_errors = false; 232 233 if (!CopyXmlReferences(artifact.abi_group, config.abi_groups, &output_artifact.abis)) { 234 src_diag.Error(DiagMessage() << "Could not lookup required ABIs: " 235 << artifact.abi_group.value()); 236 has_errors = true; 237 } 238 239 if (!CopyXmlReferences(artifact.locale_group, config.locale_groups, &output_artifact.locales)) { 240 src_diag.Error(DiagMessage() << "Could not lookup required locales: " 241 << artifact.locale_group.value()); 242 has_errors = true; 243 } 244 245 if (!CopyXmlReferences(artifact.screen_density_group, config.screen_density_groups, 246 &output_artifact.screen_densities)) { 247 src_diag.Error(DiagMessage() << "Could not lookup required screen densities: " 248 << artifact.screen_density_group.value()); 249 has_errors = true; 250 } 251 252 if (!CopyXmlReferences(artifact.device_feature_group, config.device_feature_groups, 253 &output_artifact.features)) { 254 src_diag.Error(DiagMessage() << "Could not lookup required device features: " 255 << artifact.device_feature_group.value()); 256 has_errors = true; 257 } 258 259 if (!CopyXmlReferences(artifact.gl_texture_group, config.gl_texture_groups, 260 &output_artifact.textures)) { 261 src_diag.Error(DiagMessage() << "Could not lookup required OpenGL texture formats: " 262 << artifact.gl_texture_group.value()); 263 has_errors = true; 264 } 265 266 if (artifact.android_sdk) { 267 auto entry = config.android_sdks.find(artifact.android_sdk.value()); 268 if (entry == config.android_sdks.end()) { 269 src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: " 270 << artifact.android_sdk.value()); 271 has_errors = true; 272 } else { 273 output_artifact.android_sdk = {entry->second}; 274 } 275 } 276 277 if (has_errors) { 278 return {}; 279 } 280 return {output_artifact}; 281 } 282 283 } // namespace 284 285 namespace configuration { 286 287 /** Returns the binary reprasentation of the XML configuration. */ 288 Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, 289 const std::string& config_path, 290 IDiagnostics* diag) { 291 StringInputStream in(contents); 292 std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path)); 293 if (!doc) { 294 return {}; 295 } 296 297 // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace. 298 Element* root = doc->root.get(); 299 if (root == nullptr) { 300 diag->Error(DiagMessage() << "Could not find the root element in the XML document"); 301 return {}; 302 } 303 304 std::string& xml_ns = root->namespace_uri; 305 if (!xml_ns.empty()) { 306 if (xml_ns != kAaptXmlNs) { 307 diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns); 308 return {}; 309 } 310 311 xml_ns.clear(); 312 NamespaceVisitor visitor; 313 root->Accept(&visitor); 314 } 315 316 XmlActionExecutor executor; 317 XmlNodeAction& root_action = executor["post-process"]; 318 XmlNodeAction& artifacts_action = root_action["artifacts"]; 319 320 PostProcessingConfiguration config; 321 322 // Parse the artifact elements. 323 artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler)); 324 artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler)); 325 326 // Parse the different configuration groups. 327 root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler)); 328 root_action["screen-density-groups"]["screen-density-group"].Action( 329 Bind(&config, ScreenDensityGroupTagHandler)); 330 root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler)); 331 root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler)); 332 root_action["gl-texture-groups"]["gl-texture-group"].Action( 333 Bind(&config, GlTextureGroupTagHandler)); 334 root_action["device-feature-groups"]["device-feature-group"].Action( 335 Bind(&config, DeviceFeatureGroupTagHandler)); 336 337 if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) { 338 diag->Error(DiagMessage() << "Could not process XML document"); 339 return {}; 340 } 341 342 return {config}; 343 } 344 345 const StringPiece& AbiToString(Abi abi) { 346 return kAbiToStringMap.at(static_cast<size_t>(abi)); 347 } 348 349 /** 350 * Returns the common artifact base name from a template string. 351 */ 352 Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) { 353 const StringPiece ext = file::GetExtension(apk_name); 354 size_t end_index = apk_name.to_string().rfind(ext.to_string()); 355 const std::string base_name = 356 (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : ""; 357 358 // Base name is optional. 359 if (result.find("${basename}") != std::string::npos) { 360 Maybe<StringPiece> maybe_base_name = 361 base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name}; 362 if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) { 363 return {}; 364 } 365 } 366 367 // Extension is optional. 368 if (result.find("${ext}") != std::string::npos) { 369 // Make sure we disregard the '.' in the extension when replacing the placeholder. 370 if (!ReplacePlaceholder("${ext}", {ext.substr(1)}, &result, diag)) { 371 return {}; 372 } 373 } else { 374 // If no extension is specified, and the name template does not end in the current extension, 375 // add the existing extension. 376 if (!util::EndsWith(result, ext)) { 377 result.append(ext.to_string()); 378 } 379 } 380 381 return result; 382 } 383 384 Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format, 385 const StringPiece& apk_name, 386 IDiagnostics* diag) const { 387 Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag); 388 if (!base) { 389 return {}; 390 } 391 std::string result = std::move(base.value()); 392 393 if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) { 394 return {}; 395 } 396 397 if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) { 398 return {}; 399 } 400 401 if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) { 402 return {}; 403 } 404 405 if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) { 406 return {}; 407 } 408 409 if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) { 410 return {}; 411 } 412 413 if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) { 414 return {}; 415 } 416 417 return result; 418 } 419 420 Maybe<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const { 421 if (!name) { 422 return {}; 423 } 424 425 return ToBaseName(name.value(), apk_name, diag); 426 } 427 428 } // namespace configuration 429 430 /** Returns a ConfigurationParser for the file located at the provided path. */ 431 Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) { 432 std::string contents; 433 if (!ReadFileToString(path, &contents, true)) { 434 return {}; 435 } 436 return ConfigurationParser(contents, path); 437 } 438 439 ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path) 440 : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) { 441 } 442 443 Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse( 444 const android::StringPiece& apk_path) { 445 Maybe<PostProcessingConfiguration> maybe_config = 446 ExtractConfiguration(contents_, config_path_, diag_); 447 if (!maybe_config) { 448 return {}; 449 } 450 451 // Convert from a parsed configuration to a list of artifacts for processing. 452 const std::string& apk_name = file::GetFilename(apk_path).to_string(); 453 std::vector<OutputArtifact> output_artifacts; 454 455 PostProcessingConfiguration& config = maybe_config.value(); 456 457 bool valid = true; 458 int version = 1; 459 460 for (const ConfiguredArtifact& artifact : config.artifacts) { 461 Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_); 462 if (!output_artifact) { 463 // Defer return an error condition so that all errors are reported. 464 valid = false; 465 } else { 466 output_artifact.value().version = version++; 467 output_artifacts.push_back(std::move(output_artifact.value())); 468 } 469 } 470 471 if (!config.ValidateVersionCodeOrdering(diag_)) { 472 diag_->Error(DiagMessage() << "could not validate post processing configuration"); 473 valid = false; 474 } 475 476 if (valid) { 477 // Sorting artifacts requires that all references are valid as it uses them to determine order. 478 config.SortArtifacts(); 479 } 480 481 if (!valid) { 482 return {}; 483 } 484 485 return {output_artifacts}; 486 } 487 488 namespace configuration { 489 namespace handler { 490 491 bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element, 492 IDiagnostics* diag) { 493 ConfiguredArtifact artifact{}; 494 for (const auto& attr : root_element->attributes) { 495 if (attr.name == "name") { 496 artifact.name = attr.value; 497 } else if (attr.name == "abi-group") { 498 artifact.abi_group = {attr.value}; 499 } else if (attr.name == "screen-density-group") { 500 artifact.screen_density_group = {attr.value}; 501 } else if (attr.name == "locale-group") { 502 artifact.locale_group = {attr.value}; 503 } else if (attr.name == "android-sdk") { 504 artifact.android_sdk = {attr.value}; 505 } else if (attr.name == "gl-texture-group") { 506 artifact.gl_texture_group = {attr.value}; 507 } else if (attr.name == "device-feature-group") { 508 artifact.device_feature_group = {attr.value}; 509 } else { 510 diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " 511 << attr.value); 512 } 513 } 514 config->artifacts.push_back(artifact); 515 return true; 516 }; 517 518 bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root_element, 519 IDiagnostics* /* diag */) { 520 for (auto& node : root_element->children) { 521 xml::Text* t; 522 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { 523 config->artifact_format = TrimWhitespace(t->text).to_string(); 524 break; 525 } 526 } 527 return true; 528 }; 529 530 bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, 531 IDiagnostics* diag) { 532 std::string label = GetLabel(root_element, diag); 533 if (label.empty()) { 534 return false; 535 } 536 537 bool valid = true; 538 OrderedEntry<Abi>& entry = config->abi_groups[label]; 539 Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); 540 if (!order) { 541 valid = false; 542 } else { 543 entry.order = order.value(); 544 } 545 auto& group = entry.entry; 546 547 // Special case for empty abi-group tag. Label will be used as the ABI. 548 if (root_element->GetChildElements().empty()) { 549 auto abi = kStringToAbiMap.find(label); 550 if (abi == kStringToAbiMap.end()) { 551 return false; 552 } 553 group.push_back(abi->second); 554 return valid; 555 } 556 557 for (auto* child : root_element->GetChildElements()) { 558 if (child->name != "abi") { 559 diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name); 560 valid = false; 561 } else { 562 for (auto& node : child->children) { 563 xml::Text* t; 564 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { 565 auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string()); 566 if (abi != kStringToAbiMap.end()) { 567 group.push_back(abi->second); 568 } else { 569 diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text); 570 valid = false; 571 } 572 break; 573 } 574 } 575 } 576 } 577 578 return valid; 579 }; 580 581 bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, 582 IDiagnostics* diag) { 583 std::string label = GetLabel(root_element, diag); 584 if (label.empty()) { 585 return false; 586 } 587 588 bool valid = true; 589 OrderedEntry<ConfigDescription>& entry = config->screen_density_groups[label]; 590 Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); 591 if (!order) { 592 valid = false; 593 } else { 594 entry.order = order.value(); 595 } 596 auto& group = entry.entry; 597 598 // Special case for empty screen-density-group tag. Label will be used as the screen density. 599 if (root_element->GetChildElements().empty()) { 600 ConfigDescription config_descriptor; 601 bool parsed = ConfigDescription::Parse(label, &config_descriptor); 602 if (parsed && 603 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == 604 android::ResTable_config::CONFIG_DENSITY)) { 605 // Copy the density with the minimum SDK version stripped out. 606 group.push_back(config_descriptor.CopyWithoutSdkVersion()); 607 } else { 608 diag->Error(DiagMessage() 609 << "Could not parse config descriptor for empty screen-density-group: " 610 << label); 611 valid = false; 612 } 613 614 return valid; 615 } 616 617 for (auto* child : root_element->GetChildElements()) { 618 if (child->name != "screen-density") { 619 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " 620 << child->name); 621 valid = false; 622 } else { 623 for (auto& node : child->children) { 624 xml::Text* t; 625 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { 626 ConfigDescription config_descriptor; 627 const android::StringPiece& text = TrimWhitespace(t->text); 628 bool parsed = ConfigDescription::Parse(text, &config_descriptor); 629 if (parsed && 630 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == 631 android::ResTable_config::CONFIG_DENSITY)) { 632 // Copy the density with the minimum SDK version stripped out. 633 group.push_back(config_descriptor.CopyWithoutSdkVersion()); 634 } else { 635 diag->Error(DiagMessage() 636 << "Could not parse config descriptor for screen-density: " << text); 637 valid = false; 638 } 639 break; 640 } 641 } 642 } 643 } 644 645 return valid; 646 }; 647 648 bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, 649 IDiagnostics* diag) { 650 std::string label = GetLabel(root_element, diag); 651 if (label.empty()) { 652 return false; 653 } 654 655 bool valid = true; 656 OrderedEntry<ConfigDescription>& entry = config->locale_groups[label]; 657 Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); 658 if (!order) { 659 valid = false; 660 } else { 661 entry.order = order.value(); 662 } 663 auto& group = entry.entry; 664 665 // Special case to auto insert a locale for an empty group. Label will be used for locale. 666 if (root_element->GetChildElements().empty()) { 667 ConfigDescription config_descriptor; 668 bool parsed = ConfigDescription::Parse(label, &config_descriptor); 669 if (parsed && 670 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == 671 android::ResTable_config::CONFIG_LOCALE)) { 672 // Copy the locale with the minimum SDK version stripped out. 673 group.push_back(config_descriptor.CopyWithoutSdkVersion()); 674 } else { 675 diag->Error(DiagMessage() 676 << "Could not parse config descriptor for empty screen-density-group: " 677 << label); 678 valid = false; 679 } 680 681 return valid; 682 } 683 684 for (auto* child : root_element->GetChildElements()) { 685 if (child->name != "locale") { 686 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " 687 << child->name); 688 valid = false; 689 } else { 690 for (auto& node : child->children) { 691 xml::Text* t; 692 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { 693 ConfigDescription config_descriptor; 694 const android::StringPiece& text = TrimWhitespace(t->text); 695 bool parsed = ConfigDescription::Parse(text, &config_descriptor); 696 if (parsed && 697 (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == 698 android::ResTable_config::CONFIG_LOCALE)) { 699 // Copy the locale with the minimum SDK version stripped out. 700 group.push_back(config_descriptor.CopyWithoutSdkVersion()); 701 } else { 702 diag->Error(DiagMessage() 703 << "Could not parse config descriptor for screen-density: " << text); 704 valid = false; 705 } 706 break; 707 } 708 } 709 } 710 } 711 712 return valid; 713 }; 714 715 bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element, 716 IDiagnostics* diag) { 717 AndroidSdk entry = AndroidSdk::ForMinSdk(-1); 718 bool valid = true; 719 for (const auto& attr : root_element->attributes) { 720 bool valid_attr = false; 721 if (attr.name == "label") { 722 entry.label = attr.value; 723 valid_attr = true; 724 } else if (attr.name == "minSdkVersion") { 725 Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value); 726 if (version) { 727 valid_attr = true; 728 entry.min_sdk_version = version.value(); 729 } 730 } else if (attr.name == "targetSdkVersion") { 731 Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value); 732 if (version) { 733 valid_attr = true; 734 entry.target_sdk_version = version; 735 } 736 } else if (attr.name == "maxSdkVersion") { 737 Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value); 738 if (version) { 739 valid_attr = true; 740 entry.max_sdk_version = version; 741 } 742 } 743 744 if (!valid_attr) { 745 diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value); 746 valid = false; 747 } 748 } 749 750 if (entry.min_sdk_version == -1) { 751 diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute"); 752 valid = false; 753 } 754 755 // TODO: Fill in the manifest details when they are finalised. 756 for (auto node : root_element->GetChildElements()) { 757 if (node->name == "manifest") { 758 if (entry.manifest) { 759 diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); 760 continue; 761 } 762 entry.manifest = {AndroidManifest()}; 763 } 764 } 765 766 config->android_sdks[entry.label] = entry; 767 return valid; 768 }; 769 770 bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, 771 IDiagnostics* diag) { 772 std::string label = GetLabel(root_element, diag); 773 if (label.empty()) { 774 return false; 775 } 776 777 bool valid = true; 778 OrderedEntry<GlTexture>& entry = config->gl_texture_groups[label]; 779 Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); 780 if (!order) { 781 valid = false; 782 } else { 783 entry.order = order.value(); 784 } 785 auto& group = entry.entry; 786 787 GlTexture result; 788 for (auto* child : root_element->GetChildElements()) { 789 if (child->name != "gl-texture") { 790 diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name); 791 valid = false; 792 } else { 793 for (const auto& attr : child->attributes) { 794 if (attr.name == "name") { 795 result.name = attr.value; 796 break; 797 } 798 } 799 800 for (auto* element : child->GetChildElements()) { 801 if (element->name != "texture-path") { 802 diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name); 803 valid = false; 804 continue; 805 } 806 for (auto& node : element->children) { 807 xml::Text* t; 808 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { 809 result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); 810 } 811 } 812 } 813 } 814 group.push_back(result); 815 } 816 817 return valid; 818 }; 819 820 bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, 821 IDiagnostics* diag) { 822 std::string label = GetLabel(root_element, diag); 823 if (label.empty()) { 824 return false; 825 } 826 827 bool valid = true; 828 OrderedEntry<DeviceFeature>& entry = config->device_feature_groups[label]; 829 Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); 830 if (!order) { 831 valid = false; 832 } else { 833 entry.order = order.value(); 834 } 835 auto& group = entry.entry; 836 837 for (auto* child : root_element->GetChildElements()) { 838 if (child->name != "supports-feature") { 839 diag->Error(DiagMessage() << "Unexpected root_element in device feature group: " 840 << child->name); 841 valid = false; 842 } else { 843 for (auto& node : child->children) { 844 xml::Text* t; 845 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { 846 group.push_back(TrimWhitespace(t->text).to_string()); 847 break; 848 } 849 } 850 } 851 } 852 853 return valid; 854 }; 855 856 } // namespace handler 857 } // namespace configuration 858 859 } // namespace aapt 860