1/* 2 * Copyright (C) 2015 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 "SdkConstants.h" 18#include "flatten/ChunkWriter.h" 19#include "flatten/ResourceTypeExtensions.h" 20#include "flatten/XmlFlattener.h" 21#include "xml/XmlDom.h" 22 23#include <androidfw/ResourceTypes.h> 24#include <algorithm> 25#include <utils/misc.h> 26#include <vector> 27 28using namespace android; 29 30namespace aapt { 31 32namespace { 33 34constexpr uint32_t kLowPriority = 0xffffffffu; 35 36struct XmlFlattenerVisitor : public xml::Visitor { 37 using xml::Visitor::visit; 38 39 BigBuffer* mBuffer; 40 XmlFlattenerOptions mOptions; 41 StringPool mPool; 42 std::map<uint8_t, StringPool> mPackagePools; 43 44 struct StringFlattenDest { 45 StringPool::Ref ref; 46 ResStringPool_ref* dest; 47 }; 48 std::vector<StringFlattenDest> mStringRefs; 49 50 // Scratch vector to filter attributes. We avoid allocations 51 // making this a member. 52 std::vector<xml::Attribute*> mFilteredAttrs; 53 54 55 XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) : 56 mBuffer(buffer), mOptions(options) { 57 } 58 59 void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) { 60 if (!str.empty()) { 61 mStringRefs.push_back(StringFlattenDest{ 62 mPool.makeRef(str, StringPool::Context{ priority }), 63 dest }); 64 } else { 65 // The device doesn't think a string of size 0 is the same as null. 66 dest->index = util::deviceToHost32(-1); 67 } 68 } 69 70 void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { 71 mStringRefs.push_back(StringFlattenDest{ ref, dest }); 72 } 73 74 void writeNamespace(xml::Namespace* node, uint16_t type) { 75 ChunkWriter writer(mBuffer); 76 77 ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type); 78 flatNode->lineNumber = util::hostToDevice32(node->lineNumber); 79 flatNode->comment.index = util::hostToDevice32(-1); 80 81 ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>(); 82 addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); 83 addString(node->namespaceUri, kLowPriority, &flatNs->uri); 84 85 writer.finish(); 86 } 87 88 void visit(xml::Namespace* node) override { 89 writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); 90 xml::Visitor::visit(node); 91 writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); 92 } 93 94 void visit(xml::Text* node) override { 95 if (util::trimWhitespace(node->text).empty()) { 96 // Skip whitespace only text nodes. 97 return; 98 } 99 100 ChunkWriter writer(mBuffer); 101 ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); 102 flatNode->lineNumber = util::hostToDevice32(node->lineNumber); 103 flatNode->comment.index = util::hostToDevice32(-1); 104 105 ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>(); 106 addString(node->text, kLowPriority, &flatText->data); 107 108 writer.finish(); 109 } 110 111 void visit(xml::Element* node) override { 112 { 113 ChunkWriter startWriter(mBuffer); 114 ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>( 115 RES_XML_START_ELEMENT_TYPE); 116 flatNode->lineNumber = util::hostToDevice32(node->lineNumber); 117 flatNode->comment.index = util::hostToDevice32(-1); 118 119 ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>(); 120 addString(node->namespaceUri, kLowPriority, &flatElem->ns); 121 addString(node->name, kLowPriority, &flatElem->name); 122 flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem)); 123 flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute)); 124 125 writeAttributes(node, flatElem, &startWriter); 126 127 startWriter.finish(); 128 } 129 130 xml::Visitor::visit(node); 131 132 { 133 ChunkWriter endWriter(mBuffer); 134 ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>( 135 RES_XML_END_ELEMENT_TYPE); 136 flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber); 137 flatEndNode->comment.index = util::hostToDevice32(-1); 138 139 ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>(); 140 addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); 141 addString(node->name, kLowPriority, &flatEndElem->name); 142 143 endWriter.finish(); 144 } 145 } 146 147 static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) { 148 if (a->compiledAttribute && a->compiledAttribute.value().id) { 149 if (b->compiledAttribute && b->compiledAttribute.value().id) { 150 return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value(); 151 } 152 return true; 153 } else if (!b->compiledAttribute) { 154 int diff = a->namespaceUri.compare(b->namespaceUri); 155 if (diff < 0) { 156 return true; 157 } else if (diff > 0) { 158 return false; 159 } 160 return a->name < b->name; 161 } 162 return false; 163 } 164 165 void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) { 166 mFilteredAttrs.clear(); 167 mFilteredAttrs.reserve(node->attributes.size()); 168 169 // Filter the attributes. 170 for (xml::Attribute& attr : node->attributes) { 171 if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) { 172 size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value()); 173 if (sdkLevel > mOptions.maxSdkLevel.value()) { 174 continue; 175 } 176 } 177 mFilteredAttrs.push_back(&attr); 178 } 179 180 if (mFilteredAttrs.empty()) { 181 return; 182 } 183 184 const ResourceId kIdAttr(0x010100d0); 185 186 std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById); 187 188 flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size()); 189 190 ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>( 191 mFilteredAttrs.size()); 192 uint16_t attributeIndex = 1; 193 for (const xml::Attribute* xmlAttr : mFilteredAttrs) { 194 // Assign the indices for specific attributes. 195 if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id && 196 xmlAttr->compiledAttribute.value().id.value() == kIdAttr) { 197 flatElem->idIndex = util::hostToDevice16(attributeIndex); 198 } else if (xmlAttr->namespaceUri.empty()) { 199 if (xmlAttr->name == u"class") { 200 flatElem->classIndex = util::hostToDevice16(attributeIndex); 201 } else if (xmlAttr->name == u"style") { 202 flatElem->styleIndex = util::hostToDevice16(attributeIndex); 203 } 204 } 205 attributeIndex++; 206 207 // Add the namespaceUri to the list of StringRefs to encode. 208 addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns); 209 210 flatAttr->rawValue.index = util::hostToDevice32(-1); 211 212 if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) { 213 // The attribute has no associated ResourceID, so the string order doesn't matter. 214 addString(xmlAttr->name, kLowPriority, &flatAttr->name); 215 } else { 216 // Attribute names are stored without packages, but we use 217 // their StringPool index to lookup their resource IDs. 218 // This will cause collisions, so we can't dedupe 219 // attribute names from different packages. We use separate 220 // pools that we later combine. 221 // 222 // Lookup the StringPool for this package and make the reference there. 223 const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value(); 224 225 StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef( 226 xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id }); 227 228 // Add it to the list of strings to flatten. 229 addString(nameRef, &flatAttr->name); 230 } 231 232 if (mOptions.keepRawValues || !xmlAttr->compiledValue) { 233 // Keep raw values if the value is not compiled or 234 // if we're building a static library (need symbols). 235 addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue); 236 } 237 238 if (xmlAttr->compiledValue) { 239 bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue); 240 assert(result); 241 } else { 242 // Flatten as a regular string type. 243 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; 244 addString(xmlAttr->value, kLowPriority, 245 (ResStringPool_ref*) &flatAttr->typedValue.data); 246 } 247 248 flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue)); 249 flatAttr++; 250 } 251 } 252}; 253 254} // namespace 255 256bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) { 257 BigBuffer nodeBuffer(1024); 258 XmlFlattenerVisitor visitor(&nodeBuffer, mOptions); 259 node->accept(&visitor); 260 261 // Merge the package pools into the main pool. 262 for (auto& packagePoolEntry : visitor.mPackagePools) { 263 visitor.mPool.merge(std::move(packagePoolEntry.second)); 264 } 265 266 // Sort the string pool so that attribute resource IDs show up first. 267 visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { 268 return a.context.priority < b.context.priority; 269 }); 270 271 // Now we flatten the string pool references into the correct places. 272 for (const auto& refEntry : visitor.mStringRefs) { 273 refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex()); 274 } 275 276 // Write the XML header. 277 ChunkWriter xmlHeaderWriter(mBuffer); 278 xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE); 279 280 // Flatten the StringPool. 281 StringPool::flattenUtf16(mBuffer, visitor.mPool); 282 283 { 284 // Write the array of resource IDs, indexed by StringPool order. 285 ChunkWriter resIdMapWriter(mBuffer); 286 resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); 287 for (const auto& str : visitor.mPool) { 288 ResourceId id = { str->context.priority }; 289 if (id.id == kLowPriority || !id.isValid()) { 290 // When we see the first non-resource ID, 291 // we're done. 292 break; 293 } 294 295 *resIdMapWriter.nextBlock<uint32_t>() = id.id; 296 } 297 resIdMapWriter.finish(); 298 } 299 300 // Move the nodeBuffer and append it to the out buffer. 301 mBuffer->appendBuffer(std::move(nodeBuffer)); 302 303 // Finish the xml header. 304 xmlHeaderWriter.finish(); 305 return true; 306} 307 308bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) { 309 if (!resource->root) { 310 return false; 311 } 312 return flatten(context, resource->root.get()); 313} 314 315} // namespace aapt 316