Home | History | Annotate | Download | only in configuration
      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