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
43namespace aapt {
44
45namespace {
46
47using ::aapt::configuration::Abi;
48using ::aapt::configuration::AndroidManifest;
49using ::aapt::configuration::AndroidSdk;
50using ::aapt::configuration::ConfiguredArtifact;
51using ::aapt::configuration::DeviceFeature;
52using ::aapt::configuration::Entry;
53using ::aapt::configuration::ExtractConfiguration;
54using ::aapt::configuration::GlTexture;
55using ::aapt::configuration::Group;
56using ::aapt::configuration::Locale;
57using ::aapt::configuration::OrderedEntry;
58using ::aapt::configuration::OutputArtifact;
59using ::aapt::configuration::PostProcessingConfiguration;
60using ::aapt::configuration::handler::AbiGroupTagHandler;
61using ::aapt::configuration::handler::AndroidSdkTagHandler;
62using ::aapt::configuration::handler::ArtifactFormatTagHandler;
63using ::aapt::configuration::handler::ArtifactTagHandler;
64using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
65using ::aapt::configuration::handler::GlTextureGroupTagHandler;
66using ::aapt::configuration::handler::LocaleGroupTagHandler;
67using ::aapt::configuration::handler::ScreenDensityGroupTagHandler;
68using ::aapt::io::IFile;
69using ::aapt::io::RegularFile;
70using ::aapt::io::StringInputStream;
71using ::aapt::util::TrimWhitespace;
72using ::aapt::xml::Element;
73using ::aapt::xml::NodeCast;
74using ::aapt::xml::XmlActionExecutor;
75using ::aapt::xml::XmlActionExecutorPolicy;
76using ::aapt::xml::XmlNodeAction;
77using ::android::StringPiece;
78using ::android::base::ReadFileToString;
79
80const 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};
85const std::array<StringPiece, 8> kAbiToStringMap = {
86    {"armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64", "universal"}};
87
88constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
89
90/** A default noop diagnostics context. */
91class NoopDiagnostics : public IDiagnostics {
92 public:
93  void Log(Level level, DiagMessageActual& actualMsg) override {}
94};
95NoopDiagnostics noop_;
96
97/** Returns the value of the label attribute for a given element. */
98std::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. */
114Maybe<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. */
126class 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. */
135template <typename T>
136bool 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 */
160bool 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 */
196using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config,
197                                         xml::Element* element, IDiagnostics* diag)>;
198
199/** Binds an ActionHandler to the current configuration being populated. */
200xml::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. */
208Maybe<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
285namespace configuration {
286
287/** Returns the binary reprasentation of the XML configuration. */
288Maybe<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
345const 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 */
352Maybe<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
384Maybe<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
420Maybe<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. */
431Maybe<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
439ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
440    : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
441}
442
443Maybe<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
488namespace configuration {
489namespace handler {
490
491bool 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
518bool 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
530bool 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
581bool 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
648bool 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
715bool 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
770bool 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
820bool 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