blob: 570cd9635de3d0868b364430757307caeb95f061 [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 Lesinski1ab598f2015-08-14 14:26:04 -070025#include <utils/misc.h>
Adam Lesinski467f1712015-11-16 17:35:44 -080026#include <vector>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070027
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) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800148 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();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700151 }
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) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800171 if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
172 size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700173 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.
Adam Lesinski64587af2016-02-18 18:33:06 -0800195 if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
196 xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700197 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
Adam Lesinski64587af2016-02-18 18:33:06 -0800212 if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700213 // 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
Adam Lesinski64587af2016-02-18 18:33:06 -0800225 StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
226 xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700227
228 // Add it to the list of strings to flatten.
229 addString(nameRef, &flatAttr->name);
Adam Lesinski64587af2016-02-18 18:33:06 -0800230 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700231
Adam Lesinski64587af2016-02-18 18:33:06 -0800232 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);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700236 }
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;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700244 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
Adam Lesinski467f1712015-11-16 17:35:44 -0800308bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700309 if (!resource->root) {
310 return false;
311 }
312 return flatten(context, resource->root.get());
313}
314
315} // namespace aapt