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