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