blob: ed5b60fe6f051d95d6f340665f1426ad63bc829b [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
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"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "flatten/ChunkWriter.h"
19#include "flatten/ResourceTypeExtensions.h"
20#include "flatten/XmlFlattener.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080021#include "xml/XmlDom.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070022
23#include <androidfw/ResourceTypes.h>
Adam Lesinski803c7c82016-04-06 16:09:43 -070024#include <algorithm>
Adam Lesinski32852a52016-06-15 10:56:41 -070025#include <map>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070026#include <utils/misc.h>
Adam Lesinski467f1712015-11-16 17:35:44 -080027#include <vector>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070028
29using namespace android;
30
31namespace aapt {
32
33namespace {
34
35constexpr uint32_t kLowPriority = 0xffffffffu;
36
37struct XmlFlattenerVisitor : public xml::Visitor {
38 using xml::Visitor::visit;
39
40 BigBuffer* mBuffer;
41 XmlFlattenerOptions mOptions;
42 StringPool mPool;
43 std::map<uint8_t, StringPool> mPackagePools;
44
45 struct StringFlattenDest {
46 StringPool::Ref ref;
47 ResStringPool_ref* dest;
48 };
49 std::vector<StringFlattenDest> mStringRefs;
50
51 // Scratch vector to filter attributes. We avoid allocations
52 // making this a member.
53 std::vector<xml::Attribute*> mFilteredAttrs;
54
55
56 XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
57 mBuffer(buffer), mOptions(options) {
58 }
59
Adam Lesinskid0f116b2016-07-08 15:00:32 -070060 void addString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
Adam Lesinskie1fda9a2016-07-01 14:51:51 -070061 bool treatEmptyStringAsNull = false) {
62 if (str.empty() && treatEmptyStringAsNull) {
63 // Some parts of the runtime treat null differently than empty string.
64 dest->index = util::deviceToHost32(-1);
65 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070066 mStringRefs.push_back(StringFlattenDest{
67 mPool.makeRef(str, StringPool::Context{ priority }),
68 dest });
Adam Lesinski1ab598f2015-08-14 14:26:04 -070069 }
70 }
71
72 void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
73 mStringRefs.push_back(StringFlattenDest{ ref, dest });
74 }
75
76 void writeNamespace(xml::Namespace* node, uint16_t type) {
77 ChunkWriter writer(mBuffer);
78
79 ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
80 flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
81 flatNode->comment.index = util::hostToDevice32(-1);
82
83 ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
84 addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
85 addString(node->namespaceUri, kLowPriority, &flatNs->uri);
86
87 writer.finish();
88 }
89
90 void visit(xml::Namespace* node) override {
Alexandria Cornwalla9ff1402016-08-03 09:44:10 -070091 if (node->namespaceUri == xml::kSchemaTools) {
92 // Skip dedicated tools namespace.
93 xml::Visitor::visit(node);
94 } else {
95 writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
96 xml::Visitor::visit(node);
97 writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
98 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070099 }
100
101 void visit(xml::Text* node) override {
102 if (util::trimWhitespace(node->text).empty()) {
103 // Skip whitespace only text nodes.
104 return;
105 }
106
107 ChunkWriter writer(mBuffer);
108 ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
109 flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
110 flatNode->comment.index = util::hostToDevice32(-1);
111
112 ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
113 addString(node->text, kLowPriority, &flatText->data);
114
115 writer.finish();
116 }
117
118 void visit(xml::Element* node) override {
119 {
120 ChunkWriter startWriter(mBuffer);
121 ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
122 RES_XML_START_ELEMENT_TYPE);
123 flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
124 flatNode->comment.index = util::hostToDevice32(-1);
125
126 ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
Adam Lesinskie1fda9a2016-07-01 14:51:51 -0700127
128 // A missing namespace must be null, not an empty string. Otherwise the runtime
129 // complains.
130 addString(node->namespaceUri, kLowPriority, &flatElem->ns,
131 true /* treatEmptyStringAsNull */);
132 addString(node->name, kLowPriority, &flatElem->name,
133 true /* treatEmptyStringAsNull */);
134
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700135 flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
136 flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
137
138 writeAttributes(node, flatElem, &startWriter);
139
140 startWriter.finish();
141 }
142
143 xml::Visitor::visit(node);
144
145 {
146 ChunkWriter endWriter(mBuffer);
147 ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
148 RES_XML_END_ELEMENT_TYPE);
149 flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
150 flatEndNode->comment.index = util::hostToDevice32(-1);
151
152 ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
Adam Lesinskie1fda9a2016-07-01 14:51:51 -0700153 addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
154 true /* treatEmptyStringAsNull */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700155 addString(node->name, kLowPriority, &flatEndElem->name);
156
157 endWriter.finish();
158 }
159 }
160
161 static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800162 if (a->compiledAttribute && a->compiledAttribute.value().id) {
163 if (b->compiledAttribute && b->compiledAttribute.value().id) {
164 return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700165 }
166 return true;
167 } else if (!b->compiledAttribute) {
168 int diff = a->namespaceUri.compare(b->namespaceUri);
169 if (diff < 0) {
170 return true;
171 } else if (diff > 0) {
172 return false;
173 }
174 return a->name < b->name;
175 }
176 return false;
177 }
178
179 void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
180 mFilteredAttrs.clear();
181 mFilteredAttrs.reserve(node->attributes.size());
182
183 // Filter the attributes.
184 for (xml::Attribute& attr : node->attributes) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800185 if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
186 size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700187 if (sdkLevel > mOptions.maxSdkLevel.value()) {
188 continue;
189 }
190 }
Alexandria Cornwalla9ff1402016-08-03 09:44:10 -0700191 if (attr.namespaceUri == xml::kSchemaTools) {
192 continue;
193 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700194 mFilteredAttrs.push_back(&attr);
195 }
196
197 if (mFilteredAttrs.empty()) {
198 return;
199 }
200
201 const ResourceId kIdAttr(0x010100d0);
202
203 std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
204
205 flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
206
207 ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
208 mFilteredAttrs.size());
209 uint16_t attributeIndex = 1;
210 for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
211 // Assign the indices for specific attributes.
Adam Lesinski64587af2016-02-18 18:33:06 -0800212 if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
213 xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700214 flatElem->idIndex = util::hostToDevice16(attributeIndex);
215 } else if (xmlAttr->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700216 if (xmlAttr->name == "class") {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700217 flatElem->classIndex = util::hostToDevice16(attributeIndex);
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700218 } else if (xmlAttr->name == "style") {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700219 flatElem->styleIndex = util::hostToDevice16(attributeIndex);
220 }
221 }
222 attributeIndex++;
223
Adam Lesinskie1fda9a2016-07-01 14:51:51 -0700224 // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
225 // is empty (doesn't exist).
226 addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
227 true /* treatEmptyStringAsNull */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700228
229 flatAttr->rawValue.index = util::hostToDevice32(-1);
230
Adam Lesinski64587af2016-02-18 18:33:06 -0800231 if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700232 // The attribute has no associated ResourceID, so the string order doesn't matter.
233 addString(xmlAttr->name, kLowPriority, &flatAttr->name);
234 } else {
235 // Attribute names are stored without packages, but we use
236 // their StringPool index to lookup their resource IDs.
237 // This will cause collisions, so we can't dedupe
238 // attribute names from different packages. We use separate
239 // pools that we later combine.
240 //
241 // Lookup the StringPool for this package and make the reference there.
242 const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
243
Adam Lesinski64587af2016-02-18 18:33:06 -0800244 StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
245 xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700246
247 // Add it to the list of strings to flatten.
248 addString(nameRef, &flatAttr->name);
Adam Lesinski64587af2016-02-18 18:33:06 -0800249 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700250
Adam Lesinski64587af2016-02-18 18:33:06 -0800251 if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
252 // Keep raw values if the value is not compiled or
253 // if we're building a static library (need symbols).
254 addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700255 }
256
257 if (xmlAttr->compiledValue) {
258 bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
259 assert(result);
260 } else {
261 // Flatten as a regular string type.
262 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700263 addString(xmlAttr->value, kLowPriority,
264 (ResStringPool_ref*) &flatAttr->typedValue.data);
265 }
266
267 flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
268 flatAttr++;
269 }
270 }
271};
272
273} // namespace
274
275bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
276 BigBuffer nodeBuffer(1024);
277 XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
278 node->accept(&visitor);
279
280 // Merge the package pools into the main pool.
281 for (auto& packagePoolEntry : visitor.mPackagePools) {
282 visitor.mPool.merge(std::move(packagePoolEntry.second));
283 }
284
285 // Sort the string pool so that attribute resource IDs show up first.
286 visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
287 return a.context.priority < b.context.priority;
288 });
289
290 // Now we flatten the string pool references into the correct places.
291 for (const auto& refEntry : visitor.mStringRefs) {
292 refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
293 }
294
295 // Write the XML header.
296 ChunkWriter xmlHeaderWriter(mBuffer);
297 xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
298
299 // Flatten the StringPool.
Alexandria Cornwalle0af9252016-07-22 16:25:02 -0700300 StringPool::flattenUtf8(mBuffer, visitor.mPool);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700301
302 {
303 // Write the array of resource IDs, indexed by StringPool order.
304 ChunkWriter resIdMapWriter(mBuffer);
305 resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
306 for (const auto& str : visitor.mPool) {
307 ResourceId id = { str->context.priority };
308 if (id.id == kLowPriority || !id.isValid()) {
309 // When we see the first non-resource ID,
310 // we're done.
311 break;
312 }
313
314 *resIdMapWriter.nextBlock<uint32_t>() = id.id;
315 }
316 resIdMapWriter.finish();
317 }
318
319 // Move the nodeBuffer and append it to the out buffer.
320 mBuffer->appendBuffer(std::move(nodeBuffer));
321
322 // Finish the xml header.
323 xmlHeaderWriter.finish();
324 return true;
325}
326
Adam Lesinski467f1712015-11-16 17:35:44 -0800327bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700328 if (!resource->root) {
329 return false;
330 }
331 return flatten(context, resource->root.get());
332}
333
334} // namespace aapt