blob: 67c56e70c8506f9b695548b959bf812991d458cc [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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 "BigBuffer.h"
18#include "ConfigDescription.h"
19#include "Logger.h"
20#include "ResourceTable.h"
21#include "ResourceTypeExtensions.h"
22#include "ResourceValues.h"
23#include "StringPool.h"
24#include "TableFlattener.h"
25#include "Util.h"
26
Adam Lesinskica2fc352015-04-03 12:08:26 -070027#include <algorithm>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080028#include <androidfw/ResourceTypes.h>
29#include <sstream>
30
31namespace aapt {
32
33struct FlatEntry {
34 const ResourceEntry& entry;
35 const Value& value;
36 uint32_t entryKey;
37 uint32_t sourcePathKey;
38 uint32_t sourceLine;
39};
40
41/**
42 * Visitor that knows how to encode Map values.
43 */
44class MapFlattener : public ConstValueVisitor {
45public:
46 MapFlattener(BigBuffer* out, const FlatEntry& flatEntry,
47 std::vector<std::pair<ResourceNameRef, uint32_t>>& symbols) :
48 mOut(out), mSymbols(symbols) {
49 mMap = mOut->nextBlock<android::ResTable_map_entry>();
50 mMap->key.index = flatEntry.entryKey;
51 mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
52 if (flatEntry.entry.publicStatus.isPublic) {
53 mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
54 }
55 if (flatEntry.value.isWeak()) {
56 mMap->flags |= android::ResTable_entry::FLAG_WEAK;
57 }
58
59 ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
60 sourceBlock->pathIndex = flatEntry.sourcePathKey;
61 sourceBlock->line = flatEntry.sourceLine;
62
63 mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
64 }
65
66 void flattenParent(const Reference& ref) {
67 if (!ref.id.isValid()) {
68 mSymbols.push_back({
69 ResourceNameRef(ref.name),
70 (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
71 });
72 }
73 mMap->parent.ident = ref.id.id;
74 }
75
76 void flattenEntry(const Reference& key, const Item& value) {
77 mMap->count++;
78
79 android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
80
81 // Write the key.
82 if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
83 mSymbols.push_back(std::make_pair(ResourceNameRef(key.name),
84 mOut->size() - sizeof(*outMapEntry)));
85 }
86 outMapEntry->name.ident = key.id.id;
87
88 // Write the value.
89 value.flatten(outMapEntry->value);
90
91 if (outMapEntry->value.data == 0x0) {
92 visitFunc<Reference>(value, [&](const Reference& reference) {
93 mSymbols.push_back(std::make_pair(ResourceNameRef(reference.name),
94 mOut->size() - sizeof(outMapEntry->value.data)));
95 });
96 }
97 outMapEntry->value.size = sizeof(outMapEntry->value);
98 }
99
100 static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
101 return lhs->key.id < rhs->key.id;
102 }
103
104 void visit(const Style& style, ValueVisitorArgs&) override {
105 if (style.parent.name.isValid()) {
106 flattenParent(style.parent);
107 }
108
109 // First sort the entries by ID.
110 std::vector<const Style::Entry*> sortedEntries;
111 for (const auto& styleEntry : style.entries) {
112 auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
113 &styleEntry, compareStyleEntries);
114 sortedEntries.insert(iter, &styleEntry);
115 }
116
117 for (const Style::Entry* styleEntry : sortedEntries) {
118 flattenEntry(styleEntry->key, *styleEntry->value);
119 }
120 }
121
122 void visit(const Attribute& attr, ValueVisitorArgs&) override {
123 android::Res_value tempVal;
124 tempVal.dataType = android::Res_value::TYPE_INT_DEC;
125 tempVal.data = attr.typeMask;
126 flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
127 BinaryPrimitive(tempVal));
128
129 for (const auto& symbol : attr.symbols) {
130 tempVal.data = symbol.value;
131 flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
132 }
133 }
134
135 void visit(const Styleable& styleable, ValueVisitorArgs&) override {
136 for (const auto& attr : styleable.entries) {
137 flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
138 }
139 }
140
141 void visit(const Array& array, ValueVisitorArgs&) override {
142 for (const auto& item : array.items) {
143 flattenEntry({}, *item);
144 }
145 }
146
147 void visit(const Plural& plural, ValueVisitorArgs&) override {
148 const size_t count = plural.values.size();
149 for (size_t i = 0; i < count; i++) {
150 if (!plural.values[i]) {
151 continue;
152 }
153
154 ResourceId q;
155 switch (i) {
156 case Plural::Zero:
157 q.id = android::ResTable_map::ATTR_ZERO;
158 break;
159
160 case Plural::One:
161 q.id = android::ResTable_map::ATTR_ONE;
162 break;
163
164 case Plural::Two:
165 q.id = android::ResTable_map::ATTR_TWO;
166 break;
167
168 case Plural::Few:
169 q.id = android::ResTable_map::ATTR_FEW;
170 break;
171
172 case Plural::Many:
173 q.id = android::ResTable_map::ATTR_MANY;
174 break;
175
176 case Plural::Other:
177 q.id = android::ResTable_map::ATTR_OTHER;
178 break;
179
180 default:
181 assert(false);
182 break;
183 }
184
185 flattenEntry(Reference(q), *plural.values[i]);
186 }
187 }
188
189private:
190 BigBuffer* mOut;
191 std::vector<std::pair<ResourceNameRef, uint32_t>>& mSymbols;
192 android::ResTable_map_entry* mMap;
193};
194
195TableFlattener::TableFlattener(Options options)
196: mOptions(options) {
197}
198
199bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
200 std::vector<std::pair<ResourceNameRef, uint32_t>>& symbolEntries) {
201 if (flatEntry.value.isItem()) {
202 android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
203
204 if (flatEntry.entry.publicStatus.isPublic) {
205 entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
206 }
207
208 if (flatEntry.value.isWeak()) {
209 entry->flags |= android::ResTable_entry::FLAG_WEAK;
210 }
211
212 entry->key.index = flatEntry.entryKey;
213 entry->size = sizeof(*entry);
214
215 if (mOptions.useExtendedChunks) {
216 // Write the extra source block. This will be ignored by
217 // the Android runtime.
218 ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
219 sourceBlock->pathIndex = flatEntry.sourcePathKey;
220 sourceBlock->line = flatEntry.sourceLine;
221
222 entry->size += sizeof(*sourceBlock);
223 }
224
225 android::Res_value* outValue = out->nextBlock<android::Res_value>();
226
227 const Item& item = static_cast<const Item&>(flatEntry.value);
228 if (!item.flatten(*outValue)) {
229 return false;
230 }
231
232 if (outValue->data == 0x0) {
233 visitFunc<Reference>(item, [&](const Reference& reference) {
234 symbolEntries.push_back({
235 ResourceNameRef(reference.name),
236 out->size() - sizeof(outValue->data)
237 });
238 });
239 }
240 outValue->size = sizeof(*outValue);
241 return true;
242 }
243
244 MapFlattener flattener(out, flatEntry, symbolEntries);
245 flatEntry.value.accept(flattener, {});
246 return true;
247}
248
249bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
250 const size_t beginning = out->size();
251
252 if (table.getPackage().size() == 0) {
253 Logger::error()
254 << "ResourceTable has no package name."
255 << std::endl;
256 return false;
257 }
258
259 if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
260 Logger::error()
261 << "ResourceTable has no package ID set."
262 << std::endl;
263 return false;
264 }
265
266 std::vector<std::pair<ResourceNameRef, uint32_t>> symbolEntries;
267
268 StringPool typePool;
269 StringPool keyPool;
270 StringPool sourcePool;
271
272 // Sort the types by their IDs. They will be inserted into the StringPool
273 // in this order.
274 std::vector<ResourceTableType*> sortedTypes;
275 for (const auto& type : table) {
276 if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
277 continue;
278 }
279
280 auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
281 [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
282 return lhs->typeId < rhs->typeId;
283 });
284 sortedTypes.insert(iter, type.get());
285 }
286
287 BigBuffer typeBlock(1024);
288 size_t expectedTypeId = 1;
289 for (const ResourceTableType* type : sortedTypes) {
290 if (type->typeId == ResourceTableType::kUnsetTypeId
291 || type->typeId == 0) {
292 Logger::error()
293 << "resource type '"
294 << type->type
295 << "' from package '"
296 << table.getPackage()
297 << "' has no ID."
298 << std::endl;
299 return false;
300 }
301
302 // If there is a gap in the type IDs, fill in the StringPool
303 // with empty values until we reach the ID we expect.
304 while (type->typeId > expectedTypeId) {
305 std::u16string typeName(u"?");
306 typeName += expectedTypeId;
307 typePool.makeRef(typeName);
308 expectedTypeId++;
309 }
310 expectedTypeId++;
311 typePool.makeRef(toString(type->type));
312
313 android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
314 spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
315 spec->header.headerSize = sizeof(*spec);
316 spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
317 spec->id = type->typeId;
318 spec->entryCount = type->entries.size();
319
320 // Reserve space for the masks of each resource in this type. These
321 // show for which configuration axis the resource changes.
322 uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
323
324 // Sort the entries by entry ID and write their configuration masks.
325 std::vector<ResourceEntry*> entries;
326 const size_t entryCount = type->entries.size();
327 for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
328 const auto& entry = type->entries[entryIndex];
329
330 if (entry->entryId == ResourceEntry::kUnsetEntryId) {
331 Logger::error()
332 << "resource '"
333 << ResourceName{ table.getPackage(), type->type, entry->name }
334 << "' has no ID."
335 << std::endl;
336 return false;
337 }
338
339 auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
340 [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
341 return lhs->entryId < rhs->entryId;
342 });
343 entries.insert(iter, entry.get());
344
345 // Populate the config masks for this entry.
346 if (entry->publicStatus.isPublic) {
347 configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
348 }
349
350 const size_t configCount = entry->values.size();
351 for (size_t i = 0; i < configCount; i++) {
352 const ConfigDescription& config = entry->values[i].config;
353 for (size_t j = i + 1; j < configCount; j++) {
354 configMasks[entry->entryId] |= config.diff(entry->values[j].config);
355 }
356 }
357 }
358
359 // The binary resource table lists resource entries for each configuration.
360 // We store them inverted, where a resource entry lists the values for each
361 // configuration available. Here we reverse this to match the binary table.
362 std::map<ConfigDescription, std::vector<FlatEntry>> data;
363 for (const ResourceEntry* entry : entries) {
364 size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
365
366 if (keyIndex > std::numeric_limits<uint32_t>::max()) {
367 Logger::error()
368 << "resource key string pool exceeded max size."
369 << std::endl;
370 return false;
371 }
372
373 for (const auto& configValue : entry->values) {
374 data[configValue.config].push_back(FlatEntry{
375 *entry,
376 *configValue.value,
377 static_cast<uint32_t>(keyIndex),
378 static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
379 configValue.source.path)).getIndex()),
380 static_cast<uint32_t>(configValue.source.line)
381 });
382 }
383 }
384
385 // Begin flattening a configuration for the current type.
386 for (const auto& entry : data) {
387 const size_t typeHeaderStart = typeBlock.size();
388 android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
389 typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
390 typeHeader->header.headerSize = sizeof(*typeHeader);
391 typeHeader->id = type->typeId;
392 typeHeader->entryCount = type->entries.size();
393 typeHeader->entriesStart = typeHeader->header.headerSize
394 + (sizeof(uint32_t) * type->entries.size());
395 typeHeader->config = entry.first;
396
397 uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
398 memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
399
400 const size_t entryStart = typeBlock.size();
401 for (const FlatEntry& flatEntry : entry.second) {
402 assert(flatEntry.entry.entryId < type->entries.size());
403 indices[flatEntry.entry.entryId] = typeBlock.size() - entryStart;
404 if (!flattenValue(&typeBlock, flatEntry, symbolEntries)) {
405 Logger::error()
406 << "failed to flatten resource '"
407 << ResourceNameRef {
408 table.getPackage(), type->type, flatEntry.entry.name }
409 << "' for configuration '"
410 << entry.first
411 << "'."
412 << std::endl;
413 return false;
414 }
415 }
416
417 typeBlock.align4();
418 typeHeader->header.size = typeBlock.size() - typeHeaderStart;
419 }
420 }
421
422 const size_t beforeTable = out->size();
423 android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
424 header->header.type = android::RES_TABLE_TYPE;
425 header->header.headerSize = sizeof(*header);
426 header->packageCount = 1;
427
428 SymbolTable_entry* symbolEntryData = nullptr;
429 if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
430 const size_t beforeSymbolTable = out->size();
431 StringPool symbolPool;
432 SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
433 symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
434 symbolHeader->header.headerSize = sizeof(*symbolHeader);
435 symbolHeader->count = symbolEntries.size();
436
437 symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
438
439 size_t i = 0;
440 for (const auto& entry : symbolEntries) {
441 symbolEntryData[i].offset = entry.second;
442 StringPool::Ref ref = symbolPool.makeRef(
443 entry.first.package.toString() + u":" +
444 toString(entry.first.type).toString() + u"/" +
445 entry.first.entry.toString());
446 symbolEntryData[i].stringIndex = ref.getIndex();
447 i++;
448 }
449
450 StringPool::flattenUtf8(out, symbolPool);
451 out->align4();
452 symbolHeader->header.size = out->size() - beforeSymbolTable;
453 }
454
455 if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
456 const size_t beforeSourcePool = out->size();
457 android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
458 sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
459 sourceHeader->headerSize = sizeof(*sourceHeader);
460 StringPool::flattenUtf8(out, sourcePool);
461 out->align4();
462 sourceHeader->size = out->size() - beforeSourcePool;
463 }
464
465 StringPool::flattenUtf8(out, table.getValueStringPool());
466
467 const size_t beforePackageIndex = out->size();
468 android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
469 package->header.type = android::RES_TABLE_PACKAGE_TYPE;
470 package->header.headerSize = sizeof(*package);
471
472 if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
473 Logger::error()
474 << "package ID 0x'"
475 << std::hex << table.getPackageId() << std::dec
476 << "' is invalid."
477 << std::endl;
478 return false;
479 }
480 package->id = table.getPackageId();
481
482 if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
483 Logger::error()
484 << "package name '"
485 << table.getPackage()
486 << "' is too long."
487 << std::endl;
488 return false;
489 }
490 memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
491 table.getPackage().length() * sizeof(char16_t));
492 package->name[table.getPackage().length()] = 0;
493
494 package->typeStrings = package->header.headerSize;
495 StringPool::flattenUtf8(out, typePool);
496 package->keyStrings = out->size() - beforePackageIndex;
497 StringPool::flattenUtf8(out, keyPool);
498
499 if (symbolEntryData != nullptr) {
500 for (size_t i = 0; i < symbolEntries.size(); i++) {
501 symbolEntryData[i].offset += out->size() - beginning;
502 }
503 }
504
505 out->appendBuffer(std::move(typeBlock));
506
507 package->header.size = out->size() - beforePackageIndex;
508 header->header.size = out->size() - beforeTable;
509 return true;
510}
511
512} // namespace aapt