blob: 2cf7ddaa2cc69b8e7ecc75df2374870ac8399d1b [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 Lesinskie1fda9a2016-07-01 14:51:51 -070060 void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest,
61 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 {
91 writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
92 xml::Visitor::visit(node);
93 writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
94 }
95
96 void visit(xml::Text* node) override {
97 if (util::trimWhitespace(node->text).empty()) {
98 // Skip whitespace only text nodes.
99 return;
100 }
101
102 ChunkWriter writer(mBuffer);
103 ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
104 flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
105 flatNode->comment.index = util::hostToDevice32(-1);
106
107 ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
108 addString(node->text, kLowPriority, &flatText->data);
109
110 writer.finish();
111 }
112
113 void visit(xml::Element* node) override {
114 {
115 ChunkWriter startWriter(mBuffer);
116 ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
117 RES_XML_START_ELEMENT_TYPE);
118 flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
119 flatNode->comment.index = util::hostToDevice32(-1);
120
121 ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
Adam Lesinskie1fda9a2016-07-01 14:51:51 -0700122
123 // A missing namespace must be null, not an empty string. Otherwise the runtime
124 // complains.
125 addString(node->namespaceUri, kLowPriority, &flatElem->ns,
126 true /* treatEmptyStringAsNull */);
127 addString(node->name, kLowPriority, &flatElem->name,
128 true /* treatEmptyStringAsNull */);
129
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700130 flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
131 flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
132
133 writeAttributes(node, flatElem, &startWriter);
134
135 startWriter.finish();
136 }
137
138 xml::Visitor::visit(node);
139
140 {
141 ChunkWriter endWriter(mBuffer);
142 ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
143 RES_XML_END_ELEMENT_TYPE);
144 flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
145 flatEndNode->comment.index = util::hostToDevice32(-1);
146
147 ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
Adam Lesinskie1fda9a2016-07-01 14:51:51 -0700148 addString(node->namespaceUri, kLowPriority, &flatEndElem->ns,
149 true /* treatEmptyStringAsNull */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700150 addString(node->name, kLowPriority, &flatEndElem->name);
151
152 endWriter.finish();
153 }
154 }
155
156 static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800157 if (a->compiledAttribute && a->compiledAttribute.value().id) {
158 if (b->compiledAttribute && b->compiledAttribute.value().id) {
159 return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700160 }
161 return true;
162 } else if (!b->compiledAttribute) {
163 int diff = a->namespaceUri.compare(b->namespaceUri);
164 if (diff < 0) {
165 return true;
166 } else if (diff > 0) {
167 return false;
168 }
169 return a->name < b->name;
170 }
171 return false;
172 }
173
174 void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
175 mFilteredAttrs.clear();
176 mFilteredAttrs.reserve(node->attributes.size());
177
178 // Filter the attributes.
179 for (xml::Attribute& attr : node->attributes) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800180 if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
181 size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700182 if (sdkLevel > mOptions.maxSdkLevel.value()) {
183 continue;
184 }
185 }
186 mFilteredAttrs.push_back(&attr);
187 }
188
189 if (mFilteredAttrs.empty()) {
190 return;
191 }
192
193 const ResourceId kIdAttr(0x010100d0);
194
195 std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
196
197 flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
198
199 ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
200 mFilteredAttrs.size());
201 uint16_t attributeIndex = 1;
202 for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
203 // Assign the indices for specific attributes.
Adam Lesinski64587af2016-02-18 18:33:06 -0800204 if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
205 xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700206 flatElem->idIndex = util::hostToDevice16(attributeIndex);
207 } else if (xmlAttr->namespaceUri.empty()) {
208 if (xmlAttr->name == u"class") {
209 flatElem->classIndex = util::hostToDevice16(attributeIndex);
210 } else if (xmlAttr->name == u"style") {
211 flatElem->styleIndex = util::hostToDevice16(attributeIndex);
212 }
213 }
214 attributeIndex++;
215
Adam Lesinskie1fda9a2016-07-01 14:51:51 -0700216 // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
217 // is empty (doesn't exist).
218 addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns,
219 true /* treatEmptyStringAsNull */);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700220
221 flatAttr->rawValue.index = util::hostToDevice32(-1);
222
Adam Lesinski64587af2016-02-18 18:33:06 -0800223 if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700224 // The attribute has no associated ResourceID, so the string order doesn't matter.
225 addString(xmlAttr->name, kLowPriority, &flatAttr->name);
226 } else {
227 // Attribute names are stored without packages, but we use
228 // their StringPool index to lookup their resource IDs.
229 // This will cause collisions, so we can't dedupe
230 // attribute names from different packages. We use separate
231 // pools that we later combine.
232 //
233 // Lookup the StringPool for this package and make the reference there.
234 const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
235
Adam Lesinski64587af2016-02-18 18:33:06 -0800236 StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
237 xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700238
239 // Add it to the list of strings to flatten.
240 addString(nameRef, &flatAttr->name);
Adam Lesinski64587af2016-02-18 18:33:06 -0800241 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700242
Adam Lesinski64587af2016-02-18 18:33:06 -0800243 if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
244 // Keep raw values if the value is not compiled or
245 // if we're building a static library (need symbols).
246 addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700247 }
248
249 if (xmlAttr->compiledValue) {
250 bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
251 assert(result);
252 } else {
253 // Flatten as a regular string type.
254 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700255 addString(xmlAttr->value, kLowPriority,
256 (ResStringPool_ref*) &flatAttr->typedValue.data);
257 }
258
259 flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
260 flatAttr++;
261 }
262 }
263};
264
265} // namespace
266
267bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
268 BigBuffer nodeBuffer(1024);
269 XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
270 node->accept(&visitor);
271
272 // Merge the package pools into the main pool.
273 for (auto& packagePoolEntry : visitor.mPackagePools) {
274 visitor.mPool.merge(std::move(packagePoolEntry.second));
275 }
276
277 // Sort the string pool so that attribute resource IDs show up first.
278 visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
279 return a.context.priority < b.context.priority;
280 });
281
282 // Now we flatten the string pool references into the correct places.
283 for (const auto& refEntry : visitor.mStringRefs) {
284 refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
285 }
286
287 // Write the XML header.
288 ChunkWriter xmlHeaderWriter(mBuffer);
289 xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
290
291 // Flatten the StringPool.
292 StringPool::flattenUtf16(mBuffer, visitor.mPool);
293
294 {
295 // Write the array of resource IDs, indexed by StringPool order.
296 ChunkWriter resIdMapWriter(mBuffer);
297 resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
298 for (const auto& str : visitor.mPool) {
299 ResourceId id = { str->context.priority };
300 if (id.id == kLowPriority || !id.isValid()) {
301 // When we see the first non-resource ID,
302 // we're done.
303 break;
304 }
305
306 *resIdMapWriter.nextBlock<uint32_t>() = id.id;
307 }
308 resIdMapWriter.finish();
309 }
310
311 // Move the nodeBuffer and append it to the out buffer.
312 mBuffer->appendBuffer(std::move(nodeBuffer));
313
314 // Finish the xml header.
315 xmlHeaderWriter.finish();
316 return true;
317}
318
Adam Lesinski467f1712015-11-16 17:35:44 -0800319bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700320 if (!resource->root) {
321 return false;
322 }
323 return flatten(context, resource->root.get());
324}
325
326} // namespace aapt