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 "link/XmlCompatVersioner.h" 18 19 #include <algorithm> 20 21 #include "util/Util.h" 22 23 namespace aapt { 24 25 static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string_pool) { 26 xml::Attribute dst{src.namespace_uri, src.name, src.value, src.compiled_attribute}; 27 if (src.compiled_value != nullptr) { 28 dst.compiled_value.reset(src.compiled_value->Clone(out_string_pool)); 29 } 30 return dst; 31 } 32 33 // Returns false if the attribute is not copied because an existing attribute takes precedence 34 // (came from a rule). 35 static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::Element* dst_el, 36 StringPool* out_string_pool) { 37 xml::Attribute* dst_attr = dst_el->FindAttribute(src_attr.namespace_uri, src_attr.name); 38 if (dst_attr != nullptr) { 39 if (generated) { 40 // Generated attributes always take precedence. 41 dst_attr->value = src_attr.value; 42 dst_attr->compiled_attribute = src_attr.compiled_attribute; 43 if (src_attr.compiled_value != nullptr) { 44 dst_attr->compiled_value.reset(src_attr.compiled_value->Clone(out_string_pool)); 45 } 46 return true; 47 } 48 return false; 49 } 50 dst_el->attributes.push_back(CopyAttr(src_attr, out_string_pool)); 51 return true; 52 } 53 54 void XmlCompatVersioner::ProcessRule(const xml::Element& src_el, const xml::Attribute& src_attr, 55 const ApiVersion& src_attr_version, const IDegradeRule* rule, 56 const util::Range<ApiVersion>& api_range, bool generated, 57 xml::Element* dst_el, 58 std::set<ApiVersion>* out_apis_referenced, 59 StringPool* out_string_pool) { 60 if (src_attr_version <= api_range.start) { 61 // The API is compatible, so don't check the rule and just copy. 62 if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) { 63 // TODO(adamlesinski): Log a warning that an attribute was overridden? 64 } 65 return; 66 } 67 68 if (api_range.start >= SDK_LOLLIPOP_MR1) { 69 // Since LOLLIPOP MR1, the framework can handle silently ignoring unknown public attributes, 70 // so we don't need to erase/version them. 71 // Copy. 72 if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) { 73 // TODO(adamlesinski): Log a warning that an attribute was overridden? 74 } 75 } else { 76 // We are going to erase this attribute from this XML resource version, but check if 77 // we even need to move it anywhere. A developer may have effectively overwritten it with 78 // a similarly versioned XML resource. 79 if (src_attr_version < api_range.end) { 80 // There is room for another versioned XML resource between this XML resource and the next 81 // versioned XML resource defined by the developer. 82 out_apis_referenced->insert(std::min<ApiVersion>(src_attr_version, SDK_LOLLIPOP_MR1)); 83 } 84 } 85 86 if (rule != nullptr) { 87 for (const DegradeResult& result : rule->Degrade(src_el, src_attr, out_string_pool)) { 88 const ResourceId attr_resid = result.attr.compiled_attribute.value().id.value(); 89 const ApiVersion attr_version = FindAttributeSdkLevel(attr_resid); 90 91 auto iter = rules_->find(attr_resid); 92 ProcessRule(src_el, result.attr, attr_version, 93 iter != rules_->end() ? iter->second.get() : nullptr, api_range, 94 true /*generated*/, dst_el, out_apis_referenced, out_string_pool); 95 } 96 } 97 } 98 99 XmlCompatVersioner::XmlCompatVersioner(const Rules* rules) : rules_(rules) { 100 } 101 102 std::unique_ptr<xml::XmlResource> XmlCompatVersioner::ProcessDoc( 103 ApiVersion target_api, ApiVersion max_api, xml::XmlResource* doc, 104 std::set<ApiVersion>* out_apis_referenced) { 105 const util::Range<ApiVersion> api_range{target_api, max_api}; 106 107 std::unique_ptr<xml::XmlResource> cloned_doc = util::make_unique<xml::XmlResource>(doc->file); 108 cloned_doc->file.config.sdkVersion = static_cast<uint16_t>(target_api); 109 110 cloned_doc->root = doc->root->CloneElement([&](const xml::Element& el, xml::Element* out_el) { 111 for (const auto& attr : el.attributes) { 112 if (!attr.compiled_attribute) { 113 // Just copy if this isn't a compiled attribute. 114 out_el->attributes.push_back(CopyAttr(attr, &cloned_doc->string_pool)); 115 continue; 116 } 117 118 const ResourceId attr_resid = attr.compiled_attribute.value().id.value(); 119 const ApiVersion attr_version = FindAttributeSdkLevel(attr_resid); 120 121 auto rule = rules_->find(attr_resid); 122 ProcessRule(el, attr, attr_version, rule != rules_->end() ? rule->second.get() : nullptr, 123 api_range, false /*generated*/, out_el, out_apis_referenced, 124 &cloned_doc->string_pool); 125 } 126 }); 127 return cloned_doc; 128 } 129 130 std::vector<std::unique_ptr<xml::XmlResource>> XmlCompatVersioner::Process( 131 IAaptContext* context, xml::XmlResource* doc, util::Range<ApiVersion> api_range) { 132 // Adjust the API range so that it falls after this document and after minSdkVersion. 133 api_range.start = std::max(api_range.start, context->GetMinSdkVersion()); 134 api_range.start = std::max(api_range.start, static_cast<ApiVersion>(doc->file.config.sdkVersion)); 135 136 std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs; 137 std::set<ApiVersion> apis_referenced; 138 versioned_docs.push_back(ProcessDoc(api_range.start, api_range.end, doc, &apis_referenced)); 139 140 // Adjust the sdkVersion of the first XML document back to its original (this only really 141 // makes a difference if the sdk version was below the minSdk to start). 142 versioned_docs.back()->file.config.sdkVersion = doc->file.config.sdkVersion; 143 144 // Iterate from smallest to largest API version. 145 for (ApiVersion api : apis_referenced) { 146 std::set<ApiVersion> dummy; 147 versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &dummy)); 148 } 149 return versioned_docs; 150 } 151 152 DegradeToManyRule::DegradeToManyRule(std::vector<ReplacementAttr> attrs) 153 : attrs_(std::move(attrs)) { 154 } 155 156 static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>& src, 157 StringPool* out_string_pool) { 158 if (src == nullptr) { 159 return {}; 160 } 161 return std::unique_ptr<Item>(src->Clone(out_string_pool)); 162 } 163 164 std::vector<DegradeResult> DegradeToManyRule::Degrade(const xml::Element& src_el, 165 const xml::Attribute& src_attr, 166 StringPool* out_string_pool) const { 167 std::vector<DegradeResult> result; 168 result.reserve(attrs_.size()); 169 for (const ReplacementAttr& attr : attrs_) { 170 result.push_back( 171 DegradeResult{xml::Attribute{xml::kSchemaAndroid, attr.name, src_attr.value, 172 xml::AaptAttribute{attr.attr, attr.id}, 173 CloneIfNotNull(src_attr.compiled_value, out_string_pool)}, 174 FindAttributeSdkLevel(attr.id)}); 175 } 176 return result; 177 } 178 179 } // namespace aapt 180