blob: c296dde0ca1ec66dabdc1039f967dfcc2b02a092 [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
Adam Lesinskicacb28f2016-10-19 12:18:14 -070017#include "flatten/XmlFlattener.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "SdkConstants.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070019#include "flatten/ChunkWriter.h"
20#include "flatten/ResourceTypeExtensions.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 Lesinskicacb28f2016-10-19 12:18:14 -070024#include <utils/misc.h>
Adam Lesinski803c7c82016-04-06 16:09:43 -070025#include <algorithm>
Adam Lesinski32852a52016-06-15 10:56:41 -070026#include <map>
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 {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070038 using xml::Visitor::visit;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070039
Adam Lesinskicacb28f2016-10-19 12:18:14 -070040 BigBuffer* mBuffer;
41 XmlFlattenerOptions mOptions;
42 StringPool mPool;
43 std::map<uint8_t, StringPool> mPackagePools;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070044
Adam Lesinskicacb28f2016-10-19 12:18:14 -070045 struct StringFlattenDest {
46 StringPool::Ref ref;
47 ResStringPool_ref* dest;
48 };
49 std::vector<StringFlattenDest> mStringRefs;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070050
Adam Lesinskicacb28f2016-10-19 12:18:14 -070051 // Scratch vector to filter attributes. We avoid allocations
52 // making this a member.
53 std::vector<xml::Attribute*> mFilteredAttrs;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054
Adam Lesinskicacb28f2016-10-19 12:18:14 -070055 XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
56 : mBuffer(buffer), mOptions(options) {}
Adam Lesinski1ab598f2015-08-14 14:26:04 -070057
Adam Lesinskicacb28f2016-10-19 12:18:14 -070058 void addString(const StringPiece& str, uint32_t priority,
59 android::ResStringPool_ref* dest,
60 bool treatEmptyStringAsNull = false) {
61 if (str.empty() && treatEmptyStringAsNull) {
62 // Some parts of the runtime treat null differently than empty string.
63 dest->index = util::deviceToHost32(-1);
64 } else {
65 mStringRefs.push_back(StringFlattenDest{
66 mPool.makeRef(str, StringPool::Context{priority}), dest});
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 =
82 writer.nextBlock<ResXMLTree_namespaceExt>();
83 addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
84 addString(node->namespaceUri, kLowPriority, &flatNs->uri);
85
86 writer.finish();
87 }
88
89 void visit(xml::Namespace* node) override {
90 if (node->namespaceUri == xml::kSchemaTools) {
91 // Skip dedicated tools namespace.
92 xml::Visitor::visit(node);
93 } else {
94 writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
95 xml::Visitor::visit(node);
96 writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
97 }
98 }
99
100 void visit(xml::Text* node) override {
101 if (util::trimWhitespace(node->text).empty()) {
102 // Skip whitespace only text nodes.
103 return;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700104 }
105
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700106 ChunkWriter writer(mBuffer);
107 ResXMLTree_node* flatNode =
108 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 =
122 startWriter.startChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
123 flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
124 flatNode->comment.index = util::hostToDevice32(-1);
125
126 ResXMLTree_attrExt* flatElem =
127 startWriter.nextBlock<ResXMLTree_attrExt>();
128
129 // A missing namespace must be null, not an empty string. Otherwise the
130 // runtime
131 // complains.
132 addString(node->namespaceUri, kLowPriority, &flatElem->ns,
133 true /* treatEmptyStringAsNull */);
134 addString(node->name, kLowPriority, &flatElem->name,
135 true /* treatEmptyStringAsNull */);
136
137 flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
138 flatElem->attributeSize =
139 util::hostToDevice16(sizeof(ResXMLTree_attribute));
140
141 writeAttributes(node, flatElem, &startWriter);
142
143 startWriter.finish();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700144 }
145
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700146 xml::Visitor::visit(node);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700147
148 {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700149 ChunkWriter endWriter(mBuffer);
150 ResXMLTree_node* flatEndNode =
151 endWriter.startChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
152 flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
153 flatEndNode->comment.index = util::hostToDevice32(-1);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700154
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700155 ResXMLTree_endElementExt* flatEndElem =
156 endWriter.nextBlock<ResXMLTree_endElementExt>();
157 addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
158 true /* treatEmptyStringAsNull */);
159 addString(node->name, kLowPriority, &flatEndElem->name);
160
161 endWriter.finish();
162 }
163 }
164
165 static bool cmpXmlAttributeById(const xml::Attribute* a,
166 const xml::Attribute* b) {
167 if (a->compiledAttribute && a->compiledAttribute.value().id) {
168 if (b->compiledAttribute && b->compiledAttribute.value().id) {
169 return a->compiledAttribute.value().id.value() <
170 b->compiledAttribute.value().id.value();
171 }
172 return true;
173 } else if (!b->compiledAttribute) {
174 int diff = a->namespaceUri.compare(b->namespaceUri);
175 if (diff < 0) {
176 return true;
177 } else if (diff > 0) {
178 return false;
179 }
180 return a->name < b->name;
181 }
182 return false;
183 }
184
185 void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem,
186 ChunkWriter* writer) {
187 mFilteredAttrs.clear();
188 mFilteredAttrs.reserve(node->attributes.size());
189
190 // Filter the attributes.
191 for (xml::Attribute& attr : node->attributes) {
192 if (mOptions.maxSdkLevel && attr.compiledAttribute &&
193 attr.compiledAttribute.value().id) {
194 size_t sdkLevel =
195 findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
196 if (sdkLevel > mOptions.maxSdkLevel.value()) {
197 continue;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700198 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700199 }
200 if (attr.namespaceUri == xml::kSchemaTools) {
201 continue;
202 }
203 mFilteredAttrs.push_back(&attr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700204 }
205
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700206 if (mFilteredAttrs.empty()) {
207 return;
208 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700209
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700210 const ResourceId kIdAttr(0x010100d0);
211
212 std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(),
213 cmpXmlAttributeById);
214
215 flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
216
217 ResXMLTree_attribute* flatAttr =
218 writer->nextBlock<ResXMLTree_attribute>(mFilteredAttrs.size());
219 uint16_t attributeIndex = 1;
220 for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
221 // Assign the indices for specific attributes.
222 if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
223 xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
224 flatElem->idIndex = util::hostToDevice16(attributeIndex);
225 } else if (xmlAttr->namespaceUri.empty()) {
226 if (xmlAttr->name == "class") {
227 flatElem->classIndex = util::hostToDevice16(attributeIndex);
228 } else if (xmlAttr->name == "style") {
229 flatElem->styleIndex = util::hostToDevice16(attributeIndex);
230 }
231 }
232 attributeIndex++;
233
234 // Add the namespaceUri to the list of StringRefs to encode. Use null if
235 // the namespace
236 // is empty (doesn't exist).
237 addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
238 true /* treatEmptyStringAsNull */);
239
240 flatAttr->rawValue.index = util::hostToDevice32(-1);
241
242 if (!xmlAttr->compiledAttribute ||
243 !xmlAttr->compiledAttribute.value().id) {
244 // The attribute has no associated ResourceID, so the string order
245 // doesn't matter.
246 addString(xmlAttr->name, kLowPriority, &flatAttr->name);
247 } else {
248 // Attribute names are stored without packages, but we use
249 // their StringPool index to lookup their resource IDs.
250 // This will cause collisions, so we can't dedupe
251 // attribute names from different packages. We use separate
252 // pools that we later combine.
253 //
254 // Lookup the StringPool for this package and make the reference there.
255 const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
256
257 StringPool::Ref nameRef =
258 mPackagePools[aaptAttr.id.value().packageId()].makeRef(
259 xmlAttr->name, StringPool::Context{aaptAttr.id.value().id});
260
261 // Add it to the list of strings to flatten.
262 addString(nameRef, &flatAttr->name);
263 }
264
265 if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
266 // Keep raw values if the value is not compiled or
267 // if we're building a static library (need symbols).
268 addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
269 }
270
271 if (xmlAttr->compiledValue) {
272 bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
273 assert(result);
274 } else {
275 // Flatten as a regular string type.
276 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
277 addString(xmlAttr->value, kLowPriority,
278 (ResStringPool_ref*)&flatAttr->typedValue.data);
279 }
280
281 flatAttr->typedValue.size =
282 util::hostToDevice16(sizeof(flatAttr->typedValue));
283 flatAttr++;
284 }
285 }
286};
287
288} // namespace
289
290bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
291 BigBuffer nodeBuffer(1024);
292 XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
293 node->accept(&visitor);
294
295 // Merge the package pools into the main pool.
296 for (auto& packagePoolEntry : visitor.mPackagePools) {
297 visitor.mPool.merge(std::move(packagePoolEntry.second));
298 }
299
300 // Sort the string pool so that attribute resource IDs show up first.
301 visitor.mPool.sort(
302 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
303 return a.context.priority < b.context.priority;
304 });
305
306 // Now we flatten the string pool references into the correct places.
307 for (const auto& refEntry : visitor.mStringRefs) {
308 refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
309 }
310
311 // Write the XML header.
312 ChunkWriter xmlHeaderWriter(mBuffer);
313 xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
314
315 // Flatten the StringPool.
316 StringPool::flattenUtf8(mBuffer, visitor.mPool);
317
318 {
319 // Write the array of resource IDs, indexed by StringPool order.
320 ChunkWriter resIdMapWriter(mBuffer);
321 resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
322 for (const auto& str : visitor.mPool) {
323 ResourceId id = {str->context.priority};
324 if (id.id == kLowPriority || !id.isValid()) {
325 // When we see the first non-resource ID,
326 // we're done.
327 break;
328 }
329
330 *resIdMapWriter.nextBlock<uint32_t>() = id.id;
331 }
332 resIdMapWriter.finish();
333 }
334
335 // Move the nodeBuffer and append it to the out buffer.
336 mBuffer->appendBuffer(std::move(nodeBuffer));
337
338 // Finish the xml header.
339 xmlHeaderWriter.finish();
340 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700341}
342
Adam Lesinski467f1712015-11-16 17:35:44 -0800343bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700344 if (!resource->root) {
345 return false;
346 }
347 return flatten(context, resource->root.get());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700348}
349
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700350} // namespace aapt