1 /*
2 * Copyright 2023 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 "link/FeatureFlagsFilter.h"
18
19 #include <string_view>
20
21 #include "androidfw/IDiagnostics.h"
22 #include "androidfw/Source.h"
23 #include "util/Util.h"
24 #include "xml/XmlDom.h"
25 #include "xml/XmlUtil.h"
26
27 using ::aapt::xml::Element;
28 using ::aapt::xml::Node;
29 using ::aapt::xml::NodeCast;
30
31 namespace aapt {
32
33 class FlagsVisitor : public xml::Visitor {
34 public:
FlagsVisitor(android::IDiagnostics * diagnostics,const FeatureFlagValues & feature_flag_values,const FeatureFlagsFilterOptions & options)35 explicit FlagsVisitor(android::IDiagnostics* diagnostics,
36 const FeatureFlagValues& feature_flag_values,
37 const FeatureFlagsFilterOptions& options)
38 : diagnostics_(diagnostics), feature_flag_values_(feature_flag_values), options_(options) {
39 }
40
Visit(xml::Element * node)41 void Visit(xml::Element* node) override {
42 std::erase_if(node->children,
43 [this](std::unique_ptr<xml::Node>& node) { return ShouldRemove(node); });
44 VisitChildren(node);
45 }
46
HasError() const47 bool HasError() const {
48 return has_error_;
49 }
50
51 private:
ShouldRemove(std::unique_ptr<xml::Node> & node)52 bool ShouldRemove(std::unique_ptr<xml::Node>& node) {
53 if (const auto* el = NodeCast<Element>(node.get())) {
54 auto* attr = el->FindAttribute(xml::kSchemaAndroid, "featureFlag");
55 if (attr == nullptr) {
56 return false;
57 }
58
59 bool negated = false;
60 std::string_view flag_name = util::TrimWhitespace(attr->value);
61 if (flag_name.starts_with('!')) {
62 negated = true;
63 flag_name = flag_name.substr(1);
64 }
65
66 if (auto it = feature_flag_values_.find(std::string(flag_name));
67 it != feature_flag_values_.end()) {
68 if (it->second.has_value()) {
69 if (options_.remove_disabled_elements) {
70 // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
71 return *it->second == negated;
72 }
73 } else if (options_.flags_must_have_value) {
74 diagnostics_->Error(android::DiagMessage(node->line_number)
75 << "attribute 'android:featureFlag' has flag '" << flag_name
76 << "' without a true/false value from --feature_flags parameter");
77 has_error_ = true;
78 return false;
79 }
80 } else if (options_.fail_on_unrecognized_flags) {
81 diagnostics_->Error(android::DiagMessage(node->line_number)
82 << "attribute 'android:featureFlag' has flag '" << flag_name
83 << "' not found in flags from --feature_flags parameter");
84 has_error_ = true;
85 return false;
86 }
87 }
88
89 return false;
90 }
91
92 android::IDiagnostics* diagnostics_;
93 const FeatureFlagValues& feature_flag_values_;
94 const FeatureFlagsFilterOptions& options_;
95 bool has_error_ = false;
96 };
97
Consume(IAaptContext * context,xml::XmlResource * doc)98 bool FeatureFlagsFilter::Consume(IAaptContext* context, xml::XmlResource* doc) {
99 FlagsVisitor visitor(context->GetDiagnostics(), feature_flag_values_, options_);
100 doc->root->Accept(&visitor);
101 return !visitor.HasError();
102 }
103
104 } // namespace aapt
105