blob: d02f44edaa4cccd5a041db46e71ac6c9eb04e0db [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
6
7#include "ResourceTable.h"
8
Adam Lesinskide7de472014-11-03 12:03:08 -08009#include "AaptUtil.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080010#include "XMLNode.h"
11#include "ResourceFilter.h"
12#include "ResourceIdCache.h"
Adam Lesinskidcdfe9f2014-11-06 12:54:36 -080013#include "SdkConstants.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080014
Adam Lesinski9b624c12014-11-19 17:49:26 -080015#include <algorithm>
Adam Lesinski282e1812014-01-23 18:17:42 -080016#include <androidfw/ResourceTypes.h>
17#include <utils/ByteOrder.h>
Adam Lesinski82a2dd82014-09-17 18:34:15 -070018#include <utils/TypeHelpers.h>
Adam Lesinski282e1812014-01-23 18:17:42 -080019#include <stdarg.h>
20
Andreas Gampe2412f842014-09-30 20:55:57 -070021// STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary.
Elliott Hughesb12f2412015-04-03 12:56:45 -070022#if !defined(_WIN32)
Andreas Gampe2412f842014-09-30 20:55:57 -070023# define STATUST(x) x
24#else
Andreas Gampe2412f842014-09-30 20:55:57 -070025# define STATUST(x) (status_t)x
26#endif
27
28// Set to true for noisy debug output.
29static const bool kIsDebug = false;
30
31#if PRINT_STRING_METRICS
32static const bool kPrintStringMetrics = true;
33#else
34static const bool kPrintStringMetrics = false;
35#endif
Adam Lesinski282e1812014-01-23 18:17:42 -080036
Adam Lesinski9b624c12014-11-19 17:49:26 -080037static const char* kAttrPrivateType = "^attr-private";
38
Adam Lesinskie572c012014-09-19 15:10:04 -070039status_t compileXmlFile(const Bundle* bundle,
40 const sp<AaptAssets>& assets,
41 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080042 const sp<AaptFile>& target,
43 ResourceTable* table,
44 int options)
45{
46 sp<XMLNode> root = XMLNode::parse(target);
47 if (root == NULL) {
48 return UNKNOWN_ERROR;
49 }
Anton Krumina2ef5c02014-03-12 14:46:44 -070050
Adam Lesinskie572c012014-09-19 15:10:04 -070051 return compileXmlFile(bundle, assets, resourceName, root, target, table, options);
Adam Lesinski282e1812014-01-23 18:17:42 -080052}
53
Adam Lesinskie572c012014-09-19 15:10:04 -070054status_t compileXmlFile(const Bundle* bundle,
55 const sp<AaptAssets>& assets,
56 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080057 const sp<AaptFile>& target,
58 const sp<AaptFile>& outTarget,
59 ResourceTable* table,
60 int options)
61{
62 sp<XMLNode> root = XMLNode::parse(target);
63 if (root == NULL) {
64 return UNKNOWN_ERROR;
65 }
66
Adam Lesinskie572c012014-09-19 15:10:04 -070067 return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options);
Adam Lesinski282e1812014-01-23 18:17:42 -080068}
69
Adam Lesinskie572c012014-09-19 15:10:04 -070070status_t compileXmlFile(const Bundle* bundle,
71 const sp<AaptAssets>& assets,
72 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080073 const sp<XMLNode>& root,
74 const sp<AaptFile>& target,
75 ResourceTable* table,
76 int options)
77{
Adam Lesinskicf1f1d92017-03-16 16:54:23 -070078 if (table->versionForCompat(bundle, resourceName, target, root)) {
79 // The file was versioned, so stop processing here.
80 // The resource entry has already been removed and the new one added.
81 // Remove the assets entry.
82 sp<AaptDir> resDir = assets->getDirs().valueFor(String8("res"));
83 sp<AaptDir> dir = resDir->getDirs().valueFor(target->getGroupEntry().toDirName(
84 target->getResourceType()));
85 dir->removeFile(target->getPath().getPathLeaf());
86 return NO_ERROR;
87 }
88
Adam Lesinski282e1812014-01-23 18:17:42 -080089 if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
90 root->removeWhitespace(true, NULL);
91 } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
92 root->removeWhitespace(false, NULL);
93 }
94
95 if ((options&XML_COMPILE_UTF8) != 0) {
96 root->setUTF8(true);
97 }
98
Adam Lesinski07dfd2d2015-10-28 15:44:27 -070099 if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
100 return UNKNOWN_ERROR;
101 }
Adam Lesinski5b9847c2015-11-30 21:07:44 +0000102
Adam Lesinski07dfd2d2015-10-28 15:44:27 -0700103 bool hasErrors = false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800104 if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
105 status_t err = root->assignResourceIds(assets, table);
106 if (err != NO_ERROR) {
107 hasErrors = true;
108 }
109 }
110
Adam Lesinski07dfd2d2015-10-28 15:44:27 -0700111 if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
112 status_t err = root->parseValues(assets, table);
113 if (err != NO_ERROR) {
114 hasErrors = true;
115 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800116 }
117
118 if (hasErrors) {
119 return UNKNOWN_ERROR;
120 }
Adam Lesinskie572c012014-09-19 15:10:04 -0700121
122 if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) {
123 return UNKNOWN_ERROR;
124 }
Andreas Gampe87332a72014-10-01 22:03:58 -0700125
Andreas Gampe2412f842014-09-30 20:55:57 -0700126 if (kIsDebug) {
127 printf("Input XML Resource:\n");
128 root->print();
129 }
Adam Lesinski07dfd2d2015-10-28 15:44:27 -0700130 status_t err = root->flatten(target,
Adam Lesinski282e1812014-01-23 18:17:42 -0800131 (options&XML_COMPILE_STRIP_COMMENTS) != 0,
132 (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
133 if (err != NO_ERROR) {
134 return err;
135 }
136
Andreas Gampe2412f842014-09-30 20:55:57 -0700137 if (kIsDebug) {
138 printf("Output XML Resource:\n");
139 ResXMLTree tree;
Adam Lesinski282e1812014-01-23 18:17:42 -0800140 tree.setTo(target->getData(), target->getSize());
Andreas Gampe2412f842014-09-30 20:55:57 -0700141 printXMLBlock(&tree);
142 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800143
144 target->setCompressionMethod(ZipEntry::kCompressDeflated);
145
146 return err;
147}
148
Adam Lesinski282e1812014-01-23 18:17:42 -0800149struct flag_entry
150{
151 const char16_t* name;
152 size_t nameLen;
153 uint32_t value;
154 const char* description;
155};
156
157static const char16_t referenceArray[] =
158 { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' };
159static const char16_t stringArray[] =
160 { 's', 't', 'r', 'i', 'n', 'g' };
161static const char16_t integerArray[] =
162 { 'i', 'n', 't', 'e', 'g', 'e', 'r' };
163static const char16_t booleanArray[] =
164 { 'b', 'o', 'o', 'l', 'e', 'a', 'n' };
165static const char16_t colorArray[] =
166 { 'c', 'o', 'l', 'o', 'r' };
167static const char16_t floatArray[] =
168 { 'f', 'l', 'o', 'a', 't' };
169static const char16_t dimensionArray[] =
170 { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' };
171static const char16_t fractionArray[] =
172 { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' };
173static const char16_t enumArray[] =
174 { 'e', 'n', 'u', 'm' };
175static const char16_t flagsArray[] =
176 { 'f', 'l', 'a', 'g', 's' };
177
178static const flag_entry gFormatFlags[] = {
179 { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
180 "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
181 "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
182 { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
183 "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
184 { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
185 "an integer value, such as \"<code>100</code>\"." },
186 { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
187 "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
188 { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
189 "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
190 "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
191 { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
192 "a floating point value, such as \"<code>1.2</code>\"."},
193 { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
194 "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
195 "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
196 "in (inches), mm (millimeters)." },
197 { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
198 "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
199 "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
200 "some parent container." },
201 { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
202 { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
203 { NULL, 0, 0, NULL }
204};
205
206static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' };
207
208static const flag_entry l10nRequiredFlags[] = {
209 { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL },
210 { NULL, 0, 0, NULL }
211};
212
213static const char16_t nulStr[] = { 0 };
214
215static uint32_t parse_flags(const char16_t* str, size_t len,
216 const flag_entry* flags, bool* outError = NULL)
217{
218 while (len > 0 && isspace(*str)) {
219 str++;
220 len--;
221 }
222 while (len > 0 && isspace(str[len-1])) {
223 len--;
224 }
225
226 const char16_t* const end = str + len;
227 uint32_t value = 0;
228
229 while (str < end) {
230 const char16_t* div = str;
231 while (div < end && *div != '|') {
232 div++;
233 }
234
235 const flag_entry* cur = flags;
236 while (cur->name) {
237 if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
238 value |= cur->value;
239 break;
240 }
241 cur++;
242 }
243
244 if (!cur->name) {
245 if (outError) *outError = true;
246 return 0;
247 }
248
249 str = div < end ? div+1 : div;
250 }
251
252 if (outError) *outError = false;
253 return value;
254}
255
256static String16 mayOrMust(int type, int flags)
257{
258 if ((type&(~flags)) == 0) {
259 return String16("<p>Must");
260 }
261
262 return String16("<p>May");
263}
264
265static void appendTypeInfo(ResourceTable* outTable, const String16& pkg,
266 const String16& typeName, const String16& ident, int type,
267 const flag_entry* flags)
268{
269 bool hadType = false;
270 while (flags->name) {
271 if ((type&flags->value) != 0 && flags->description != NULL) {
272 String16 fullMsg(mayOrMust(type, flags->value));
273 fullMsg.append(String16(" be "));
274 fullMsg.append(String16(flags->description));
275 outTable->appendTypeComment(pkg, typeName, ident, fullMsg);
276 hadType = true;
277 }
278 flags++;
279 }
280 if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) {
281 outTable->appendTypeComment(pkg, typeName, ident,
282 String16("<p>This may also be a reference to a resource (in the form\n"
283 "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
284 "theme attribute (in the form\n"
285 "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
286 "containing a value of this type."));
287 }
288}
289
290struct PendingAttribute
291{
292 const String16 myPackage;
293 const SourcePos sourcePos;
294 const bool appendComment;
295 int32_t type;
296 String16 ident;
297 String16 comment;
298 bool hasErrors;
299 bool added;
300
301 PendingAttribute(String16 _package, const sp<AaptFile>& in,
302 ResXMLTree& block, bool _appendComment)
303 : myPackage(_package)
304 , sourcePos(in->getPrintableSource(), block.getLineNumber())
305 , appendComment(_appendComment)
306 , type(ResTable_map::TYPE_ANY)
307 , hasErrors(false)
308 , added(false)
309 {
310 }
311
312 status_t createIfNeeded(ResourceTable* outTable)
313 {
314 if (added || hasErrors) {
315 return NO_ERROR;
316 }
317 added = true;
318
Adam Lesinskiafc79be2016-02-22 09:16:33 -0800319 if (!outTable->makeAttribute(myPackage, ident, sourcePos, type, comment, appendComment)) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800320 hasErrors = true;
321 return UNKNOWN_ERROR;
322 }
Adam Lesinskiafc79be2016-02-22 09:16:33 -0800323 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -0800324 }
325};
326
327static status_t compileAttribute(const sp<AaptFile>& in,
328 ResXMLTree& block,
329 const String16& myPackage,
330 ResourceTable* outTable,
331 String16* outIdent = NULL,
332 bool inStyleable = false)
333{
334 PendingAttribute attr(myPackage, in, block, inStyleable);
335
336 const String16 attr16("attr");
337 const String16 id16("id");
338
339 // Attribute type constants.
340 const String16 enum16("enum");
341 const String16 flag16("flag");
342
343 ResXMLTree::event_code_t code;
344 size_t len;
345 status_t err;
346
347 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
348 if (identIdx >= 0) {
349 attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
350 if (outIdent) {
351 *outIdent = attr.ident;
352 }
353 } else {
354 attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
355 attr.hasErrors = true;
356 }
357
358 attr.comment = String16(
359 block.getComment(&len) ? block.getComment(&len) : nulStr);
360
361 ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
362 if (typeIdx >= 0) {
363 String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len));
364 attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);
365 if (attr.type == 0) {
366 attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
367 String8(typeStr).string());
368 attr.hasErrors = true;
369 }
370 attr.createIfNeeded(outTable);
371 } else if (!inStyleable) {
372 // Attribute definitions outside of styleables always define the
373 // attribute as a generic value.
374 attr.createIfNeeded(outTable);
375 }
376
377 //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
378
379 ssize_t minIdx = block.indexOfAttribute(NULL, "min");
380 if (minIdx >= 0) {
381 String16 val = String16(block.getAttributeStringValue(minIdx, &len));
382 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
383 attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
384 String8(val).string());
385 attr.hasErrors = true;
386 }
387 attr.createIfNeeded(outTable);
388 if (!attr.hasErrors) {
389 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
390 String16(""), String16("^min"), String16(val), NULL, NULL);
391 if (err != NO_ERROR) {
392 attr.hasErrors = true;
393 }
394 }
395 }
396
397 ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
398 if (maxIdx >= 0) {
399 String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
400 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
401 attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
402 String8(val).string());
403 attr.hasErrors = true;
404 }
405 attr.createIfNeeded(outTable);
406 if (!attr.hasErrors) {
407 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
408 String16(""), String16("^max"), String16(val), NULL, NULL);
409 attr.hasErrors = true;
410 }
411 }
412
413 if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
414 attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
415 attr.hasErrors = true;
416 }
417
418 ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
419 if (l10nIdx >= 0) {
Dan Albertf348c152014-09-08 18:28:00 -0700420 const char16_t* str = block.getAttributeStringValue(l10nIdx, &len);
Adam Lesinski282e1812014-01-23 18:17:42 -0800421 bool error;
422 uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
423 if (error) {
424 attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
425 String8(str).string());
426 attr.hasErrors = true;
427 }
428 attr.createIfNeeded(outTable);
429 if (!attr.hasErrors) {
430 char buf[11];
431 sprintf(buf, "%d", l10n_required);
432 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
433 String16(""), String16("^l10n"), String16(buf), NULL, NULL);
434 if (err != NO_ERROR) {
435 attr.hasErrors = true;
436 }
437 }
438 }
439
440 String16 enumOrFlagsComment;
441
442 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
443 if (code == ResXMLTree::START_TAG) {
444 uint32_t localType = 0;
445 if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
446 localType = ResTable_map::TYPE_ENUM;
447 } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
448 localType = ResTable_map::TYPE_FLAGS;
449 } else {
450 SourcePos(in->getPrintableSource(), block.getLineNumber())
451 .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
452 String8(block.getElementName(&len)).string());
453 return UNKNOWN_ERROR;
454 }
455
456 attr.createIfNeeded(outTable);
457
458 if (attr.type == ResTable_map::TYPE_ANY) {
459 // No type was explicitly stated, so supplying enum tags
460 // implicitly creates an enum or flag.
461 attr.type = 0;
462 }
463
464 if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
465 // Wasn't originally specified as an enum, so update its type.
466 attr.type |= localType;
467 if (!attr.hasErrors) {
468 char numberStr[16];
469 sprintf(numberStr, "%d", attr.type);
470 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
471 myPackage, attr16, attr.ident, String16(""),
472 String16("^type"), String16(numberStr), NULL, NULL, true);
473 if (err != NO_ERROR) {
474 attr.hasErrors = true;
475 }
476 }
477 } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
478 if (localType == ResTable_map::TYPE_ENUM) {
479 SourcePos(in->getPrintableSource(), block.getLineNumber())
480 .error("<enum> attribute can not be used inside a flags format\n");
481 attr.hasErrors = true;
482 } else {
483 SourcePos(in->getPrintableSource(), block.getLineNumber())
484 .error("<flag> attribute can not be used inside a enum format\n");
485 attr.hasErrors = true;
486 }
487 }
488
489 String16 itemIdent;
490 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
491 if (itemIdentIdx >= 0) {
492 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
493 } else {
494 SourcePos(in->getPrintableSource(), block.getLineNumber())
495 .error("A 'name' attribute is required for <enum> or <flag>\n");
496 attr.hasErrors = true;
497 }
498
499 String16 value;
500 ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
501 if (valueIdx >= 0) {
502 value = String16(block.getAttributeStringValue(valueIdx, &len));
503 } else {
504 SourcePos(in->getPrintableSource(), block.getLineNumber())
505 .error("A 'value' attribute is required for <enum> or <flag>\n");
506 attr.hasErrors = true;
507 }
508 if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
509 SourcePos(in->getPrintableSource(), block.getLineNumber())
510 .error("Tag <enum> or <flag> 'value' attribute must be a number,"
511 " not \"%s\"\n",
512 String8(value).string());
513 attr.hasErrors = true;
514 }
515
Adam Lesinski282e1812014-01-23 18:17:42 -0800516 if (!attr.hasErrors) {
517 if (enumOrFlagsComment.size() == 0) {
518 enumOrFlagsComment.append(mayOrMust(attr.type,
519 ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
520 enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
521 ? String16(" be one of the following constant values.")
522 : String16(" be one or more (separated by '|') of the following constant values."));
523 enumOrFlagsComment.append(String16("</p>\n<table>\n"
524 "<colgroup align=\"left\" />\n"
525 "<colgroup align=\"left\" />\n"
526 "<colgroup align=\"left\" />\n"
527 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>"));
528 }
529
530 enumOrFlagsComment.append(String16("\n<tr><td><code>"));
531 enumOrFlagsComment.append(itemIdent);
532 enumOrFlagsComment.append(String16("</code></td><td>"));
533 enumOrFlagsComment.append(value);
534 enumOrFlagsComment.append(String16("</td><td>"));
535 if (block.getComment(&len)) {
536 enumOrFlagsComment.append(String16(block.getComment(&len)));
537 }
538 enumOrFlagsComment.append(String16("</td></tr>"));
539
540 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
541 myPackage,
542 attr16, attr.ident, String16(""),
543 itemIdent, value, NULL, NULL, false, true);
544 if (err != NO_ERROR) {
545 attr.hasErrors = true;
546 }
547 }
548 } else if (code == ResXMLTree::END_TAG) {
549 if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
550 break;
551 }
552 if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
553 if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
554 SourcePos(in->getPrintableSource(), block.getLineNumber())
555 .error("Found tag </%s> where </enum> is expected\n",
556 String8(block.getElementName(&len)).string());
557 return UNKNOWN_ERROR;
558 }
559 } else {
560 if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
561 SourcePos(in->getPrintableSource(), block.getLineNumber())
562 .error("Found tag </%s> where </flag> is expected\n",
563 String8(block.getElementName(&len)).string());
564 return UNKNOWN_ERROR;
565 }
566 }
567 }
568 }
569
570 if (!attr.hasErrors && attr.added) {
571 appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
572 }
573
574 if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
575 enumOrFlagsComment.append(String16("\n</table>"));
576 outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
577 }
578
579
580 return NO_ERROR;
581}
582
583bool localeIsDefined(const ResTable_config& config)
584{
585 return config.locale == 0;
586}
587
588status_t parseAndAddBag(Bundle* bundle,
589 const sp<AaptFile>& in,
590 ResXMLTree* block,
591 const ResTable_config& config,
592 const String16& myPackage,
593 const String16& curType,
594 const String16& ident,
595 const String16& parentIdent,
596 const String16& itemIdent,
597 int32_t curFormat,
598 bool isFormatted,
Andreas Gampe2412f842014-09-30 20:55:57 -0700599 const String16& /* product */,
Anton Krumina2ef5c02014-03-12 14:46:44 -0700600 PseudolocalizationMethod pseudolocalize,
Adam Lesinski282e1812014-01-23 18:17:42 -0800601 const bool overwrite,
602 ResourceTable* outTable)
603{
604 status_t err;
605 const String16 item16("item");
Anton Krumina2ef5c02014-03-12 14:46:44 -0700606
Adam Lesinski282e1812014-01-23 18:17:42 -0800607 String16 str;
608 Vector<StringPool::entry_style_span> spans;
609 err = parseStyledString(bundle, in->getPrintableSource().string(),
610 block, item16, &str, &spans, isFormatted,
611 pseudolocalize);
612 if (err != NO_ERROR) {
613 return err;
614 }
Andreas Gampe2412f842014-09-30 20:55:57 -0700615
616 if (kIsDebug) {
617 printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d "
618 " pid=%s, bag=%s, id=%s: %s\n",
619 config.language[0], config.language[1],
620 config.country[0], config.country[1],
621 config.orientation, config.density,
622 String8(parentIdent).string(),
623 String8(ident).string(),
624 String8(itemIdent).string(),
625 String8(str).string());
626 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800627
628 err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
629 myPackage, curType, ident, parentIdent, itemIdent, str,
630 &spans, &config, overwrite, false, curFormat);
631 return err;
632}
633
634/*
635 * Returns true if needle is one of the elements in the comma-separated list
636 * haystack, false otherwise.
637 */
638bool isInProductList(const String16& needle, const String16& haystack) {
639 const char16_t *needle2 = needle.string();
640 const char16_t *haystack2 = haystack.string();
641 size_t needlesize = needle.size();
642
643 while (*haystack2 != '\0') {
644 if (strncmp16(haystack2, needle2, needlesize) == 0) {
645 if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') {
646 return true;
647 }
648 }
649
650 while (*haystack2 != '\0' && *haystack2 != ',') {
651 haystack2++;
652 }
653 if (*haystack2 == ',') {
654 haystack2++;
655 }
656 }
657
658 return false;
659}
660
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700661/*
662 * A simple container that holds a resource type and name. It is ordered first by type then
663 * by name.
664 */
665struct type_ident_pair_t {
666 String16 type;
667 String16 ident;
668
669 type_ident_pair_t() { };
670 type_ident_pair_t(const String16& t, const String16& i) : type(t), ident(i) { }
671 type_ident_pair_t(const type_ident_pair_t& o) : type(o.type), ident(o.ident) { }
672 inline bool operator < (const type_ident_pair_t& o) const {
673 int cmp = compare_type(type, o.type);
674 if (cmp < 0) {
675 return true;
676 } else if (cmp > 0) {
677 return false;
678 } else {
679 return strictly_order_type(ident, o.ident);
680 }
681 }
682};
683
684
Adam Lesinski282e1812014-01-23 18:17:42 -0800685status_t parseAndAddEntry(Bundle* bundle,
686 const sp<AaptFile>& in,
687 ResXMLTree* block,
688 const ResTable_config& config,
689 const String16& myPackage,
690 const String16& curType,
691 const String16& ident,
692 const String16& curTag,
693 bool curIsStyled,
694 int32_t curFormat,
695 bool isFormatted,
696 const String16& product,
Anton Krumina2ef5c02014-03-12 14:46:44 -0700697 PseudolocalizationMethod pseudolocalize,
Adam Lesinski282e1812014-01-23 18:17:42 -0800698 const bool overwrite,
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700699 KeyedVector<type_ident_pair_t, bool>* skippedResourceNames,
Adam Lesinski282e1812014-01-23 18:17:42 -0800700 ResourceTable* outTable)
701{
702 status_t err;
703
704 String16 str;
705 Vector<StringPool::entry_style_span> spans;
706 err = parseStyledString(bundle, in->getPrintableSource().string(), block,
707 curTag, &str, curIsStyled ? &spans : NULL,
708 isFormatted, pseudolocalize);
709
710 if (err < NO_ERROR) {
711 return err;
712 }
713
714 /*
715 * If a product type was specified on the command line
716 * and also in the string, and the two are not the same,
717 * return without adding the string.
718 */
719
720 const char *bundleProduct = bundle->getProduct();
721 if (bundleProduct == NULL) {
722 bundleProduct = "";
723 }
724
725 if (product.size() != 0) {
726 /*
727 * If the command-line-specified product is empty, only "default"
728 * matches. Other variants are skipped. This is so generation
729 * of the R.java file when the product is not known is predictable.
730 */
731
732 if (bundleProduct[0] == '\0') {
733 if (strcmp16(String16("default").string(), product.string()) != 0) {
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700734 /*
735 * This string has a product other than 'default'. Do not add it,
736 * but record it so that if we do not see the same string with
737 * product 'default' or no product, then report an error.
738 */
739 skippedResourceNames->replaceValueFor(
740 type_ident_pair_t(curType, ident), true);
Adam Lesinski282e1812014-01-23 18:17:42 -0800741 return NO_ERROR;
742 }
743 } else {
744 /*
745 * The command-line product is not empty.
746 * If the product for this string is on the command-line list,
747 * it matches. "default" also matches, but only if nothing
748 * else has matched already.
749 */
750
751 if (isInProductList(product, String16(bundleProduct))) {
752 ;
753 } else if (strcmp16(String16("default").string(), product.string()) == 0 &&
754 !outTable->hasBagOrEntry(myPackage, curType, ident, config)) {
755 ;
756 } else {
757 return NO_ERROR;
758 }
759 }
760 }
761
Andreas Gampe2412f842014-09-30 20:55:57 -0700762 if (kIsDebug) {
763 printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n",
764 config.language[0], config.language[1],
765 config.country[0], config.country[1],
766 config.orientation, config.density,
767 String8(ident).string(), String8(str).string());
768 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800769
770 err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
771 myPackage, curType, ident, str, &spans, &config,
772 false, curFormat, overwrite);
773
774 return err;
775}
776
777status_t compileResourceFile(Bundle* bundle,
778 const sp<AaptAssets>& assets,
779 const sp<AaptFile>& in,
780 const ResTable_config& defParams,
781 const bool overwrite,
782 ResourceTable* outTable)
783{
784 ResXMLTree block;
785 status_t err = parseXMLResource(in, &block, false, true);
786 if (err != NO_ERROR) {
787 return err;
788 }
789
790 // Top-level tag.
791 const String16 resources16("resources");
792
793 // Identifier declaration tags.
794 const String16 declare_styleable16("declare-styleable");
795 const String16 attr16("attr");
796
797 // Data creation organizational tags.
798 const String16 string16("string");
799 const String16 drawable16("drawable");
800 const String16 color16("color");
801 const String16 bool16("bool");
802 const String16 integer16("integer");
803 const String16 dimen16("dimen");
804 const String16 fraction16("fraction");
805 const String16 style16("style");
806 const String16 plurals16("plurals");
807 const String16 array16("array");
808 const String16 string_array16("string-array");
809 const String16 integer_array16("integer-array");
810 const String16 public16("public");
811 const String16 public_padding16("public-padding");
812 const String16 private_symbols16("private-symbols");
813 const String16 java_symbol16("java-symbol");
814 const String16 add_resource16("add-resource");
815 const String16 skip16("skip");
816 const String16 eat_comment16("eat-comment");
817
818 // Data creation tags.
819 const String16 bag16("bag");
820 const String16 item16("item");
821
822 // Attribute type constants.
823 const String16 enum16("enum");
824
825 // plural values
826 const String16 other16("other");
827 const String16 quantityOther16("^other");
828 const String16 zero16("zero");
829 const String16 quantityZero16("^zero");
830 const String16 one16("one");
831 const String16 quantityOne16("^one");
832 const String16 two16("two");
833 const String16 quantityTwo16("^two");
834 const String16 few16("few");
835 const String16 quantityFew16("^few");
836 const String16 many16("many");
837 const String16 quantityMany16("^many");
838
839 // useful attribute names and special values
840 const String16 name16("name");
841 const String16 translatable16("translatable");
842 const String16 formatted16("formatted");
843 const String16 false16("false");
844
845 const String16 myPackage(assets->getPackage());
846
847 bool hasErrors = false;
848
849 bool fileIsTranslatable = true;
850 if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) {
851 fileIsTranslatable = false;
852 }
853
854 DefaultKeyedVector<String16, uint32_t> nextPublicId(0);
855
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700856 // Stores the resource names that were skipped. Typically this happens when
857 // AAPT is invoked without a product specified and a resource has no
858 // 'default' product attribute.
859 KeyedVector<type_ident_pair_t, bool> skippedResourceNames;
860
Adam Lesinski282e1812014-01-23 18:17:42 -0800861 ResXMLTree::event_code_t code;
862 do {
863 code = block.next();
864 } while (code == ResXMLTree::START_NAMESPACE);
865
866 size_t len;
867 if (code != ResXMLTree::START_TAG) {
868 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
869 "No start tag found\n");
870 return UNKNOWN_ERROR;
871 }
872 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
873 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
874 "Invalid start tag %s\n", String8(block.getElementName(&len)).string());
875 return UNKNOWN_ERROR;
876 }
877
878 ResTable_config curParams(defParams);
879
880 ResTable_config pseudoParams(curParams);
Anton Krumina2ef5c02014-03-12 14:46:44 -0700881 pseudoParams.language[0] = 'e';
882 pseudoParams.language[1] = 'n';
883 pseudoParams.country[0] = 'X';
884 pseudoParams.country[1] = 'A';
885
886 ResTable_config pseudoBidiParams(curParams);
887 pseudoBidiParams.language[0] = 'a';
888 pseudoBidiParams.language[1] = 'r';
889 pseudoBidiParams.country[0] = 'X';
890 pseudoBidiParams.country[1] = 'B';
Adam Lesinski282e1812014-01-23 18:17:42 -0800891
Igor Viarheichyk47843df2014-05-01 17:04:39 -0700892 // We should skip resources for pseudolocales if they were
893 // already added automatically. This is a fix for a transition period when
894 // manually pseudolocalized resources may be expected.
895 // TODO: remove this check after next SDK version release.
896 if ((bundle->getPseudolocalize() & PSEUDO_ACCENTED &&
897 curParams.locale == pseudoParams.locale) ||
898 (bundle->getPseudolocalize() & PSEUDO_BIDI &&
899 curParams.locale == pseudoBidiParams.locale)) {
900 SourcePos(in->getPrintableSource(), 0).warning(
901 "Resource file %s is skipped as pseudolocalization"
902 " was done automatically.",
903 in->getPrintableSource().string());
904 return NO_ERROR;
905 }
906
Adam Lesinski282e1812014-01-23 18:17:42 -0800907 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
908 if (code == ResXMLTree::START_TAG) {
909 const String16* curTag = NULL;
910 String16 curType;
Adrian Roos58922482015-06-01 17:59:41 -0700911 String16 curName;
Adam Lesinski282e1812014-01-23 18:17:42 -0800912 int32_t curFormat = ResTable_map::TYPE_ANY;
913 bool curIsBag = false;
914 bool curIsBagReplaceOnOverwrite = false;
915 bool curIsStyled = false;
916 bool curIsPseudolocalizable = false;
917 bool curIsFormatted = fileIsTranslatable;
918 bool localHasErrors = false;
919
920 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
921 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
922 && code != ResXMLTree::BAD_DOCUMENT) {
923 if (code == ResXMLTree::END_TAG) {
924 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
925 break;
926 }
927 }
928 }
929 continue;
930
931 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
932 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
933 && code != ResXMLTree::BAD_DOCUMENT) {
934 if (code == ResXMLTree::END_TAG) {
935 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
936 break;
937 }
938 }
939 }
940 continue;
941
942 } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
943 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
944
945 String16 type;
946 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
947 if (typeIdx < 0) {
948 srcPos.error("A 'type' attribute is required for <public>\n");
949 hasErrors = localHasErrors = true;
950 }
951 type = String16(block.getAttributeStringValue(typeIdx, &len));
952
953 String16 name;
954 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
955 if (nameIdx < 0) {
956 srcPos.error("A 'name' attribute is required for <public>\n");
957 hasErrors = localHasErrors = true;
958 }
959 name = String16(block.getAttributeStringValue(nameIdx, &len));
960
961 uint32_t ident = 0;
962 ssize_t identIdx = block.indexOfAttribute(NULL, "id");
963 if (identIdx >= 0) {
964 const char16_t* identStr = block.getAttributeStringValue(identIdx, &len);
965 Res_value identValue;
966 if (!ResTable::stringToInt(identStr, len, &identValue)) {
967 srcPos.error("Given 'id' attribute is not an integer: %s\n",
968 String8(block.getAttributeStringValue(identIdx, &len)).string());
969 hasErrors = localHasErrors = true;
970 } else {
971 ident = identValue.data;
972 nextPublicId.replaceValueFor(type, ident+1);
973 }
974 } else if (nextPublicId.indexOfKey(type) < 0) {
975 srcPos.error("No 'id' attribute supplied <public>,"
976 " and no previous id defined in this file.\n");
977 hasErrors = localHasErrors = true;
978 } else if (!localHasErrors) {
979 ident = nextPublicId.valueFor(type);
980 nextPublicId.replaceValueFor(type, ident+1);
981 }
982
983 if (!localHasErrors) {
984 err = outTable->addPublic(srcPos, myPackage, type, name, ident);
985 if (err < NO_ERROR) {
986 hasErrors = localHasErrors = true;
987 }
988 }
989 if (!localHasErrors) {
990 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
991 if (symbols != NULL) {
992 symbols = symbols->addNestedSymbol(String8(type), srcPos);
993 }
994 if (symbols != NULL) {
995 symbols->makeSymbolPublic(String8(name), srcPos);
996 String16 comment(
997 block.getComment(&len) ? block.getComment(&len) : nulStr);
998 symbols->appendComment(String8(name), comment, srcPos);
999 } else {
1000 srcPos.error("Unable to create symbols!\n");
1001 hasErrors = localHasErrors = true;
1002 }
1003 }
1004
1005 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1006 if (code == ResXMLTree::END_TAG) {
1007 if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
1008 break;
1009 }
1010 }
1011 }
1012 continue;
1013
1014 } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
1015 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1016
1017 String16 type;
1018 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1019 if (typeIdx < 0) {
1020 srcPos.error("A 'type' attribute is required for <public-padding>\n");
1021 hasErrors = localHasErrors = true;
1022 }
1023 type = String16(block.getAttributeStringValue(typeIdx, &len));
1024
1025 String16 name;
1026 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1027 if (nameIdx < 0) {
1028 srcPos.error("A 'name' attribute is required for <public-padding>\n");
1029 hasErrors = localHasErrors = true;
1030 }
1031 name = String16(block.getAttributeStringValue(nameIdx, &len));
1032
1033 uint32_t start = 0;
1034 ssize_t startIdx = block.indexOfAttribute(NULL, "start");
1035 if (startIdx >= 0) {
1036 const char16_t* startStr = block.getAttributeStringValue(startIdx, &len);
1037 Res_value startValue;
1038 if (!ResTable::stringToInt(startStr, len, &startValue)) {
1039 srcPos.error("Given 'start' attribute is not an integer: %s\n",
1040 String8(block.getAttributeStringValue(startIdx, &len)).string());
1041 hasErrors = localHasErrors = true;
1042 } else {
1043 start = startValue.data;
1044 }
1045 } else if (nextPublicId.indexOfKey(type) < 0) {
1046 srcPos.error("No 'start' attribute supplied <public-padding>,"
1047 " and no previous id defined in this file.\n");
1048 hasErrors = localHasErrors = true;
1049 } else if (!localHasErrors) {
1050 start = nextPublicId.valueFor(type);
1051 }
1052
1053 uint32_t end = 0;
1054 ssize_t endIdx = block.indexOfAttribute(NULL, "end");
1055 if (endIdx >= 0) {
1056 const char16_t* endStr = block.getAttributeStringValue(endIdx, &len);
1057 Res_value endValue;
1058 if (!ResTable::stringToInt(endStr, len, &endValue)) {
1059 srcPos.error("Given 'end' attribute is not an integer: %s\n",
1060 String8(block.getAttributeStringValue(endIdx, &len)).string());
1061 hasErrors = localHasErrors = true;
1062 } else {
1063 end = endValue.data;
1064 }
1065 } else {
1066 srcPos.error("No 'end' attribute supplied <public-padding>\n");
1067 hasErrors = localHasErrors = true;
1068 }
1069
1070 if (end >= start) {
1071 nextPublicId.replaceValueFor(type, end+1);
1072 } else {
1073 srcPos.error("Padding start '%ul' is after end '%ul'\n",
1074 start, end);
1075 hasErrors = localHasErrors = true;
1076 }
1077
1078 String16 comment(
1079 block.getComment(&len) ? block.getComment(&len) : nulStr);
1080 for (uint32_t curIdent=start; curIdent<=end; curIdent++) {
1081 if (localHasErrors) {
1082 break;
1083 }
1084 String16 curName(name);
1085 char buf[64];
1086 sprintf(buf, "%d", (int)(end-curIdent+1));
1087 curName.append(String16(buf));
1088
1089 err = outTable->addEntry(srcPos, myPackage, type, curName,
1090 String16("padding"), NULL, &curParams, false,
1091 ResTable_map::TYPE_STRING, overwrite);
1092 if (err < NO_ERROR) {
1093 hasErrors = localHasErrors = true;
1094 break;
1095 }
1096 err = outTable->addPublic(srcPos, myPackage, type,
1097 curName, curIdent);
1098 if (err < NO_ERROR) {
1099 hasErrors = localHasErrors = true;
1100 break;
1101 }
1102 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1103 if (symbols != NULL) {
1104 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1105 }
1106 if (symbols != NULL) {
1107 symbols->makeSymbolPublic(String8(curName), srcPos);
1108 symbols->appendComment(String8(curName), comment, srcPos);
1109 } else {
1110 srcPos.error("Unable to create symbols!\n");
1111 hasErrors = localHasErrors = true;
1112 }
1113 }
1114
1115 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1116 if (code == ResXMLTree::END_TAG) {
1117 if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
1118 break;
1119 }
1120 }
1121 }
1122 continue;
1123
1124 } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
1125 String16 pkg;
1126 ssize_t pkgIdx = block.indexOfAttribute(NULL, "package");
1127 if (pkgIdx < 0) {
1128 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1129 "A 'package' attribute is required for <private-symbols>\n");
1130 hasErrors = localHasErrors = true;
1131 }
1132 pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
1133 if (!localHasErrors) {
Adam Lesinski78713992015-12-07 14:02:15 -08001134 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
1135 "<private-symbols> is deprecated. Use the command line flag "
1136 "--private-symbols instead.\n");
1137 if (assets->havePrivateSymbols()) {
1138 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
1139 "private symbol package already specified. Ignoring...\n");
1140 } else {
1141 assets->setSymbolsPrivatePackage(String8(pkg));
1142 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001143 }
1144
1145 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1146 if (code == ResXMLTree::END_TAG) {
1147 if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
1148 break;
1149 }
1150 }
1151 }
1152 continue;
1153
1154 } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
1155 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1156
1157 String16 type;
1158 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1159 if (typeIdx < 0) {
1160 srcPos.error("A 'type' attribute is required for <public>\n");
1161 hasErrors = localHasErrors = true;
1162 }
1163 type = String16(block.getAttributeStringValue(typeIdx, &len));
1164
1165 String16 name;
1166 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1167 if (nameIdx < 0) {
1168 srcPos.error("A 'name' attribute is required for <public>\n");
1169 hasErrors = localHasErrors = true;
1170 }
1171 name = String16(block.getAttributeStringValue(nameIdx, &len));
1172
1173 sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R"));
1174 if (symbols != NULL) {
1175 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1176 }
1177 if (symbols != NULL) {
1178 symbols->makeSymbolJavaSymbol(String8(name), srcPos);
1179 String16 comment(
1180 block.getComment(&len) ? block.getComment(&len) : nulStr);
1181 symbols->appendComment(String8(name), comment, srcPos);
1182 } else {
1183 srcPos.error("Unable to create symbols!\n");
1184 hasErrors = localHasErrors = true;
1185 }
1186
1187 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1188 if (code == ResXMLTree::END_TAG) {
1189 if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
1190 break;
1191 }
1192 }
1193 }
1194 continue;
1195
1196
1197 } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
1198 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1199
1200 String16 typeName;
1201 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1202 if (typeIdx < 0) {
1203 srcPos.error("A 'type' attribute is required for <add-resource>\n");
1204 hasErrors = localHasErrors = true;
1205 }
1206 typeName = String16(block.getAttributeStringValue(typeIdx, &len));
1207
1208 String16 name;
1209 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1210 if (nameIdx < 0) {
1211 srcPos.error("A 'name' attribute is required for <add-resource>\n");
1212 hasErrors = localHasErrors = true;
1213 }
1214 name = String16(block.getAttributeStringValue(nameIdx, &len));
1215
1216 outTable->canAddEntry(srcPos, myPackage, typeName, name);
1217
1218 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1219 if (code == ResXMLTree::END_TAG) {
1220 if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
1221 break;
1222 }
1223 }
1224 }
1225 continue;
1226
1227 } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
1228 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1229
1230 String16 ident;
1231 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
1232 if (identIdx < 0) {
1233 srcPos.error("A 'name' attribute is required for <declare-styleable>\n");
1234 hasErrors = localHasErrors = true;
1235 }
1236 ident = String16(block.getAttributeStringValue(identIdx, &len));
1237
1238 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1239 if (!localHasErrors) {
1240 if (symbols != NULL) {
1241 symbols = symbols->addNestedSymbol(String8("styleable"), srcPos);
1242 }
1243 sp<AaptSymbols> styleSymbols = symbols;
1244 if (symbols != NULL) {
1245 symbols = symbols->addNestedSymbol(String8(ident), srcPos);
1246 }
1247 if (symbols == NULL) {
1248 srcPos.error("Unable to create symbols!\n");
1249 return UNKNOWN_ERROR;
1250 }
1251
1252 String16 comment(
1253 block.getComment(&len) ? block.getComment(&len) : nulStr);
1254 styleSymbols->appendComment(String8(ident), comment, srcPos);
1255 } else {
1256 symbols = NULL;
1257 }
1258
1259 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1260 if (code == ResXMLTree::START_TAG) {
1261 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
1262 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1263 && code != ResXMLTree::BAD_DOCUMENT) {
1264 if (code == ResXMLTree::END_TAG) {
1265 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
1266 break;
1267 }
1268 }
1269 }
1270 continue;
1271 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
1272 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1273 && code != ResXMLTree::BAD_DOCUMENT) {
1274 if (code == ResXMLTree::END_TAG) {
1275 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
1276 break;
1277 }
1278 }
1279 }
1280 continue;
1281 } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) {
1282 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1283 "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n",
1284 String8(block.getElementName(&len)).string());
1285 return UNKNOWN_ERROR;
1286 }
1287
1288 String16 comment(
1289 block.getComment(&len) ? block.getComment(&len) : nulStr);
1290 String16 itemIdent;
1291 err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true);
1292 if (err != NO_ERROR) {
1293 hasErrors = localHasErrors = true;
1294 }
1295
1296 if (symbols != NULL) {
1297 SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber());
1298 symbols->addSymbol(String8(itemIdent), 0, srcPos);
1299 symbols->appendComment(String8(itemIdent), comment, srcPos);
1300 //printf("Attribute %s comment: %s\n", String8(itemIdent).string(),
1301 // String8(comment).string());
1302 }
1303 } else if (code == ResXMLTree::END_TAG) {
1304 if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
1305 break;
1306 }
1307
1308 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1309 "Found tag </%s> where </attr> is expected\n",
1310 String8(block.getElementName(&len)).string());
1311 return UNKNOWN_ERROR;
1312 }
1313 }
1314 continue;
1315
1316 } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
1317 err = compileAttribute(in, block, myPackage, outTable, NULL);
1318 if (err != NO_ERROR) {
1319 hasErrors = true;
1320 }
1321 continue;
1322
1323 } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) {
1324 curTag = &item16;
1325 ssize_t attri = block.indexOfAttribute(NULL, "type");
1326 if (attri >= 0) {
1327 curType = String16(block.getAttributeStringValue(attri, &len));
Adrian Roos58922482015-06-01 17:59:41 -07001328 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1329 if (nameIdx >= 0) {
1330 curName = String16(block.getAttributeStringValue(nameIdx, &len));
1331 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001332 ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
1333 if (formatIdx >= 0) {
1334 String16 formatStr = String16(block.getAttributeStringValue(
1335 formatIdx, &len));
1336 curFormat = parse_flags(formatStr.string(), formatStr.size(),
1337 gFormatFlags);
1338 if (curFormat == 0) {
1339 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1340 "Tag <item> 'format' attribute value \"%s\" not valid\n",
1341 String8(formatStr).string());
1342 hasErrors = localHasErrors = true;
1343 }
1344 }
1345 } else {
1346 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1347 "A 'type' attribute is required for <item>\n");
1348 hasErrors = localHasErrors = true;
1349 }
1350 curIsStyled = true;
1351 } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
1352 // Note the existence and locale of every string we process
Narayan Kamath91447d82014-01-21 15:32:36 +00001353 char rawLocale[RESTABLE_MAX_LOCALE_LEN];
1354 curParams.getBcp47Locale(rawLocale);
Adam Lesinski282e1812014-01-23 18:17:42 -08001355 String8 locale(rawLocale);
1356 String16 name;
1357 String16 translatable;
1358 String16 formatted;
1359
1360 size_t n = block.getAttributeCount();
1361 for (size_t i = 0; i < n; i++) {
1362 size_t length;
Dan Albertf348c152014-09-08 18:28:00 -07001363 const char16_t* attr = block.getAttributeName(i, &length);
Adam Lesinski282e1812014-01-23 18:17:42 -08001364 if (strcmp16(attr, name16.string()) == 0) {
1365 name.setTo(block.getAttributeStringValue(i, &length));
1366 } else if (strcmp16(attr, translatable16.string()) == 0) {
1367 translatable.setTo(block.getAttributeStringValue(i, &length));
1368 } else if (strcmp16(attr, formatted16.string()) == 0) {
1369 formatted.setTo(block.getAttributeStringValue(i, &length));
1370 }
1371 }
1372
1373 if (name.size() > 0) {
Adrian Roos58922482015-06-01 17:59:41 -07001374 if (locale.size() == 0) {
1375 outTable->addDefaultLocalization(name);
1376 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001377 if (translatable == false16) {
1378 curIsFormatted = false;
1379 // Untranslatable strings must only exist in the default [empty] locale
1380 if (locale.size() > 0) {
Adam Lesinskia01a9372014-03-20 18:04:57 -07001381 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
1382 "string '%s' marked untranslatable but exists in locale '%s'\n",
1383 String8(name).string(),
Adam Lesinski282e1812014-01-23 18:17:42 -08001384 locale.string());
1385 // hasErrors = localHasErrors = true;
1386 } else {
1387 // Intentionally empty block:
1388 //
1389 // Don't add untranslatable strings to the localization table; that
1390 // way if we later see localizations of them, they'll be flagged as
1391 // having no default translation.
1392 }
1393 } else {
Adam Lesinskia01a9372014-03-20 18:04:57 -07001394 outTable->addLocalization(
1395 name,
1396 locale,
1397 SourcePos(in->getPrintableSource(), block.getLineNumber()));
Adam Lesinski282e1812014-01-23 18:17:42 -08001398 }
1399
1400 if (formatted == false16) {
1401 curIsFormatted = false;
1402 }
1403 }
1404
1405 curTag = &string16;
1406 curType = string16;
1407 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
1408 curIsStyled = true;
Igor Viarheichyk84410b02014-04-30 11:56:42 -07001409 curIsPseudolocalizable = fileIsTranslatable && (translatable != false16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001410 } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) {
1411 curTag = &drawable16;
1412 curType = drawable16;
1413 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
1414 } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) {
1415 curTag = &color16;
1416 curType = color16;
1417 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
1418 } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
1419 curTag = &bool16;
1420 curType = bool16;
1421 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
1422 } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
1423 curTag = &integer16;
1424 curType = integer16;
1425 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
1426 } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) {
1427 curTag = &dimen16;
1428 curType = dimen16;
1429 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION;
1430 } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) {
1431 curTag = &fraction16;
1432 curType = fraction16;
1433 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION;
1434 } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) {
1435 curTag = &bag16;
1436 curIsBag = true;
1437 ssize_t attri = block.indexOfAttribute(NULL, "type");
1438 if (attri >= 0) {
1439 curType = String16(block.getAttributeStringValue(attri, &len));
1440 } else {
1441 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1442 "A 'type' attribute is required for <bag>\n");
1443 hasErrors = localHasErrors = true;
1444 }
1445 } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) {
1446 curTag = &style16;
1447 curType = style16;
1448 curIsBag = true;
1449 } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) {
1450 curTag = &plurals16;
1451 curType = plurals16;
1452 curIsBag = true;
Anton Krumina2ef5c02014-03-12 14:46:44 -07001453 curIsPseudolocalizable = fileIsTranslatable;
Adam Lesinski282e1812014-01-23 18:17:42 -08001454 } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
1455 curTag = &array16;
1456 curType = array16;
1457 curIsBag = true;
1458 curIsBagReplaceOnOverwrite = true;
1459 ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
1460 if (formatIdx >= 0) {
1461 String16 formatStr = String16(block.getAttributeStringValue(
1462 formatIdx, &len));
1463 curFormat = parse_flags(formatStr.string(), formatStr.size(),
1464 gFormatFlags);
1465 if (curFormat == 0) {
1466 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1467 "Tag <array> 'format' attribute value \"%s\" not valid\n",
1468 String8(formatStr).string());
1469 hasErrors = localHasErrors = true;
1470 }
1471 }
1472 } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
1473 // Check whether these strings need valid formats.
1474 // (simplified form of what string16 does above)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001475 bool isTranslatable = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001476 size_t n = block.getAttributeCount();
Narayan Kamath9a9fa162013-12-18 13:27:30 +00001477
1478 // Pseudolocalizable by default, unless this string array isn't
1479 // translatable.
Adam Lesinski282e1812014-01-23 18:17:42 -08001480 for (size_t i = 0; i < n; i++) {
1481 size_t length;
Dan Albertf348c152014-09-08 18:28:00 -07001482 const char16_t* attr = block.getAttributeName(i, &length);
Narayan Kamath9a9fa162013-12-18 13:27:30 +00001483 if (strcmp16(attr, formatted16.string()) == 0) {
Dan Albertf348c152014-09-08 18:28:00 -07001484 const char16_t* value = block.getAttributeStringValue(i, &length);
Adam Lesinski282e1812014-01-23 18:17:42 -08001485 if (strcmp16(value, false16.string()) == 0) {
1486 curIsFormatted = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001487 }
Anton Krumina2ef5c02014-03-12 14:46:44 -07001488 } else if (strcmp16(attr, translatable16.string()) == 0) {
Dan Albertf348c152014-09-08 18:28:00 -07001489 const char16_t* value = block.getAttributeStringValue(i, &length);
Anton Krumina2ef5c02014-03-12 14:46:44 -07001490 if (strcmp16(value, false16.string()) == 0) {
1491 isTranslatable = false;
1492 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001493 }
1494 }
1495
1496 curTag = &string_array16;
1497 curType = array16;
1498 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
1499 curIsBag = true;
1500 curIsBagReplaceOnOverwrite = true;
Anton Krumina2ef5c02014-03-12 14:46:44 -07001501 curIsPseudolocalizable = isTranslatable && fileIsTranslatable;
Adam Lesinski282e1812014-01-23 18:17:42 -08001502 } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
1503 curTag = &integer_array16;
1504 curType = array16;
1505 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
1506 curIsBag = true;
1507 curIsBagReplaceOnOverwrite = true;
1508 } else {
1509 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1510 "Found tag %s where item is expected\n",
1511 String8(block.getElementName(&len)).string());
1512 return UNKNOWN_ERROR;
1513 }
1514
1515 String16 ident;
1516 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
1517 if (identIdx >= 0) {
1518 ident = String16(block.getAttributeStringValue(identIdx, &len));
1519 } else {
1520 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1521 "A 'name' attribute is required for <%s>\n",
1522 String8(*curTag).string());
1523 hasErrors = localHasErrors = true;
1524 }
1525
1526 String16 product;
1527 identIdx = block.indexOfAttribute(NULL, "product");
1528 if (identIdx >= 0) {
1529 product = String16(block.getAttributeStringValue(identIdx, &len));
1530 }
1531
1532 String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
1533
1534 if (curIsBag) {
1535 // Figure out the parent of this bag...
1536 String16 parentIdent;
1537 ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent");
1538 if (parentIdentIdx >= 0) {
1539 parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len));
1540 } else {
1541 ssize_t sep = ident.findLast('.');
1542 if (sep >= 0) {
1543 parentIdent.setTo(ident, sep);
1544 }
1545 }
1546
1547 if (!localHasErrors) {
1548 err = outTable->startBag(SourcePos(in->getPrintableSource(),
1549 block.getLineNumber()), myPackage, curType, ident,
1550 parentIdent, &curParams,
1551 overwrite, curIsBagReplaceOnOverwrite);
1552 if (err != NO_ERROR) {
1553 hasErrors = localHasErrors = true;
1554 }
1555 }
1556
1557 ssize_t elmIndex = 0;
1558 char elmIndexStr[14];
1559 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1560 && code != ResXMLTree::BAD_DOCUMENT) {
1561
1562 if (code == ResXMLTree::START_TAG) {
1563 if (strcmp16(block.getElementName(&len), item16.string()) != 0) {
1564 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1565 "Tag <%s> can not appear inside <%s>, only <item>\n",
1566 String8(block.getElementName(&len)).string(),
1567 String8(*curTag).string());
1568 return UNKNOWN_ERROR;
1569 }
1570
1571 String16 itemIdent;
1572 if (curType == array16) {
1573 sprintf(elmIndexStr, "^index_%d", (int)elmIndex++);
1574 itemIdent = String16(elmIndexStr);
1575 } else if (curType == plurals16) {
1576 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity");
1577 if (itemIdentIdx >= 0) {
1578 String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len));
1579 if (quantity16 == other16) {
1580 itemIdent = quantityOther16;
1581 }
1582 else if (quantity16 == zero16) {
1583 itemIdent = quantityZero16;
1584 }
1585 else if (quantity16 == one16) {
1586 itemIdent = quantityOne16;
1587 }
1588 else if (quantity16 == two16) {
1589 itemIdent = quantityTwo16;
1590 }
1591 else if (quantity16 == few16) {
1592 itemIdent = quantityFew16;
1593 }
1594 else if (quantity16 == many16) {
1595 itemIdent = quantityMany16;
1596 }
1597 else {
1598 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1599 "Illegal 'quantity' attribute is <item> inside <plurals>\n");
1600 hasErrors = localHasErrors = true;
1601 }
1602 } else {
1603 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1604 "A 'quantity' attribute is required for <item> inside <plurals>\n");
1605 hasErrors = localHasErrors = true;
1606 }
1607 } else {
1608 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
1609 if (itemIdentIdx >= 0) {
1610 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
1611 } else {
1612 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1613 "A 'name' attribute is required for <item>\n");
1614 hasErrors = localHasErrors = true;
1615 }
1616 }
1617
1618 ResXMLParser::ResXMLPosition parserPosition;
1619 block.getPosition(&parserPosition);
1620
1621 err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
1622 ident, parentIdent, itemIdent, curFormat, curIsFormatted,
Anton Krumina2ef5c02014-03-12 14:46:44 -07001623 product, NO_PSEUDOLOCALIZATION, overwrite, outTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001624 if (err == NO_ERROR) {
1625 if (curIsPseudolocalizable && localeIsDefined(curParams)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001626 && bundle->getPseudolocalize() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001627 // pseudolocalize here
Anton Krumina2ef5c02014-03-12 14:46:44 -07001628 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
1629 PSEUDO_ACCENTED) {
1630 block.setPosition(parserPosition);
1631 err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
1632 curType, ident, parentIdent, itemIdent, curFormat,
1633 curIsFormatted, product, PSEUDO_ACCENTED,
1634 overwrite, outTable);
1635 }
1636 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
1637 PSEUDO_BIDI) {
1638 block.setPosition(parserPosition);
1639 err = parseAndAddBag(bundle, in, &block, pseudoBidiParams, myPackage,
1640 curType, ident, parentIdent, itemIdent, curFormat,
1641 curIsFormatted, product, PSEUDO_BIDI,
1642 overwrite, outTable);
1643 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001644 }
Anton Krumina2ef5c02014-03-12 14:46:44 -07001645 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001646 if (err != NO_ERROR) {
1647 hasErrors = localHasErrors = true;
1648 }
1649 } else if (code == ResXMLTree::END_TAG) {
1650 if (strcmp16(block.getElementName(&len), curTag->string()) != 0) {
1651 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1652 "Found tag </%s> where </%s> is expected\n",
1653 String8(block.getElementName(&len)).string(),
1654 String8(*curTag).string());
1655 return UNKNOWN_ERROR;
1656 }
1657 break;
1658 }
1659 }
1660 } else {
1661 ResXMLParser::ResXMLPosition parserPosition;
1662 block.getPosition(&parserPosition);
1663
1664 err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
1665 *curTag, curIsStyled, curFormat, curIsFormatted,
Anton Krumina2ef5c02014-03-12 14:46:44 -07001666 product, NO_PSEUDOLOCALIZATION, overwrite, &skippedResourceNames, outTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001667
1668 if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
1669 hasErrors = localHasErrors = true;
1670 }
1671 else if (err == NO_ERROR) {
Adrian Roos58922482015-06-01 17:59:41 -07001672 if (curType == string16 && !curParams.language[0] && !curParams.country[0]) {
1673 outTable->addDefaultLocalization(curName);
1674 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001675 if (curIsPseudolocalizable && localeIsDefined(curParams)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001676 && bundle->getPseudolocalize() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001677 // pseudolocalize here
Anton Krumina2ef5c02014-03-12 14:46:44 -07001678 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
1679 PSEUDO_ACCENTED) {
1680 block.setPosition(parserPosition);
1681 err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
1682 ident, *curTag, curIsStyled, curFormat,
1683 curIsFormatted, product,
1684 PSEUDO_ACCENTED, overwrite, &skippedResourceNames, outTable);
1685 }
1686 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
1687 PSEUDO_BIDI) {
1688 block.setPosition(parserPosition);
1689 err = parseAndAddEntry(bundle, in, &block, pseudoBidiParams,
1690 myPackage, curType, ident, *curTag, curIsStyled, curFormat,
1691 curIsFormatted, product,
1692 PSEUDO_BIDI, overwrite, &skippedResourceNames, outTable);
1693 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001694 if (err != NO_ERROR) {
1695 hasErrors = localHasErrors = true;
1696 }
1697 }
1698 }
1699 }
1700
1701#if 0
1702 if (comment.size() > 0) {
1703 printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(),
1704 String8(curType).string(), String8(ident).string(),
1705 String8(comment).string());
1706 }
1707#endif
1708 if (!localHasErrors) {
1709 outTable->appendComment(myPackage, curType, ident, comment, false);
1710 }
1711 }
1712 else if (code == ResXMLTree::END_TAG) {
1713 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
1714 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1715 "Unexpected end tag %s\n", String8(block.getElementName(&len)).string());
1716 return UNKNOWN_ERROR;
1717 }
1718 }
1719 else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) {
1720 }
1721 else if (code == ResXMLTree::TEXT) {
1722 if (isWhitespace(block.getText(&len))) {
1723 continue;
1724 }
1725 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1726 "Found text \"%s\" where item tag is expected\n",
1727 String8(block.getText(&len)).string());
1728 return UNKNOWN_ERROR;
1729 }
1730 }
1731
Adam Lesinski8ff15b42013-10-07 16:54:01 -07001732 // For every resource defined, there must be exist one variant with a product attribute
1733 // set to 'default' (or no product attribute at all).
1734 // We check to see that for every resource that was ignored because of a mismatched
1735 // product attribute, some product variant of that resource was processed.
1736 for (size_t i = 0; i < skippedResourceNames.size(); i++) {
1737 if (skippedResourceNames[i]) {
1738 const type_ident_pair_t& p = skippedResourceNames.keyAt(i);
1739 if (!outTable->hasBagOrEntry(myPackage, p.type, p.ident)) {
1740 const char* bundleProduct =
1741 (bundle->getProduct() == NULL) ? "" : bundle->getProduct();
1742 fprintf(stderr, "In resource file %s: %s\n",
1743 in->getPrintableSource().string(),
1744 curParams.toString().string());
1745
1746 fprintf(stderr, "\t%s '%s' does not match product %s.\n"
1747 "\tYou may have forgotten to include a 'default' product variant"
1748 " of the resource.\n",
1749 String8(p.type).string(), String8(p.ident).string(),
1750 bundleProduct[0] == 0 ? "default" : bundleProduct);
1751 return UNKNOWN_ERROR;
1752 }
1753 }
1754 }
1755
Andreas Gampe2412f842014-09-30 20:55:57 -07001756 return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001757}
1758
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001759ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
1760 : mAssetsPackage(assetsPackage)
1761 , mPackageType(type)
1762 , mTypeIdOffset(0)
1763 , mNumLocal(0)
1764 , mBundle(bundle)
Adam Lesinski282e1812014-01-23 18:17:42 -08001765{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001766 ssize_t packageId = -1;
1767 switch (mPackageType) {
1768 case App:
1769 case AppFeature:
1770 packageId = 0x7f;
1771 break;
1772
1773 case System:
1774 packageId = 0x01;
1775 break;
1776
1777 case SharedLibrary:
1778 packageId = 0x00;
1779 break;
1780
1781 default:
1782 assert(0);
1783 break;
1784 }
1785 sp<Package> package = new Package(mAssetsPackage, packageId);
1786 mPackages.add(assetsPackage, package);
1787 mOrderedPackages.add(package);
1788
1789 // Every resource table always has one first entry, the bag attributes.
1790 const SourcePos unknown(String8("????"), 0);
1791 getType(mAssetsPackage, String16("attr"), unknown);
1792}
1793
1794static uint32_t findLargestTypeIdForPackage(const ResTable& table, const String16& packageName) {
1795 const size_t basePackageCount = table.getBasePackageCount();
1796 for (size_t i = 0; i < basePackageCount; i++) {
1797 if (packageName == table.getBasePackageName(i)) {
1798 return table.getLastTypeIdForPackage(i);
1799 }
1800 }
1801 return 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08001802}
1803
1804status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
1805{
1806 status_t err = assets->buildIncludedResources(bundle);
1807 if (err != NO_ERROR) {
1808 return err;
1809 }
1810
Adam Lesinski282e1812014-01-23 18:17:42 -08001811 mAssets = assets;
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001812 mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
Adam Lesinski282e1812014-01-23 18:17:42 -08001813
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001814 const String8& featureAfter = bundle->getFeatureAfterPackage();
1815 if (!featureAfter.isEmpty()) {
1816 AssetManager featureAssetManager;
1817 if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
1818 fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
1819 featureAfter.string());
1820 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001821 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001822
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001823 const ResTable& featureTable = featureAssetManager.getResources(false);
Dan Albert030f5362015-03-04 13:54:20 -08001824 mTypeIdOffset = std::max(mTypeIdOffset,
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001825 findLargestTypeIdForPackage(featureTable, mAssetsPackage));
1826 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001827
1828 return NO_ERROR;
1829}
1830
1831status_t ResourceTable::addPublic(const SourcePos& sourcePos,
1832 const String16& package,
1833 const String16& type,
1834 const String16& name,
1835 const uint32_t ident)
1836{
1837 uint32_t rid = mAssets->getIncludedResources()
1838 .identifierForName(name.string(), name.size(),
1839 type.string(), type.size(),
1840 package.string(), package.size());
1841 if (rid != 0) {
1842 sourcePos.error("Error declaring public resource %s/%s for included package %s\n",
1843 String8(type).string(), String8(name).string(),
1844 String8(package).string());
1845 return UNKNOWN_ERROR;
1846 }
1847
1848 sp<Type> t = getType(package, type, sourcePos);
1849 if (t == NULL) {
1850 return UNKNOWN_ERROR;
1851 }
1852 return t->addPublic(sourcePos, name, ident);
1853}
1854
1855status_t ResourceTable::addEntry(const SourcePos& sourcePos,
1856 const String16& package,
1857 const String16& type,
1858 const String16& name,
1859 const String16& value,
1860 const Vector<StringPool::entry_style_span>* style,
1861 const ResTable_config* params,
1862 const bool doSetIndex,
1863 const int32_t format,
1864 const bool overwrite)
1865{
Adam Lesinski282e1812014-01-23 18:17:42 -08001866 uint32_t rid = mAssets->getIncludedResources()
1867 .identifierForName(name.string(), name.size(),
1868 type.string(), type.size(),
1869 package.string(), package.size());
1870 if (rid != 0) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001871 sourcePos.error("Resource entry %s/%s is already defined in package %s.",
1872 String8(type).string(), String8(name).string(), String8(package).string());
1873 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001874 }
1875
Adam Lesinski282e1812014-01-23 18:17:42 -08001876 sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
1877 params, doSetIndex);
1878 if (e == NULL) {
1879 return UNKNOWN_ERROR;
1880 }
1881 status_t err = e->setItem(sourcePos, value, style, format, overwrite);
1882 if (err == NO_ERROR) {
1883 mNumLocal++;
1884 }
1885 return err;
1886}
1887
1888status_t ResourceTable::startBag(const SourcePos& sourcePos,
1889 const String16& package,
1890 const String16& type,
1891 const String16& name,
1892 const String16& bagParent,
1893 const ResTable_config* params,
1894 bool overlay,
Andreas Gampe2412f842014-09-30 20:55:57 -07001895 bool replace, bool /* isId */)
Adam Lesinski282e1812014-01-23 18:17:42 -08001896{
1897 status_t result = NO_ERROR;
1898
1899 // Check for adding entries in other packages... for now we do
1900 // nothing. We need to do the right thing here to support skinning.
1901 uint32_t rid = mAssets->getIncludedResources()
1902 .identifierForName(name.string(), name.size(),
1903 type.string(), type.size(),
1904 package.string(), package.size());
1905 if (rid != 0) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001906 sourcePos.error("Resource entry %s/%s is already defined in package %s.",
1907 String8(type).string(), String8(name).string(), String8(package).string());
1908 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001909 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001910
Adam Lesinski282e1812014-01-23 18:17:42 -08001911 if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) {
1912 bool canAdd = false;
1913 sp<Package> p = mPackages.valueFor(package);
1914 if (p != NULL) {
1915 sp<Type> t = p->getTypes().valueFor(type);
1916 if (t != NULL) {
1917 if (t->getCanAddEntries().indexOf(name) >= 0) {
1918 canAdd = true;
1919 }
1920 }
1921 }
1922 if (!canAdd) {
1923 sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n",
1924 String8(name).string());
1925 return UNKNOWN_ERROR;
1926 }
1927 }
1928 sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params);
1929 if (e == NULL) {
1930 return UNKNOWN_ERROR;
1931 }
1932
1933 // If a parent is explicitly specified, set it.
1934 if (bagParent.size() > 0) {
1935 e->setParent(bagParent);
1936 }
1937
1938 if ((result = e->makeItABag(sourcePos)) != NO_ERROR) {
1939 return result;
1940 }
1941
1942 if (overlay && replace) {
1943 return e->emptyBag(sourcePos);
1944 }
1945 return result;
1946}
1947
1948status_t ResourceTable::addBag(const SourcePos& sourcePos,
1949 const String16& package,
1950 const String16& type,
1951 const String16& name,
1952 const String16& bagParent,
1953 const String16& bagKey,
1954 const String16& value,
1955 const Vector<StringPool::entry_style_span>* style,
1956 const ResTable_config* params,
1957 bool replace, bool isId, const int32_t format)
1958{
1959 // Check for adding entries in other packages... for now we do
1960 // nothing. We need to do the right thing here to support skinning.
1961 uint32_t rid = mAssets->getIncludedResources()
1962 .identifierForName(name.string(), name.size(),
1963 type.string(), type.size(),
1964 package.string(), package.size());
1965 if (rid != 0) {
1966 return NO_ERROR;
1967 }
1968
1969#if 0
1970 if (name == String16("left")) {
1971 printf("Adding bag left: file=%s, line=%d, type=%s\n",
1972 sourcePos.file.striing(), sourcePos.line, String8(type).string());
1973 }
1974#endif
1975 sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params);
1976 if (e == NULL) {
1977 return UNKNOWN_ERROR;
1978 }
1979
1980 // If a parent is explicitly specified, set it.
1981 if (bagParent.size() > 0) {
1982 e->setParent(bagParent);
1983 }
1984
1985 const bool first = e->getBag().indexOfKey(bagKey) < 0;
1986 status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format);
1987 if (err == NO_ERROR && first) {
1988 mNumLocal++;
1989 }
1990 return err;
1991}
1992
1993bool ResourceTable::hasBagOrEntry(const String16& package,
1994 const String16& type,
1995 const String16& name) const
1996{
1997 // First look for this in the included resources...
1998 uint32_t rid = mAssets->getIncludedResources()
1999 .identifierForName(name.string(), name.size(),
2000 type.string(), type.size(),
2001 package.string(), package.size());
2002 if (rid != 0) {
2003 return true;
2004 }
2005
2006 sp<Package> p = mPackages.valueFor(package);
2007 if (p != NULL) {
2008 sp<Type> t = p->getTypes().valueFor(type);
2009 if (t != NULL) {
2010 sp<ConfigList> c = t->getConfigs().valueFor(name);
2011 if (c != NULL) return true;
2012 }
2013 }
2014
2015 return false;
2016}
2017
2018bool ResourceTable::hasBagOrEntry(const String16& package,
2019 const String16& type,
2020 const String16& name,
2021 const ResTable_config& config) const
2022{
2023 // First look for this in the included resources...
2024 uint32_t rid = mAssets->getIncludedResources()
2025 .identifierForName(name.string(), name.size(),
2026 type.string(), type.size(),
2027 package.string(), package.size());
2028 if (rid != 0) {
2029 return true;
2030 }
2031
2032 sp<Package> p = mPackages.valueFor(package);
2033 if (p != NULL) {
2034 sp<Type> t = p->getTypes().valueFor(type);
2035 if (t != NULL) {
2036 sp<ConfigList> c = t->getConfigs().valueFor(name);
2037 if (c != NULL) {
2038 sp<Entry> e = c->getEntries().valueFor(config);
2039 if (e != NULL) {
2040 return true;
2041 }
2042 }
2043 }
2044 }
2045
2046 return false;
2047}
2048
2049bool ResourceTable::hasBagOrEntry(const String16& ref,
2050 const String16* defType,
2051 const String16* defPackage)
2052{
2053 String16 package, type, name;
2054 if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name,
2055 defType, defPackage ? defPackage:&mAssetsPackage, NULL)) {
2056 return false;
2057 }
2058 return hasBagOrEntry(package, type, name);
2059}
2060
2061bool ResourceTable::appendComment(const String16& package,
2062 const String16& type,
2063 const String16& name,
2064 const String16& comment,
2065 bool onlyIfEmpty)
2066{
2067 if (comment.size() <= 0) {
2068 return true;
2069 }
2070
2071 sp<Package> p = mPackages.valueFor(package);
2072 if (p != NULL) {
2073 sp<Type> t = p->getTypes().valueFor(type);
2074 if (t != NULL) {
2075 sp<ConfigList> c = t->getConfigs().valueFor(name);
2076 if (c != NULL) {
2077 c->appendComment(comment, onlyIfEmpty);
2078 return true;
2079 }
2080 }
2081 }
2082 return false;
2083}
2084
2085bool ResourceTable::appendTypeComment(const String16& package,
2086 const String16& type,
2087 const String16& name,
2088 const String16& comment)
2089{
2090 if (comment.size() <= 0) {
2091 return true;
2092 }
2093
2094 sp<Package> p = mPackages.valueFor(package);
2095 if (p != NULL) {
2096 sp<Type> t = p->getTypes().valueFor(type);
2097 if (t != NULL) {
2098 sp<ConfigList> c = t->getConfigs().valueFor(name);
2099 if (c != NULL) {
2100 c->appendTypeComment(comment);
2101 return true;
2102 }
2103 }
2104 }
2105 return false;
2106}
2107
Adam Lesinskiafc79be2016-02-22 09:16:33 -08002108bool ResourceTable::makeAttribute(const String16& package,
2109 const String16& name,
2110 const SourcePos& source,
2111 int32_t format,
2112 const String16& comment,
2113 bool shouldAppendComment) {
2114 const String16 attr16("attr");
2115
2116 // First look for this in the included resources...
2117 uint32_t rid = mAssets->getIncludedResources()
2118 .identifierForName(name.string(), name.size(),
2119 attr16.string(), attr16.size(),
2120 package.string(), package.size());
2121 if (rid != 0) {
2122 source.error("Attribute \"%s\" has already been defined", String8(name).string());
2123 return false;
2124 }
2125
2126 sp<ResourceTable::Entry> entry = getEntry(package, attr16, name, source, false);
2127 if (entry == NULL) {
2128 source.error("Failed to create entry attr/%s", String8(name).string());
2129 return false;
2130 }
2131
2132 if (entry->makeItABag(source) != NO_ERROR) {
2133 return false;
2134 }
2135
2136 const String16 formatKey16("^type");
2137 const String16 formatValue16(String8::format("%d", format));
2138
2139 ssize_t idx = entry->getBag().indexOfKey(formatKey16);
2140 if (idx >= 0) {
2141 // We have already set a format for this attribute, check if they are different.
2142 // We allow duplicate attribute definitions so long as they are identical.
2143 // This is to ensure inter-operation with libraries that define the same generic attribute.
2144 const Item& formatItem = entry->getBag().valueAt(idx);
2145 if ((format & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) ||
2146 formatItem.value != formatValue16) {
2147 source.error("Attribute \"%s\" already defined with incompatible format.\n"
2148 "%s:%d: Original attribute defined here.",
2149 String8(name).string(), formatItem.sourcePos.file.string(),
2150 formatItem.sourcePos.line);
2151 return false;
2152 }
2153 } else {
2154 entry->addToBag(source, formatKey16, formatValue16);
2155 // Increment the number of resources we have. This is used to determine if we should
2156 // even generate a resource table.
2157 mNumLocal++;
2158 }
2159 appendComment(package, attr16, name, comment, shouldAppendComment);
2160 return true;
2161}
2162
Adam Lesinski282e1812014-01-23 18:17:42 -08002163void ResourceTable::canAddEntry(const SourcePos& pos,
2164 const String16& package, const String16& type, const String16& name)
2165{
2166 sp<Type> t = getType(package, type, pos);
2167 if (t != NULL) {
2168 t->canAddEntry(name);
2169 }
2170}
2171
2172size_t ResourceTable::size() const {
2173 return mPackages.size();
2174}
2175
2176size_t ResourceTable::numLocalResources() const {
2177 return mNumLocal;
2178}
2179
2180bool ResourceTable::hasResources() const {
2181 return mNumLocal > 0;
2182}
2183
Adam Lesinski27f69f42014-08-21 13:19:12 -07002184sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
2185 const bool isBase)
Adam Lesinski282e1812014-01-23 18:17:42 -08002186{
2187 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
Adam Lesinski27f69f42014-08-21 13:19:12 -07002188 status_t err = flatten(bundle, filter, data, isBase);
Adam Lesinski282e1812014-01-23 18:17:42 -08002189 return err == NO_ERROR ? data : NULL;
2190}
2191
2192inline uint32_t ResourceTable::getResId(const sp<Package>& p,
2193 const sp<Type>& t,
2194 uint32_t nameId)
2195{
2196 return makeResId(p->getAssignedId(), t->getIndex(), nameId);
2197}
2198
2199uint32_t ResourceTable::getResId(const String16& package,
2200 const String16& type,
2201 const String16& name,
2202 bool onlyPublic) const
2203{
2204 uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic);
2205 if (id != 0) return id; // cache hit
2206
Adam Lesinski282e1812014-01-23 18:17:42 -08002207 // First look for this in the included resources...
2208 uint32_t specFlags = 0;
2209 uint32_t rid = mAssets->getIncludedResources()
2210 .identifierForName(name.string(), name.size(),
2211 type.string(), type.size(),
2212 package.string(), package.size(),
2213 &specFlags);
2214 if (rid != 0) {
Adam Lesinskifa1e9d72017-01-24 16:16:09 -08002215 if (onlyPublic && (specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
2216 // If this is a feature split and the resource has the same
2217 // package name as us, then everything is public.
2218 if (mPackageType != AppFeature || mAssetsPackage != package) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002219 return 0;
2220 }
2221 }
2222
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002223 return ResourceIdCache::store(package, type, name, onlyPublic, rid);
Adam Lesinski282e1812014-01-23 18:17:42 -08002224 }
2225
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002226 sp<Package> p = mPackages.valueFor(package);
2227 if (p == NULL) return 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08002228 sp<Type> t = p->getTypes().valueFor(type);
2229 if (t == NULL) return 0;
Adam Lesinski9b624c12014-11-19 17:49:26 -08002230 sp<ConfigList> c = t->getConfigs().valueFor(name);
2231 if (c == NULL) {
2232 if (type != String16("attr")) {
2233 return 0;
2234 }
2235 t = p->getTypes().valueFor(String16(kAttrPrivateType));
2236 if (t == NULL) return 0;
2237 c = t->getConfigs().valueFor(name);
2238 if (c == NULL) return 0;
2239 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002240 int32_t ei = c->getEntryIndex();
2241 if (ei < 0) return 0;
2242
2243 return ResourceIdCache::store(package, type, name, onlyPublic,
2244 getResId(p, t, ei));
2245}
2246
2247uint32_t ResourceTable::getResId(const String16& ref,
2248 const String16* defType,
2249 const String16* defPackage,
2250 const char** outErrorMsg,
2251 bool onlyPublic) const
2252{
2253 String16 package, type, name;
2254 bool refOnlyPublic = true;
2255 if (!ResTable::expandResourceRef(
2256 ref.string(), ref.size(), &package, &type, &name,
2257 defType, defPackage ? defPackage:&mAssetsPackage,
2258 outErrorMsg, &refOnlyPublic)) {
Andreas Gampe2412f842014-09-30 20:55:57 -07002259 if (kIsDebug) {
2260 printf("Expanding resource: ref=%s\n", String8(ref).string());
2261 printf("Expanding resource: defType=%s\n",
2262 defType ? String8(*defType).string() : "NULL");
2263 printf("Expanding resource: defPackage=%s\n",
2264 defPackage ? String8(*defPackage).string() : "NULL");
2265 printf("Expanding resource: ref=%s\n", String8(ref).string());
2266 printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n",
2267 String8(package).string(), String8(type).string(),
2268 String8(name).string());
2269 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002270 return 0;
2271 }
2272 uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic);
Andreas Gampe2412f842014-09-30 20:55:57 -07002273 if (kIsDebug) {
2274 printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
2275 String8(package).string(), String8(type).string(),
2276 String8(name).string(), res);
2277 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002278 if (res == 0) {
2279 if (outErrorMsg)
2280 *outErrorMsg = "No resource found that matches the given name";
2281 }
2282 return res;
2283}
2284
2285bool ResourceTable::isValidResourceName(const String16& s)
2286{
2287 const char16_t* p = s.string();
2288 bool first = true;
2289 while (*p) {
2290 if ((*p >= 'a' && *p <= 'z')
2291 || (*p >= 'A' && *p <= 'Z')
2292 || *p == '_'
2293 || (!first && *p >= '0' && *p <= '9')) {
2294 first = false;
2295 p++;
2296 continue;
2297 }
2298 return false;
2299 }
2300 return true;
2301}
2302
2303bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
2304 const String16& str,
2305 bool preserveSpaces, bool coerceType,
2306 uint32_t attrID,
2307 const Vector<StringPool::entry_style_span>* style,
2308 String16* outStr, void* accessorCookie,
2309 uint32_t attrType, const String8* configTypeName,
2310 const ConfigDescription* config)
2311{
2312 String16 finalStr;
2313
2314 bool res = true;
2315 if (style == NULL || style->size() == 0) {
2316 // Text is not styled so it can be any type... let's figure it out.
2317 res = mAssets->getIncludedResources()
2318 .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces,
2319 coerceType, attrID, NULL, &mAssetsPackage, this,
2320 accessorCookie, attrType);
2321 } else {
2322 // Styled text can only be a string, and while collecting the style
2323 // information we have already processed that string!
2324 outValue->size = sizeof(Res_value);
2325 outValue->res0 = 0;
2326 outValue->dataType = outValue->TYPE_STRING;
2327 outValue->data = 0;
2328 finalStr = str;
2329 }
2330
2331 if (!res) {
2332 return false;
2333 }
2334
2335 if (outValue->dataType == outValue->TYPE_STRING) {
2336 // Should do better merging styles.
2337 if (pool) {
2338 String8 configStr;
2339 if (config != NULL) {
2340 configStr = config->toString();
2341 } else {
2342 configStr = "(null)";
2343 }
Andreas Gampe2412f842014-09-30 20:55:57 -07002344 if (kIsDebug) {
2345 printf("Adding to pool string style #%zu config %s: %s\n",
2346 style != NULL ? style->size() : 0U,
2347 configStr.string(), String8(finalStr).string());
2348 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002349 if (style != NULL && style->size() > 0) {
2350 outValue->data = pool->add(finalStr, *style, configTypeName, config);
2351 } else {
2352 outValue->data = pool->add(finalStr, true, configTypeName, config);
2353 }
2354 } else {
2355 // Caller will fill this in later.
2356 outValue->data = 0;
2357 }
2358
2359 if (outStr) {
2360 *outStr = finalStr;
2361 }
2362
2363 }
2364
2365 return true;
2366}
2367
2368uint32_t ResourceTable::getCustomResource(
2369 const String16& package, const String16& type, const String16& name) const
2370{
2371 //printf("getCustomResource: %s %s %s\n", String8(package).string(),
2372 // String8(type).string(), String8(name).string());
2373 sp<Package> p = mPackages.valueFor(package);
2374 if (p == NULL) return 0;
2375 sp<Type> t = p->getTypes().valueFor(type);
2376 if (t == NULL) return 0;
2377 sp<ConfigList> c = t->getConfigs().valueFor(name);
Adam Lesinski9b624c12014-11-19 17:49:26 -08002378 if (c == NULL) {
2379 if (type != String16("attr")) {
2380 return 0;
2381 }
2382 t = p->getTypes().valueFor(String16(kAttrPrivateType));
2383 if (t == NULL) return 0;
2384 c = t->getConfigs().valueFor(name);
2385 if (c == NULL) return 0;
2386 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002387 int32_t ei = c->getEntryIndex();
2388 if (ei < 0) return 0;
2389 return getResId(p, t, ei);
2390}
2391
2392uint32_t ResourceTable::getCustomResourceWithCreation(
2393 const String16& package, const String16& type, const String16& name,
2394 const bool createIfNotFound)
2395{
2396 uint32_t resId = getCustomResource(package, type, name);
2397 if (resId != 0 || !createIfNotFound) {
2398 return resId;
2399 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002400
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002401 if (mAssetsPackage != package) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002402 mCurrentXmlPos.error("creating resource for external package %s: %s/%s.",
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002403 String8(package).string(), String8(type).string(), String8(name).string());
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002404 if (package == String16("android")) {
2405 mCurrentXmlPos.printf("did you mean to use @+id instead of @+android:id?");
2406 }
2407 return 0;
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002408 }
2409
2410 String16 value("false");
Adam Lesinski282e1812014-01-23 18:17:42 -08002411 status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
2412 if (status == NO_ERROR) {
2413 resId = getResId(package, type, name);
2414 return resId;
2415 }
2416 return 0;
2417}
2418
2419uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const
2420{
2421 return origPackage;
2422}
2423
2424bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType)
2425{
2426 //printf("getAttributeType #%08x\n", attrID);
2427 Res_value value;
2428 if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) {
2429 //printf("getAttributeType #%08x (%s): #%08x\n", attrID,
2430 // String8(getEntry(attrID)->getName()).string(), value.data);
2431 *outType = value.data;
2432 return true;
2433 }
2434 return false;
2435}
2436
2437bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin)
2438{
2439 //printf("getAttributeMin #%08x\n", attrID);
2440 Res_value value;
2441 if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) {
2442 *outMin = value.data;
2443 return true;
2444 }
2445 return false;
2446}
2447
2448bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax)
2449{
2450 //printf("getAttributeMax #%08x\n", attrID);
2451 Res_value value;
2452 if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) {
2453 *outMax = value.data;
2454 return true;
2455 }
2456 return false;
2457}
2458
2459uint32_t ResourceTable::getAttributeL10N(uint32_t attrID)
2460{
2461 //printf("getAttributeL10N #%08x\n", attrID);
2462 Res_value value;
2463 if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) {
2464 return value.data;
2465 }
2466 return ResTable_map::L10N_NOT_REQUIRED;
2467}
2468
2469bool ResourceTable::getLocalizationSetting()
2470{
2471 return mBundle->getRequireLocalization();
2472}
2473
2474void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...)
2475{
2476 if (accessorCookie != NULL && fmt != NULL) {
2477 AccessorCookie* ac = (AccessorCookie*)accessorCookie;
2478 int retval=0;
2479 char buf[1024];
2480 va_list ap;
2481 va_start(ap, fmt);
2482 retval = vsnprintf(buf, sizeof(buf), fmt, ap);
2483 va_end(ap);
2484 ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n",
2485 buf, ac->attr.string(), ac->value.string());
2486 }
2487}
2488
2489bool ResourceTable::getAttributeKeys(
2490 uint32_t attrID, Vector<String16>* outKeys)
2491{
2492 sp<const Entry> e = getEntry(attrID);
2493 if (e != NULL) {
2494 const size_t N = e->getBag().size();
2495 for (size_t i=0; i<N; i++) {
2496 const String16& key = e->getBag().keyAt(i);
2497 if (key.size() > 0 && key.string()[0] != '^') {
2498 outKeys->add(key);
2499 }
2500 }
2501 return true;
2502 }
2503 return false;
2504}
2505
2506bool ResourceTable::getAttributeEnum(
2507 uint32_t attrID, const char16_t* name, size_t nameLen,
2508 Res_value* outValue)
2509{
2510 //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string());
2511 String16 nameStr(name, nameLen);
2512 sp<const Entry> e = getEntry(attrID);
2513 if (e != NULL) {
2514 const size_t N = e->getBag().size();
2515 for (size_t i=0; i<N; i++) {
2516 //printf("Comparing %s to %s\n", String8(name, nameLen).string(),
2517 // String8(e->getBag().keyAt(i)).string());
2518 if (e->getBag().keyAt(i) == nameStr) {
2519 return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue);
2520 }
2521 }
2522 }
2523 return false;
2524}
2525
2526bool ResourceTable::getAttributeFlags(
2527 uint32_t attrID, const char16_t* name, size_t nameLen,
2528 Res_value* outValue)
2529{
2530 outValue->dataType = Res_value::TYPE_INT_HEX;
2531 outValue->data = 0;
2532
2533 //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string());
2534 String16 nameStr(name, nameLen);
2535 sp<const Entry> e = getEntry(attrID);
2536 if (e != NULL) {
2537 const size_t N = e->getBag().size();
2538
2539 const char16_t* end = name + nameLen;
2540 const char16_t* pos = name;
2541 while (pos < end) {
2542 const char16_t* start = pos;
2543 while (pos < end && *pos != '|') {
2544 pos++;
2545 }
2546
2547 String16 nameStr(start, pos-start);
2548 size_t i;
2549 for (i=0; i<N; i++) {
2550 //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(),
2551 // String8(e->getBag().keyAt(i)).string());
2552 if (e->getBag().keyAt(i) == nameStr) {
2553 Res_value val;
2554 bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val);
2555 if (!got) {
2556 return false;
2557 }
2558 //printf("Got value: 0x%08x\n", val.data);
2559 outValue->data |= val.data;
2560 break;
2561 }
2562 }
2563
2564 if (i >= N) {
2565 // Didn't find this flag identifier.
2566 return false;
2567 }
2568 pos++;
2569 }
2570
2571 return true;
2572 }
2573 return false;
2574}
2575
2576status_t ResourceTable::assignResourceIds()
2577{
2578 const size_t N = mOrderedPackages.size();
2579 size_t pi;
2580 status_t firstError = NO_ERROR;
2581
2582 // First generate all bag attributes and assign indices.
2583 for (pi=0; pi<N; pi++) {
2584 sp<Package> p = mOrderedPackages.itemAt(pi);
2585 if (p == NULL || p->getTypes().size() == 0) {
2586 // Empty, skip!
2587 continue;
2588 }
2589
Adam Lesinski9b624c12014-11-19 17:49:26 -08002590 if (mPackageType == System) {
2591 p->movePrivateAttrs();
2592 }
2593
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002594 // This has no sense for packages being built as AppFeature (aka with a non-zero offset).
Adam Lesinski282e1812014-01-23 18:17:42 -08002595 status_t err = p->applyPublicTypeOrder();
2596 if (err != NO_ERROR && firstError == NO_ERROR) {
2597 firstError = err;
2598 }
2599
2600 // Generate attributes...
2601 const size_t N = p->getOrderedTypes().size();
2602 size_t ti;
2603 for (ti=0; ti<N; ti++) {
2604 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2605 if (t == NULL) {
2606 continue;
2607 }
2608 const size_t N = t->getOrderedConfigs().size();
2609 for (size_t ci=0; ci<N; ci++) {
2610 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2611 if (c == NULL) {
2612 continue;
2613 }
2614 const size_t N = c->getEntries().size();
2615 for (size_t ei=0; ei<N; ei++) {
2616 sp<Entry> e = c->getEntries().valueAt(ei);
2617 if (e == NULL) {
2618 continue;
2619 }
2620 status_t err = e->generateAttributes(this, p->getName());
2621 if (err != NO_ERROR && firstError == NO_ERROR) {
2622 firstError = err;
2623 }
2624 }
2625 }
2626 }
2627
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002628 uint32_t typeIdOffset = 0;
2629 if (mPackageType == AppFeature && p->getName() == mAssetsPackage) {
2630 typeIdOffset = mTypeIdOffset;
2631 }
2632
Adam Lesinski282e1812014-01-23 18:17:42 -08002633 const SourcePos unknown(String8("????"), 0);
2634 sp<Type> attr = p->getType(String16("attr"), unknown);
2635
Adam Lesinski4d219da2016-08-03 15:40:19 -07002636 // Force creation of ID if we are building feature splits.
2637 // Auto-generated ID resources won't apply the type ID offset correctly unless
2638 // the offset is applied here first.
2639 // b/30607637
2640 if (mPackageType == AppFeature && p->getName() == mAssetsPackage) {
2641 sp<Type> id = p->getType(String16("id"), unknown);
2642 }
2643
Adam Lesinski282e1812014-01-23 18:17:42 -08002644 // Assign indices...
Adam Lesinski43a0df02014-08-18 17:14:57 -07002645 const size_t typeCount = p->getOrderedTypes().size();
2646 for (size_t ti = 0; ti < typeCount; ti++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002647 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2648 if (t == NULL) {
2649 continue;
2650 }
Adam Lesinski43a0df02014-08-18 17:14:57 -07002651
Adam Lesinski282e1812014-01-23 18:17:42 -08002652 err = t->applyPublicEntryOrder();
2653 if (err != NO_ERROR && firstError == NO_ERROR) {
2654 firstError = err;
2655 }
2656
2657 const size_t N = t->getOrderedConfigs().size();
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002658 t->setIndex(ti + 1 + typeIdOffset);
Adam Lesinski282e1812014-01-23 18:17:42 -08002659
2660 LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
2661 "First type is not attr!");
2662
2663 for (size_t ei=0; ei<N; ei++) {
2664 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
2665 if (c == NULL) {
2666 continue;
2667 }
2668 c->setEntryIndex(ei);
2669 }
2670 }
2671
Adam Lesinski9b624c12014-11-19 17:49:26 -08002672
Adam Lesinski282e1812014-01-23 18:17:42 -08002673 // Assign resource IDs to keys in bags...
Adam Lesinski43a0df02014-08-18 17:14:57 -07002674 for (size_t ti = 0; ti < typeCount; ti++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002675 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2676 if (t == NULL) {
2677 continue;
2678 }
Adam Lesinski9b624c12014-11-19 17:49:26 -08002679
Adam Lesinski282e1812014-01-23 18:17:42 -08002680 const size_t N = t->getOrderedConfigs().size();
2681 for (size_t ci=0; ci<N; ci++) {
2682 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
Adam Lesinski9b624c12014-11-19 17:49:26 -08002683 if (c == NULL) {
2684 continue;
2685 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002686 //printf("Ordered config #%d: %p\n", ci, c.get());
2687 const size_t N = c->getEntries().size();
2688 for (size_t ei=0; ei<N; ei++) {
2689 sp<Entry> e = c->getEntries().valueAt(ei);
2690 if (e == NULL) {
2691 continue;
2692 }
2693 status_t err = e->assignResourceIds(this, p->getName());
2694 if (err != NO_ERROR && firstError == NO_ERROR) {
2695 firstError = err;
2696 }
2697 }
2698 }
2699 }
2700 }
2701 return firstError;
2702}
2703
Adrian Roos58922482015-06-01 17:59:41 -07002704status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols,
2705 bool skipSymbolsWithoutDefaultLocalization) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002706 const size_t N = mOrderedPackages.size();
Adrian Roos58922482015-06-01 17:59:41 -07002707 const String8 defaultLocale;
2708 const String16 stringType("string");
Adam Lesinski282e1812014-01-23 18:17:42 -08002709 size_t pi;
2710
2711 for (pi=0; pi<N; pi++) {
2712 sp<Package> p = mOrderedPackages.itemAt(pi);
2713 if (p->getTypes().size() == 0) {
2714 // Empty, skip!
2715 continue;
2716 }
2717
2718 const size_t N = p->getOrderedTypes().size();
2719 size_t ti;
2720
2721 for (ti=0; ti<N; ti++) {
2722 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2723 if (t == NULL) {
2724 continue;
2725 }
Adam Lesinski9b624c12014-11-19 17:49:26 -08002726
Adam Lesinski282e1812014-01-23 18:17:42 -08002727 const size_t N = t->getOrderedConfigs().size();
Adam Lesinski9b624c12014-11-19 17:49:26 -08002728 sp<AaptSymbols> typeSymbols;
2729 if (t->getName() == String16(kAttrPrivateType)) {
2730 typeSymbols = outSymbols->addNestedSymbol(String8("attr"), t->getPos());
2731 } else {
2732 typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
2733 }
2734
Adam Lesinski3fb8c9b2014-09-09 16:05:10 -07002735 if (typeSymbols == NULL) {
2736 return UNKNOWN_ERROR;
2737 }
2738
Adam Lesinski282e1812014-01-23 18:17:42 -08002739 for (size_t ci=0; ci<N; ci++) {
2740 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2741 if (c == NULL) {
2742 continue;
2743 }
2744 uint32_t rid = getResId(p, t, ci);
2745 if (rid == 0) {
2746 return UNKNOWN_ERROR;
2747 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002748 if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) {
Adrian Roos58922482015-06-01 17:59:41 -07002749
2750 if (skipSymbolsWithoutDefaultLocalization &&
2751 t->getName() == stringType) {
2752
2753 // Don't generate symbols for strings without a default localization.
2754 if (mHasDefaultLocalization.find(c->getName())
2755 == mHasDefaultLocalization.end()) {
2756 // printf("Skip symbol [%08x] %s\n", rid,
2757 // String8(c->getName()).string());
2758 continue;
2759 }
2760 }
2761
Adam Lesinski282e1812014-01-23 18:17:42 -08002762 typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
2763
2764 String16 comment(c->getComment());
2765 typeSymbols->appendComment(String8(c->getName()), comment, c->getPos());
Adam Lesinski8ff15b42013-10-07 16:54:01 -07002766 //printf("Type symbol [%08x] %s comment: %s\n", rid,
2767 // String8(c->getName()).string(), String8(comment).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002768 comment = c->getTypeComment();
2769 typeSymbols->appendTypeComment(String8(c->getName()), comment);
Adam Lesinski282e1812014-01-23 18:17:42 -08002770 }
2771 }
2772 }
2773 }
2774 return NO_ERROR;
2775}
2776
2777
2778void
Adam Lesinskia01a9372014-03-20 18:04:57 -07002779ResourceTable::addLocalization(const String16& name, const String8& locale, const SourcePos& src)
Adam Lesinski282e1812014-01-23 18:17:42 -08002780{
Adam Lesinskia01a9372014-03-20 18:04:57 -07002781 mLocalizations[name][locale] = src;
Adam Lesinski282e1812014-01-23 18:17:42 -08002782}
2783
Adrian Roos58922482015-06-01 17:59:41 -07002784void
2785ResourceTable::addDefaultLocalization(const String16& name)
2786{
2787 mHasDefaultLocalization.insert(name);
2788}
2789
Adam Lesinski282e1812014-01-23 18:17:42 -08002790
2791/*!
2792 * Flag various sorts of localization problems. '+' indicates checks already implemented;
2793 * '-' indicates checks that will be implemented in the future.
2794 *
2795 * + A localized string for which no default-locale version exists => warning
2796 * + A string for which no version in an explicitly-requested locale exists => warning
2797 * + A localized translation of an translateable="false" string => warning
2798 * - A localized string not provided in every locale used by the table
2799 */
2800status_t
2801ResourceTable::validateLocalizations(void)
2802{
2803 status_t err = NO_ERROR;
2804 const String8 defaultLocale;
2805
2806 // For all strings...
Dan Albert030f5362015-03-04 13:54:20 -08002807 for (const auto& nameIter : mLocalizations) {
2808 const std::map<String8, SourcePos>& configSrcMap = nameIter.second;
Adam Lesinski282e1812014-01-23 18:17:42 -08002809
2810 // Look for strings with no default localization
Adam Lesinskia01a9372014-03-20 18:04:57 -07002811 if (configSrcMap.count(defaultLocale) == 0) {
2812 SourcePos().warning("string '%s' has no default translation.",
Dan Albert030f5362015-03-04 13:54:20 -08002813 String8(nameIter.first).string());
Adam Lesinskia01a9372014-03-20 18:04:57 -07002814 if (mBundle->getVerbose()) {
Dan Albert030f5362015-03-04 13:54:20 -08002815 for (const auto& locale : configSrcMap) {
2816 locale.second.printf("locale %s found", locale.first.string());
Adam Lesinskia01a9372014-03-20 18:04:57 -07002817 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002818 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002819 // !!! TODO: throw an error here in some circumstances
2820 }
2821
2822 // Check that all requested localizations are present for this string
Adam Lesinskifab50872014-04-16 14:40:42 -07002823 if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) {
2824 const char* allConfigs = mBundle->getConfigurations().string();
Adam Lesinski282e1812014-01-23 18:17:42 -08002825 const char* start = allConfigs;
2826 const char* comma;
Dan Albert030f5362015-03-04 13:54:20 -08002827
2828 std::set<String8> missingConfigs;
Adam Lesinskia01a9372014-03-20 18:04:57 -07002829 AaptLocaleValue locale;
Adam Lesinski282e1812014-01-23 18:17:42 -08002830 do {
2831 String8 config;
2832 comma = strchr(start, ',');
2833 if (comma != NULL) {
2834 config.setTo(start, comma - start);
2835 start = comma + 1;
2836 } else {
2837 config.setTo(start);
2838 }
2839
Adam Lesinskia01a9372014-03-20 18:04:57 -07002840 if (!locale.initFromFilterString(config)) {
2841 continue;
2842 }
2843
Anton Krumina2ef5c02014-03-12 14:46:44 -07002844 // don't bother with the pseudolocale "en_XA" or "ar_XB"
2845 if (config != "en_XA" && config != "ar_XB") {
Adam Lesinskia01a9372014-03-20 18:04:57 -07002846 if (configSrcMap.find(config) == configSrcMap.end()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002847 // okay, no specific localization found. it's possible that we are
2848 // requiring a specific regional localization [e.g. de_DE] but there is an
2849 // available string in the generic language localization [e.g. de];
2850 // consider that string to have fulfilled the localization requirement.
2851 String8 region(config.string(), 2);
Adam Lesinskia01a9372014-03-20 18:04:57 -07002852 if (configSrcMap.find(region) == configSrcMap.end() &&
2853 configSrcMap.count(defaultLocale) == 0) {
2854 missingConfigs.insert(config);
Adam Lesinski282e1812014-01-23 18:17:42 -08002855 }
2856 }
2857 }
Adam Lesinskia01a9372014-03-20 18:04:57 -07002858 } while (comma != NULL);
2859
2860 if (!missingConfigs.empty()) {
2861 String8 configStr;
Dan Albert030f5362015-03-04 13:54:20 -08002862 for (const auto& iter : missingConfigs) {
2863 configStr.appendFormat(" %s", iter.string());
Adam Lesinskia01a9372014-03-20 18:04:57 -07002864 }
2865 SourcePos().warning("string '%s' is missing %u required localizations:%s",
Dan Albert030f5362015-03-04 13:54:20 -08002866 String8(nameIter.first).string(),
Adam Lesinskia01a9372014-03-20 18:04:57 -07002867 (unsigned int)missingConfigs.size(),
2868 configStr.string());
2869 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002870 }
2871 }
2872
2873 return err;
2874}
2875
Adam Lesinski27f69f42014-08-21 13:19:12 -07002876status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
2877 const sp<AaptFile>& dest,
2878 const bool isBase)
Adam Lesinski282e1812014-01-23 18:17:42 -08002879{
Adam Lesinski282e1812014-01-23 18:17:42 -08002880 const ConfigDescription nullConfig;
2881
2882 const size_t N = mOrderedPackages.size();
2883 size_t pi;
2884
2885 const static String16 mipmap16("mipmap");
2886
2887 bool useUTF8 = !bundle->getUTF16StringsOption();
2888
Adam Lesinskide898ff2014-01-29 18:20:45 -08002889 // The libraries this table references.
2890 Vector<sp<Package> > libraryPackages;
Adam Lesinski6022deb2014-08-20 14:59:19 -07002891 const ResTable& table = mAssets->getIncludedResources();
2892 const size_t basePackageCount = table.getBasePackageCount();
2893 for (size_t i = 0; i < basePackageCount; i++) {
2894 size_t packageId = table.getBasePackageId(i);
2895 String16 packageName(table.getBasePackageName(i));
2896 if (packageId > 0x01 && packageId != 0x7f &&
2897 packageName != String16("android")) {
2898 libraryPackages.add(sp<Package>(new Package(packageName, packageId)));
2899 }
2900 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08002901
Adam Lesinski282e1812014-01-23 18:17:42 -08002902 // Iterate through all data, collecting all values (strings,
2903 // references, etc).
2904 StringPool valueStrings(useUTF8);
2905 Vector<sp<Entry> > allEntries;
2906 for (pi=0; pi<N; pi++) {
2907 sp<Package> p = mOrderedPackages.itemAt(pi);
2908 if (p->getTypes().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002909 continue;
2910 }
2911
2912 StringPool typeStrings(useUTF8);
2913 StringPool keyStrings(useUTF8);
2914
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002915 ssize_t stringsAdded = 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08002916 const size_t N = p->getOrderedTypes().size();
2917 for (size_t ti=0; ti<N; ti++) {
2918 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2919 if (t == NULL) {
2920 typeStrings.add(String16("<empty>"), false);
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002921 stringsAdded++;
Adam Lesinski282e1812014-01-23 18:17:42 -08002922 continue;
2923 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002924
2925 while (stringsAdded < t->getIndex() - 1) {
2926 typeStrings.add(String16("<empty>"), false);
2927 stringsAdded++;
2928 }
2929
Adam Lesinski282e1812014-01-23 18:17:42 -08002930 const String16 typeName(t->getName());
2931 typeStrings.add(typeName, false);
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002932 stringsAdded++;
Adam Lesinski282e1812014-01-23 18:17:42 -08002933
2934 // This is a hack to tweak the sorting order of the final strings,
2935 // to put stuff that is generally not language-specific first.
2936 String8 configTypeName(typeName);
2937 if (configTypeName == "drawable" || configTypeName == "layout"
2938 || configTypeName == "color" || configTypeName == "anim"
2939 || configTypeName == "interpolator" || configTypeName == "animator"
2940 || configTypeName == "xml" || configTypeName == "menu"
2941 || configTypeName == "mipmap" || configTypeName == "raw") {
2942 configTypeName = "1complex";
2943 } else {
2944 configTypeName = "2value";
2945 }
2946
Adam Lesinski27f69f42014-08-21 13:19:12 -07002947 // mipmaps don't get filtered, so they will
2948 // allways end up in the base. Make sure they
2949 // don't end up in a split.
2950 if (typeName == mipmap16 && !isBase) {
2951 continue;
2952 }
2953
Adam Lesinski282e1812014-01-23 18:17:42 -08002954 const bool filterable = (typeName != mipmap16);
2955
2956 const size_t N = t->getOrderedConfigs().size();
2957 for (size_t ci=0; ci<N; ci++) {
2958 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2959 if (c == NULL) {
2960 continue;
2961 }
2962 const size_t N = c->getEntries().size();
2963 for (size_t ei=0; ei<N; ei++) {
2964 ConfigDescription config = c->getEntries().keyAt(ei);
Adam Lesinskifab50872014-04-16 14:40:42 -07002965 if (filterable && !filter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002966 continue;
2967 }
2968 sp<Entry> e = c->getEntries().valueAt(ei);
2969 if (e == NULL) {
2970 continue;
2971 }
2972 e->setNameIndex(keyStrings.add(e->getName(), true));
2973
2974 // If this entry has no values for other configs,
2975 // and is the default config, then it is special. Otherwise
2976 // we want to add it with the config info.
2977 ConfigDescription* valueConfig = NULL;
2978 if (N != 1 || config == nullConfig) {
2979 valueConfig = &config;
2980 }
2981
2982 status_t err = e->prepareFlatten(&valueStrings, this,
2983 &configTypeName, &config);
2984 if (err != NO_ERROR) {
2985 return err;
2986 }
2987 allEntries.add(e);
2988 }
2989 }
2990 }
2991
2992 p->setTypeStrings(typeStrings.createStringBlock());
2993 p->setKeyStrings(keyStrings.createStringBlock());
2994 }
2995
2996 if (bundle->getOutputAPKFile() != NULL) {
2997 // Now we want to sort the value strings for better locality. This will
2998 // cause the positions of the strings to change, so we need to go back
2999 // through out resource entries and update them accordingly. Only need
3000 // to do this if actually writing the output file.
3001 valueStrings.sortByConfig();
3002 for (pi=0; pi<allEntries.size(); pi++) {
3003 allEntries[pi]->remapStringValue(&valueStrings);
3004 }
3005 }
3006
3007 ssize_t strAmt = 0;
Adam Lesinskide898ff2014-01-29 18:20:45 -08003008
Adam Lesinski282e1812014-01-23 18:17:42 -08003009 // Now build the array of package chunks.
3010 Vector<sp<AaptFile> > flatPackages;
3011 for (pi=0; pi<N; pi++) {
3012 sp<Package> p = mOrderedPackages.itemAt(pi);
3013 if (p->getTypes().size() == 0) {
3014 // Empty, skip!
3015 continue;
3016 }
3017
3018 const size_t N = p->getTypeStrings().size();
3019
3020 const size_t baseSize = sizeof(ResTable_package);
3021
3022 // Start the package data.
3023 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
3024 ResTable_package* header = (ResTable_package*)data->editData(baseSize);
3025 if (header == NULL) {
3026 fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
3027 return NO_MEMORY;
3028 }
3029 memset(header, 0, sizeof(*header));
3030 header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
3031 header->header.headerSize = htods(sizeof(*header));
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003032 header->id = htodl(static_cast<uint32_t>(p->getAssignedId()));
Adam Lesinski282e1812014-01-23 18:17:42 -08003033 strcpy16_htod(header->name, p->getName().string());
3034
3035 // Write the string blocks.
3036 const size_t typeStringsStart = data->getSize();
3037 sp<AaptFile> strFile = p->getTypeStringsData();
3038 ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
Andreas Gampe2412f842014-09-30 20:55:57 -07003039 if (kPrintStringMetrics) {
Pirama Arumuga Nainardc36bb62018-05-11 15:52:49 -07003040 fprintf(stderr, "**** type strings: %zd\n", amt);
Andreas Gampe2412f842014-09-30 20:55:57 -07003041 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003042 strAmt += amt;
3043 if (amt < 0) {
3044 return amt;
3045 }
3046 const size_t keyStringsStart = data->getSize();
3047 strFile = p->getKeyStringsData();
3048 amt = data->writeData(strFile->getData(), strFile->getSize());
Andreas Gampe2412f842014-09-30 20:55:57 -07003049 if (kPrintStringMetrics) {
Pirama Arumuga Nainardc36bb62018-05-11 15:52:49 -07003050 fprintf(stderr, "**** key strings: %zd\n", amt);
Andreas Gampe2412f842014-09-30 20:55:57 -07003051 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003052 strAmt += amt;
3053 if (amt < 0) {
3054 return amt;
3055 }
3056
Adam Lesinski27f69f42014-08-21 13:19:12 -07003057 if (isBase) {
3058 status_t err = flattenLibraryTable(data, libraryPackages);
3059 if (err != NO_ERROR) {
3060 fprintf(stderr, "ERROR: failed to write library table\n");
3061 return err;
3062 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003063 }
3064
Adam Lesinski282e1812014-01-23 18:17:42 -08003065 // Build the type chunks inside of this package.
3066 for (size_t ti=0; ti<N; ti++) {
3067 // Retrieve them in the same order as the type string block.
3068 size_t len;
3069 String16 typeName(p->getTypeStrings().stringAt(ti, &len));
3070 sp<Type> t = p->getTypes().valueFor(typeName);
3071 LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
3072 "Type name %s not found",
3073 String8(typeName).string());
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003074 if (t == NULL) {
3075 continue;
3076 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003077 const bool filterable = (typeName != mipmap16);
Adam Lesinski27f69f42014-08-21 13:19:12 -07003078 const bool skipEntireType = (typeName == mipmap16 && !isBase);
Adam Lesinski282e1812014-01-23 18:17:42 -08003079
3080 const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
3081
3082 // Until a non-NO_ENTRY value has been written for a resource,
3083 // that resource is invalid; validResources[i] represents
3084 // the item at t->getOrderedConfigs().itemAt(i).
3085 Vector<bool> validResources;
3086 validResources.insertAt(false, 0, N);
3087
3088 // First write the typeSpec chunk, containing information about
3089 // each resource entry in this type.
3090 {
3091 const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
3092 const size_t typeSpecStart = data->getSize();
3093 ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
3094 (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
3095 if (tsHeader == NULL) {
3096 fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n");
3097 return NO_MEMORY;
3098 }
3099 memset(tsHeader, 0, sizeof(*tsHeader));
3100 tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
3101 tsHeader->header.headerSize = htods(sizeof(*tsHeader));
3102 tsHeader->header.size = htodl(typeSpecSize);
3103 tsHeader->id = ti+1;
3104 tsHeader->entryCount = htodl(N);
3105
3106 uint32_t* typeSpecFlags = (uint32_t*)
3107 (((uint8_t*)data->editData())
3108 + typeSpecStart + sizeof(ResTable_typeSpec));
3109 memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
3110
3111 for (size_t ei=0; ei<N; ei++) {
3112 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
Adam Lesinski9b624c12014-11-19 17:49:26 -08003113 if (cl == NULL) {
3114 continue;
3115 }
3116
Adam Lesinski282e1812014-01-23 18:17:42 -08003117 if (cl->getPublic()) {
3118 typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
3119 }
Adam Lesinski27f69f42014-08-21 13:19:12 -07003120
3121 if (skipEntireType) {
3122 continue;
3123 }
3124
Adam Lesinski282e1812014-01-23 18:17:42 -08003125 const size_t CN = cl->getEntries().size();
3126 for (size_t ci=0; ci<CN; ci++) {
Adam Lesinskifab50872014-04-16 14:40:42 -07003127 if (filterable && !filter->match(cl->getEntries().keyAt(ci))) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003128 continue;
3129 }
3130 for (size_t cj=ci+1; cj<CN; cj++) {
Adam Lesinskifab50872014-04-16 14:40:42 -07003131 if (filterable && !filter->match(cl->getEntries().keyAt(cj))) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003132 continue;
3133 }
3134 typeSpecFlags[ei] |= htodl(
3135 cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
3136 }
3137 }
3138 }
3139 }
3140
Adam Lesinski27f69f42014-08-21 13:19:12 -07003141 if (skipEntireType) {
3142 continue;
3143 }
3144
Adam Lesinski282e1812014-01-23 18:17:42 -08003145 // We need to write one type chunk for each configuration for
3146 // which we have entries in this type.
Adam Lesinskie97908d2014-12-05 11:06:21 -08003147 SortedVector<ConfigDescription> uniqueConfigs;
3148 if (t != NULL) {
3149 uniqueConfigs = t->getUniqueConfigs();
3150 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003151
3152 const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
3153
Adam Lesinskie97908d2014-12-05 11:06:21 -08003154 const size_t NC = uniqueConfigs.size();
Adam Lesinski282e1812014-01-23 18:17:42 -08003155 for (size_t ci=0; ci<NC; ci++) {
Adam Lesinski9b624c12014-11-19 17:49:26 -08003156 const ConfigDescription& config = uniqueConfigs[ci];
Adam Lesinski282e1812014-01-23 18:17:42 -08003157
Andreas Gampe2412f842014-09-30 20:55:57 -07003158 if (kIsDebug) {
3159 printf("Writing config %zu config: imsi:%d/%d lang:%c%c cnt:%c%c "
3160 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
3161 "sw%ddp w%ddp h%ddp layout:%d\n",
3162 ti + 1,
3163 config.mcc, config.mnc,
3164 config.language[0] ? config.language[0] : '-',
3165 config.language[1] ? config.language[1] : '-',
3166 config.country[0] ? config.country[0] : '-',
3167 config.country[1] ? config.country[1] : '-',
3168 config.orientation,
3169 config.uiMode,
3170 config.touchscreen,
3171 config.density,
3172 config.keyboard,
3173 config.inputFlags,
3174 config.navigation,
3175 config.screenWidth,
3176 config.screenHeight,
3177 config.smallestScreenWidthDp,
3178 config.screenWidthDp,
3179 config.screenHeightDp,
3180 config.screenLayout);
3181 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003182
Adam Lesinskifab50872014-04-16 14:40:42 -07003183 if (filterable && !filter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003184 continue;
3185 }
3186
3187 const size_t typeStart = data->getSize();
3188
3189 ResTable_type* tHeader = (ResTable_type*)
3190 (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
3191 if (tHeader == NULL) {
3192 fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
3193 return NO_MEMORY;
3194 }
3195
3196 memset(tHeader, 0, sizeof(*tHeader));
3197 tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
3198 tHeader->header.headerSize = htods(sizeof(*tHeader));
3199 tHeader->id = ti+1;
3200 tHeader->entryCount = htodl(N);
3201 tHeader->entriesStart = htodl(typeSize);
3202 tHeader->config = config;
Andreas Gampe2412f842014-09-30 20:55:57 -07003203 if (kIsDebug) {
3204 printf("Writing type %zu config: imsi:%d/%d lang:%c%c cnt:%c%c "
3205 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
3206 "sw%ddp w%ddp h%ddp layout:%d\n",
3207 ti + 1,
3208 tHeader->config.mcc, tHeader->config.mnc,
3209 tHeader->config.language[0] ? tHeader->config.language[0] : '-',
3210 tHeader->config.language[1] ? tHeader->config.language[1] : '-',
3211 tHeader->config.country[0] ? tHeader->config.country[0] : '-',
3212 tHeader->config.country[1] ? tHeader->config.country[1] : '-',
3213 tHeader->config.orientation,
3214 tHeader->config.uiMode,
3215 tHeader->config.touchscreen,
3216 tHeader->config.density,
3217 tHeader->config.keyboard,
3218 tHeader->config.inputFlags,
3219 tHeader->config.navigation,
3220 tHeader->config.screenWidth,
3221 tHeader->config.screenHeight,
3222 tHeader->config.smallestScreenWidthDp,
3223 tHeader->config.screenWidthDp,
3224 tHeader->config.screenHeightDp,
3225 tHeader->config.screenLayout);
3226 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003227 tHeader->config.swapHtoD();
3228
3229 // Build the entries inside of this type.
3230 for (size_t ei=0; ei<N; ei++) {
3231 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
Adam Lesinski9b624c12014-11-19 17:49:26 -08003232 sp<Entry> e = NULL;
3233 if (cl != NULL) {
3234 e = cl->getEntries().valueFor(config);
3235 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003236
3237 // Set the offset for this entry in its type.
3238 uint32_t* index = (uint32_t*)
3239 (((uint8_t*)data->editData())
3240 + typeStart + sizeof(ResTable_type));
3241 if (e != NULL) {
3242 index[ei] = htodl(data->getSize()-typeStart-typeSize);
3243
3244 // Create the entry.
3245 ssize_t amt = e->flatten(bundle, data, cl->getPublic());
3246 if (amt < 0) {
3247 return amt;
3248 }
3249 validResources.editItemAt(ei) = true;
3250 } else {
3251 index[ei] = htodl(ResTable_type::NO_ENTRY);
3252 }
3253 }
3254
3255 // Fill in the rest of the type information.
3256 tHeader = (ResTable_type*)
3257 (((uint8_t*)data->editData()) + typeStart);
3258 tHeader->header.size = htodl(data->getSize()-typeStart);
3259 }
3260
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003261 // If we're building splits, then each invocation of the flattening
3262 // step will have 'missing' entries. Don't warn/error for this case.
3263 if (bundle->getSplitConfigurations().isEmpty()) {
3264 bool missing_entry = false;
3265 const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
3266 "error" : "warning";
3267 for (size_t i = 0; i < N; ++i) {
3268 if (!validResources[i]) {
3269 sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
Adam Lesinski9b624c12014-11-19 17:49:26 -08003270 if (c != NULL) {
Colin Cross01f18562015-04-08 17:29:00 -07003271 fprintf(stderr, "%s: no entries written for %s/%s (0x%08zx)\n", log_prefix,
Adam Lesinski9b624c12014-11-19 17:49:26 -08003272 String8(typeName).string(), String8(c->getName()).string(),
3273 Res_MAKEID(p->getAssignedId() - 1, ti, i));
3274 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003275 missing_entry = true;
3276 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003277 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003278 if (bundle->getErrorOnMissingConfigEntry() && missing_entry) {
3279 fprintf(stderr, "Error: Missing entries, quit!\n");
3280 return NOT_ENOUGH_DATA;
3281 }
Ying Wangcd28bd32013-11-14 17:12:10 -08003282 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003283 }
3284
3285 // Fill in the rest of the package information.
3286 header = (ResTable_package*)data->editData();
3287 header->header.size = htodl(data->getSize());
3288 header->typeStrings = htodl(typeStringsStart);
3289 header->lastPublicType = htodl(p->getTypeStrings().size());
3290 header->keyStrings = htodl(keyStringsStart);
3291 header->lastPublicKey = htodl(p->getKeyStrings().size());
3292
3293 flatPackages.add(data);
3294 }
3295
3296 // And now write out the final chunks.
3297 const size_t dataStart = dest->getSize();
3298
3299 {
3300 // blah
3301 ResTable_header header;
3302 memset(&header, 0, sizeof(header));
3303 header.header.type = htods(RES_TABLE_TYPE);
3304 header.header.headerSize = htods(sizeof(header));
3305 header.packageCount = htodl(flatPackages.size());
3306 status_t err = dest->writeData(&header, sizeof(header));
3307 if (err != NO_ERROR) {
3308 fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
3309 return err;
3310 }
3311 }
3312
3313 ssize_t strStart = dest->getSize();
Adam Lesinskifab50872014-04-16 14:40:42 -07003314 status_t err = valueStrings.writeStringBlock(dest);
Adam Lesinski282e1812014-01-23 18:17:42 -08003315 if (err != NO_ERROR) {
3316 return err;
3317 }
3318
3319 ssize_t amt = (dest->getSize()-strStart);
3320 strAmt += amt;
Andreas Gampe2412f842014-09-30 20:55:57 -07003321 if (kPrintStringMetrics) {
Pirama Arumuga Nainardc36bb62018-05-11 15:52:49 -07003322 fprintf(stderr, "**** value strings: %zd\n", amt);
3323 fprintf(stderr, "**** total strings: %zd\n", amt);
Andreas Gampe2412f842014-09-30 20:55:57 -07003324 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003325
Adam Lesinski282e1812014-01-23 18:17:42 -08003326 for (pi=0; pi<flatPackages.size(); pi++) {
3327 err = dest->writeData(flatPackages[pi]->getData(),
3328 flatPackages[pi]->getSize());
3329 if (err != NO_ERROR) {
3330 fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n");
3331 return err;
3332 }
3333 }
3334
3335 ResTable_header* header = (ResTable_header*)
3336 (((uint8_t*)dest->getData()) + dataStart);
3337 header->header.size = htodl(dest->getSize() - dataStart);
3338
Andreas Gampe2412f842014-09-30 20:55:57 -07003339 if (kPrintStringMetrics) {
3340 fprintf(stderr, "**** total resource table size: %zu / %zu%% strings\n",
3341 dest->getSize(), (size_t)(strAmt*100)/dest->getSize());
3342 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003343
3344 return NO_ERROR;
3345}
3346
Adam Lesinskide898ff2014-01-29 18:20:45 -08003347status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) {
3348 // Write out the library table if necessary
3349 if (libs.size() > 0) {
Andreas Gampe87332a72014-10-01 22:03:58 -07003350 if (kIsDebug) {
3351 fprintf(stderr, "Writing library reference table\n");
3352 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003353
3354 const size_t libStart = dest->getSize();
3355 const size_t count = libs.size();
Adam Lesinski6022deb2014-08-20 14:59:19 -07003356 ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange(
3357 libStart, sizeof(ResTable_lib_header));
Adam Lesinskide898ff2014-01-29 18:20:45 -08003358
3359 memset(libHeader, 0, sizeof(*libHeader));
3360 libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE);
3361 libHeader->header.headerSize = htods(sizeof(*libHeader));
3362 libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count));
3363 libHeader->count = htodl(count);
3364
3365 // Write the library entries
3366 for (size_t i = 0; i < count; i++) {
3367 const size_t entryStart = dest->getSize();
3368 sp<Package> libPackage = libs[i];
Andreas Gampe87332a72014-10-01 22:03:58 -07003369 if (kIsDebug) {
3370 fprintf(stderr, " Entry %s -> 0x%02x\n",
Adam Lesinskide898ff2014-01-29 18:20:45 -08003371 String8(libPackage->getName()).string(),
Andreas Gampe87332a72014-10-01 22:03:58 -07003372 (uint8_t)libPackage->getAssignedId());
3373 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003374
Adam Lesinski6022deb2014-08-20 14:59:19 -07003375 ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange(
3376 entryStart, sizeof(ResTable_lib_entry));
Adam Lesinskide898ff2014-01-29 18:20:45 -08003377 memset(entry, 0, sizeof(*entry));
3378 entry->packageId = htodl(libPackage->getAssignedId());
3379 strcpy16_htod(entry->packageName, libPackage->getName().string());
3380 }
3381 }
3382 return NO_ERROR;
3383}
3384
Adam Lesinski282e1812014-01-23 18:17:42 -08003385void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp)
3386{
3387 fprintf(fp,
3388 "<!-- This file contains <public> resource definitions for all\n"
3389 " resources that were generated from the source data. -->\n"
3390 "\n"
3391 "<resources>\n");
3392
3393 writePublicDefinitions(package, fp, true);
3394 writePublicDefinitions(package, fp, false);
3395
3396 fprintf(fp,
3397 "\n"
3398 "</resources>\n");
3399}
3400
3401void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub)
3402{
3403 bool didHeader = false;
3404
3405 sp<Package> pkg = mPackages.valueFor(package);
3406 if (pkg != NULL) {
3407 const size_t NT = pkg->getOrderedTypes().size();
3408 for (size_t i=0; i<NT; i++) {
3409 sp<Type> t = pkg->getOrderedTypes().itemAt(i);
3410 if (t == NULL) {
3411 continue;
3412 }
3413
3414 bool didType = false;
3415
3416 const size_t NC = t->getOrderedConfigs().size();
3417 for (size_t j=0; j<NC; j++) {
3418 sp<ConfigList> c = t->getOrderedConfigs().itemAt(j);
3419 if (c == NULL) {
3420 continue;
3421 }
3422
3423 if (c->getPublic() != pub) {
3424 continue;
3425 }
3426
3427 if (!didType) {
3428 fprintf(fp, "\n");
3429 didType = true;
3430 }
3431 if (!didHeader) {
3432 if (pub) {
3433 fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n");
3434 fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n");
3435 } else {
3436 fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n");
3437 fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n");
3438 }
3439 didHeader = true;
3440 }
3441 if (!pub) {
3442 const size_t NE = c->getEntries().size();
3443 for (size_t k=0; k<NE; k++) {
3444 const SourcePos& pos = c->getEntries().valueAt(k)->getPos();
3445 if (pos.file != "") {
3446 fprintf(fp," <!-- Declared at %s:%d -->\n",
3447 pos.file.string(), pos.line);
3448 }
3449 }
3450 }
3451 fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n",
3452 String8(t->getName()).string(),
3453 String8(c->getName()).string(),
3454 getResId(pkg, t, c->getEntryIndex()));
3455 }
3456 }
3457 }
3458}
3459
3460ResourceTable::Item::Item(const SourcePos& _sourcePos,
3461 bool _isId,
3462 const String16& _value,
3463 const Vector<StringPool::entry_style_span>* _style,
3464 int32_t _format)
3465 : sourcePos(_sourcePos)
3466 , isId(_isId)
3467 , value(_value)
3468 , format(_format)
3469 , bagKeyId(0)
3470 , evaluating(false)
3471{
3472 if (_style) {
3473 style = *_style;
3474 }
3475}
3476
Adam Lesinski82a2dd82014-09-17 18:34:15 -07003477ResourceTable::Entry::Entry(const Entry& entry)
3478 : RefBase()
3479 , mName(entry.mName)
3480 , mParent(entry.mParent)
3481 , mType(entry.mType)
3482 , mItem(entry.mItem)
3483 , mItemFormat(entry.mItemFormat)
3484 , mBag(entry.mBag)
3485 , mNameIndex(entry.mNameIndex)
3486 , mParentId(entry.mParentId)
3487 , mPos(entry.mPos) {}
3488
Adam Lesinski978ab9d2014-09-24 19:02:52 -07003489ResourceTable::Entry& ResourceTable::Entry::operator=(const Entry& entry) {
3490 mName = entry.mName;
3491 mParent = entry.mParent;
3492 mType = entry.mType;
3493 mItem = entry.mItem;
3494 mItemFormat = entry.mItemFormat;
3495 mBag = entry.mBag;
3496 mNameIndex = entry.mNameIndex;
3497 mParentId = entry.mParentId;
3498 mPos = entry.mPos;
3499 return *this;
3500}
3501
Adam Lesinski282e1812014-01-23 18:17:42 -08003502status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
3503{
3504 if (mType == TYPE_BAG) {
3505 return NO_ERROR;
3506 }
3507 if (mType == TYPE_UNKNOWN) {
3508 mType = TYPE_BAG;
3509 return NO_ERROR;
3510 }
3511 sourcePos.error("Resource entry %s is already defined as a single item.\n"
3512 "%s:%d: Originally defined here.\n",
3513 String8(mName).string(),
3514 mItem.sourcePos.file.string(), mItem.sourcePos.line);
3515 return UNKNOWN_ERROR;
3516}
3517
3518status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
3519 const String16& value,
3520 const Vector<StringPool::entry_style_span>* style,
3521 int32_t format,
3522 const bool overwrite)
3523{
3524 Item item(sourcePos, false, value, style);
3525
3526 if (mType == TYPE_BAG) {
Adam Lesinski43a0df02014-08-18 17:14:57 -07003527 if (mBag.size() == 0) {
3528 sourcePos.error("Resource entry %s is already defined as a bag.",
3529 String8(mName).string());
3530 } else {
3531 const Item& item(mBag.valueAt(0));
3532 sourcePos.error("Resource entry %s is already defined as a bag.\n"
3533 "%s:%d: Originally defined here.\n",
3534 String8(mName).string(),
3535 item.sourcePos.file.string(), item.sourcePos.line);
3536 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003537 return UNKNOWN_ERROR;
3538 }
3539 if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
3540 sourcePos.error("Resource entry %s is already defined.\n"
3541 "%s:%d: Originally defined here.\n",
3542 String8(mName).string(),
3543 mItem.sourcePos.file.string(), mItem.sourcePos.line);
3544 return UNKNOWN_ERROR;
3545 }
3546
3547 mType = TYPE_ITEM;
3548 mItem = item;
3549 mItemFormat = format;
3550 return NO_ERROR;
3551}
3552
3553status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
3554 const String16& key, const String16& value,
3555 const Vector<StringPool::entry_style_span>* style,
3556 bool replace, bool isId, int32_t format)
3557{
3558 status_t err = makeItABag(sourcePos);
3559 if (err != NO_ERROR) {
3560 return err;
3561 }
3562
3563 Item item(sourcePos, isId, value, style, format);
3564
3565 // XXX NOTE: there is an error if you try to have a bag with two keys,
3566 // one an attr and one an id, with the same name. Not something we
3567 // currently ever have to worry about.
3568 ssize_t origKey = mBag.indexOfKey(key);
3569 if (origKey >= 0) {
3570 if (!replace) {
3571 const Item& item(mBag.valueAt(origKey));
3572 sourcePos.error("Resource entry %s already has bag item %s.\n"
3573 "%s:%d: Originally defined here.\n",
3574 String8(mName).string(), String8(key).string(),
3575 item.sourcePos.file.string(), item.sourcePos.line);
3576 return UNKNOWN_ERROR;
3577 }
3578 //printf("Replacing %s with %s\n",
3579 // String8(mBag.valueFor(key).value).string(), String8(value).string());
3580 mBag.replaceValueFor(key, item);
3581 }
3582
3583 mBag.add(key, item);
3584 return NO_ERROR;
3585}
3586
Adam Lesinski82a2dd82014-09-17 18:34:15 -07003587status_t ResourceTable::Entry::removeFromBag(const String16& key) {
3588 if (mType != Entry::TYPE_BAG) {
3589 return NO_ERROR;
3590 }
3591
3592 if (mBag.removeItem(key) >= 0) {
3593 return NO_ERROR;
3594 }
3595 return UNKNOWN_ERROR;
3596}
3597
Adam Lesinski282e1812014-01-23 18:17:42 -08003598status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos)
3599{
3600 status_t err = makeItABag(sourcePos);
3601 if (err != NO_ERROR) {
3602 return err;
3603 }
3604
3605 mBag.clear();
3606 return NO_ERROR;
3607}
3608
3609status_t ResourceTable::Entry::generateAttributes(ResourceTable* table,
3610 const String16& package)
3611{
3612 const String16 attr16("attr");
3613 const String16 id16("id");
3614 const size_t N = mBag.size();
3615 for (size_t i=0; i<N; i++) {
3616 const String16& key = mBag.keyAt(i);
3617 const Item& it = mBag.valueAt(i);
3618 if (it.isId) {
3619 if (!table->hasBagOrEntry(key, &id16, &package)) {
3620 String16 value("false");
Andreas Gampe87332a72014-10-01 22:03:58 -07003621 if (kIsDebug) {
3622 fprintf(stderr, "Generating %s:id/%s\n",
3623 String8(package).string(),
3624 String8(key).string());
3625 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003626 status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package,
3627 id16, key, value);
3628 if (err != NO_ERROR) {
3629 return err;
3630 }
3631 }
3632 } else if (!table->hasBagOrEntry(key, &attr16, &package)) {
3633
3634#if 1
3635// fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n",
3636// String8(key).string());
3637// const Item& item(mBag.valueAt(i));
3638// fprintf(stderr, "Referenced from file %s line %d\n",
3639// item.sourcePos.file.string(), item.sourcePos.line);
3640// return UNKNOWN_ERROR;
3641#else
3642 char numberStr[16];
3643 sprintf(numberStr, "%d", ResTable_map::TYPE_ANY);
3644 status_t err = table->addBag(SourcePos("<generated>", 0), package,
3645 attr16, key, String16(""),
3646 String16("^type"),
3647 String16(numberStr), NULL, NULL);
3648 if (err != NO_ERROR) {
3649 return err;
3650 }
3651#endif
3652 }
3653 }
3654 return NO_ERROR;
3655}
3656
3657status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
Andreas Gampe2412f842014-09-30 20:55:57 -07003658 const String16& /* package */)
Adam Lesinski282e1812014-01-23 18:17:42 -08003659{
3660 bool hasErrors = false;
3661
3662 if (mType == TYPE_BAG) {
3663 const char* errorMsg;
3664 const String16 style16("style");
3665 const String16 attr16("attr");
3666 const String16 id16("id");
3667 mParentId = 0;
3668 if (mParent.size() > 0) {
3669 mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
3670 if (mParentId == 0) {
3671 mPos.error("Error retrieving parent for item: %s '%s'.\n",
3672 errorMsg, String8(mParent).string());
3673 hasErrors = true;
3674 }
3675 }
3676 const size_t N = mBag.size();
3677 for (size_t i=0; i<N; i++) {
3678 const String16& key = mBag.keyAt(i);
3679 Item& it = mBag.editValueAt(i);
3680 it.bagKeyId = table->getResId(key,
3681 it.isId ? &id16 : &attr16, NULL, &errorMsg);
3682 //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId);
3683 if (it.bagKeyId == 0) {
3684 it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
3685 String8(it.isId ? id16 : attr16).string(),
3686 String8(key).string());
3687 hasErrors = true;
3688 }
3689 }
3690 }
Andreas Gampe2412f842014-09-30 20:55:57 -07003691 return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08003692}
3693
3694status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table,
3695 const String8* configTypeName, const ConfigDescription* config)
3696{
3697 if (mType == TYPE_ITEM) {
3698 Item& it = mItem;
3699 AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
3700 if (!table->stringToValue(&it.parsedValue, strings,
3701 it.value, false, true, 0,
3702 &it.style, NULL, &ac, mItemFormat,
3703 configTypeName, config)) {
3704 return UNKNOWN_ERROR;
3705 }
3706 } else if (mType == TYPE_BAG) {
3707 const size_t N = mBag.size();
3708 for (size_t i=0; i<N; i++) {
3709 const String16& key = mBag.keyAt(i);
3710 Item& it = mBag.editValueAt(i);
3711 AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
3712 if (!table->stringToValue(&it.parsedValue, strings,
3713 it.value, false, true, it.bagKeyId,
3714 &it.style, NULL, &ac, it.format,
3715 configTypeName, config)) {
3716 return UNKNOWN_ERROR;
3717 }
3718 }
3719 } else {
3720 mPos.error("Error: entry %s is not a single item or a bag.\n",
3721 String8(mName).string());
3722 return UNKNOWN_ERROR;
3723 }
3724 return NO_ERROR;
3725}
3726
3727status_t ResourceTable::Entry::remapStringValue(StringPool* strings)
3728{
3729 if (mType == TYPE_ITEM) {
3730 Item& it = mItem;
3731 if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
3732 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
3733 }
3734 } else if (mType == TYPE_BAG) {
3735 const size_t N = mBag.size();
3736 for (size_t i=0; i<N; i++) {
3737 Item& it = mBag.editValueAt(i);
3738 if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
3739 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
3740 }
3741 }
3742 } else {
3743 mPos.error("Error: entry %s is not a single item or a bag.\n",
3744 String8(mName).string());
3745 return UNKNOWN_ERROR;
3746 }
3747 return NO_ERROR;
3748}
3749
Andreas Gampe2412f842014-09-30 20:55:57 -07003750ssize_t ResourceTable::Entry::flatten(Bundle* /* bundle */, const sp<AaptFile>& data, bool isPublic)
Adam Lesinski282e1812014-01-23 18:17:42 -08003751{
3752 size_t amt = 0;
3753 ResTable_entry header;
3754 memset(&header, 0, sizeof(header));
3755 header.size = htods(sizeof(header));
Andreas Gampe2412f842014-09-30 20:55:57 -07003756 const type ty = mType;
3757 if (ty == TYPE_BAG) {
3758 header.flags |= htods(header.FLAG_COMPLEX);
Adam Lesinski282e1812014-01-23 18:17:42 -08003759 }
Andreas Gampe2412f842014-09-30 20:55:57 -07003760 if (isPublic) {
3761 header.flags |= htods(header.FLAG_PUBLIC);
3762 }
3763 header.key.index = htodl(mNameIndex);
Adam Lesinski282e1812014-01-23 18:17:42 -08003764 if (ty != TYPE_BAG) {
3765 status_t err = data->writeData(&header, sizeof(header));
3766 if (err != NO_ERROR) {
3767 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
3768 return err;
3769 }
3770
3771 const Item& it = mItem;
3772 Res_value par;
3773 memset(&par, 0, sizeof(par));
3774 par.size = htods(it.parsedValue.size);
3775 par.dataType = it.parsedValue.dataType;
3776 par.res0 = it.parsedValue.res0;
3777 par.data = htodl(it.parsedValue.data);
3778 #if 0
3779 printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n",
3780 String8(mName).string(), it.parsedValue.dataType,
3781 it.parsedValue.data, par.res0);
3782 #endif
3783 err = data->writeData(&par, it.parsedValue.size);
3784 if (err != NO_ERROR) {
3785 fprintf(stderr, "ERROR: out of memory creating Res_value\n");
3786 return err;
3787 }
3788 amt += it.parsedValue.size;
3789 } else {
3790 size_t N = mBag.size();
3791 size_t i;
3792 // Create correct ordering of items.
3793 KeyedVector<uint32_t, const Item*> items;
3794 for (i=0; i<N; i++) {
3795 const Item& it = mBag.valueAt(i);
3796 items.add(it.bagKeyId, &it);
3797 }
3798 N = items.size();
3799
3800 ResTable_map_entry mapHeader;
3801 memcpy(&mapHeader, &header, sizeof(header));
3802 mapHeader.size = htods(sizeof(mapHeader));
3803 mapHeader.parent.ident = htodl(mParentId);
3804 mapHeader.count = htodl(N);
3805 status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
3806 if (err != NO_ERROR) {
3807 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
3808 return err;
3809 }
3810
3811 for (i=0; i<N; i++) {
3812 const Item& it = *items.valueAt(i);
3813 ResTable_map map;
3814 map.name.ident = htodl(it.bagKeyId);
3815 map.value.size = htods(it.parsedValue.size);
3816 map.value.dataType = it.parsedValue.dataType;
3817 map.value.res0 = it.parsedValue.res0;
3818 map.value.data = htodl(it.parsedValue.data);
3819 err = data->writeData(&map, sizeof(map));
3820 if (err != NO_ERROR) {
3821 fprintf(stderr, "ERROR: out of memory creating Res_value\n");
3822 return err;
3823 }
3824 amt += sizeof(map);
3825 }
3826 }
3827 return amt;
3828}
3829
3830void ResourceTable::ConfigList::appendComment(const String16& comment,
3831 bool onlyIfEmpty)
3832{
3833 if (comment.size() <= 0) {
3834 return;
3835 }
3836 if (onlyIfEmpty && mComment.size() > 0) {
3837 return;
3838 }
3839 if (mComment.size() > 0) {
3840 mComment.append(String16("\n"));
3841 }
3842 mComment.append(comment);
3843}
3844
3845void ResourceTable::ConfigList::appendTypeComment(const String16& comment)
3846{
3847 if (comment.size() <= 0) {
3848 return;
3849 }
3850 if (mTypeComment.size() > 0) {
3851 mTypeComment.append(String16("\n"));
3852 }
3853 mTypeComment.append(comment);
3854}
3855
3856status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos,
3857 const String16& name,
3858 const uint32_t ident)
3859{
3860 #if 0
3861 int32_t entryIdx = Res_GETENTRY(ident);
3862 if (entryIdx < 0) {
3863 sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n",
3864 String8(mName).string(), String8(name).string(), ident);
3865 return UNKNOWN_ERROR;
3866 }
3867 #endif
3868
3869 int32_t typeIdx = Res_GETTYPE(ident);
3870 if (typeIdx >= 0) {
3871 typeIdx++;
3872 if (mPublicIndex > 0 && mPublicIndex != typeIdx) {
3873 sourcePos.error("Public resource %s/%s has conflicting type codes for its"
3874 " public identifiers (0x%x vs 0x%x).\n",
3875 String8(mName).string(), String8(name).string(),
3876 mPublicIndex, typeIdx);
3877 return UNKNOWN_ERROR;
3878 }
3879 mPublicIndex = typeIdx;
3880 }
3881
3882 if (mFirstPublicSourcePos == NULL) {
3883 mFirstPublicSourcePos = new SourcePos(sourcePos);
3884 }
3885
3886 if (mPublic.indexOfKey(name) < 0) {
3887 mPublic.add(name, Public(sourcePos, String16(), ident));
3888 } else {
3889 Public& p = mPublic.editValueFor(name);
3890 if (p.ident != ident) {
3891 sourcePos.error("Public resource %s/%s has conflicting public identifiers"
3892 " (0x%08x vs 0x%08x).\n"
3893 "%s:%d: Originally defined here.\n",
3894 String8(mName).string(), String8(name).string(), p.ident, ident,
3895 p.sourcePos.file.string(), p.sourcePos.line);
3896 return UNKNOWN_ERROR;
3897 }
3898 }
3899
3900 return NO_ERROR;
3901}
3902
3903void ResourceTable::Type::canAddEntry(const String16& name)
3904{
3905 mCanAddEntries.add(name);
3906}
3907
3908sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
3909 const SourcePos& sourcePos,
3910 const ResTable_config* config,
3911 bool doSetIndex,
3912 bool overlay,
3913 bool autoAddOverlay)
3914{
3915 int pos = -1;
3916 sp<ConfigList> c = mConfigs.valueFor(entry);
3917 if (c == NULL) {
3918 if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) {
3919 sourcePos.error("Resource at %s appears in overlay but not"
3920 " in the base package; use <add-resource> to add.\n",
3921 String8(entry).string());
3922 return NULL;
3923 }
3924 c = new ConfigList(entry, sourcePos);
3925 mConfigs.add(entry, c);
3926 pos = (int)mOrderedConfigs.size();
3927 mOrderedConfigs.add(c);
3928 if (doSetIndex) {
3929 c->setEntryIndex(pos);
3930 }
3931 }
3932
3933 ConfigDescription cdesc;
3934 if (config) cdesc = *config;
3935
3936 sp<Entry> e = c->getEntries().valueFor(cdesc);
3937 if (e == NULL) {
Andreas Gampe2412f842014-09-30 20:55:57 -07003938 if (kIsDebug) {
3939 if (config != NULL) {
3940 printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c "
Adam Lesinski282e1812014-01-23 18:17:42 -08003941 "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
Andreas Gampe2412f842014-09-30 20:55:57 -07003942 "sw%ddp w%ddp h%ddp layout:%d\n",
Adam Lesinski282e1812014-01-23 18:17:42 -08003943 sourcePos.file.string(), sourcePos.line,
3944 config->mcc, config->mnc,
3945 config->language[0] ? config->language[0] : '-',
3946 config->language[1] ? config->language[1] : '-',
3947 config->country[0] ? config->country[0] : '-',
3948 config->country[1] ? config->country[1] : '-',
3949 config->orientation,
3950 config->touchscreen,
3951 config->density,
3952 config->keyboard,
3953 config->inputFlags,
3954 config->navigation,
3955 config->screenWidth,
3956 config->screenHeight,
3957 config->smallestScreenWidthDp,
3958 config->screenWidthDp,
3959 config->screenHeightDp,
Andreas Gampe2412f842014-09-30 20:55:57 -07003960 config->screenLayout);
3961 } else {
3962 printf("New entry at %s:%d: NULL config\n",
3963 sourcePos.file.string(), sourcePos.line);
3964 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003965 }
3966 e = new Entry(entry, sourcePos);
3967 c->addEntry(cdesc, e);
3968 /*
3969 if (doSetIndex) {
3970 if (pos < 0) {
3971 for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) {
3972 if (mOrderedConfigs[pos] == c) {
3973 break;
3974 }
3975 }
3976 if (pos >= (int)mOrderedConfigs.size()) {
3977 sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry");
3978 return NULL;
3979 }
3980 }
3981 e->setEntryIndex(pos);
3982 }
3983 */
3984 }
3985
Adam Lesinski282e1812014-01-23 18:17:42 -08003986 return e;
3987}
3988
Adam Lesinski9b624c12014-11-19 17:49:26 -08003989sp<ResourceTable::ConfigList> ResourceTable::Type::removeEntry(const String16& entry) {
3990 ssize_t idx = mConfigs.indexOfKey(entry);
3991 if (idx < 0) {
3992 return NULL;
3993 }
3994
3995 sp<ConfigList> removed = mConfigs.valueAt(idx);
3996 mConfigs.removeItemsAt(idx);
3997
3998 Vector<sp<ConfigList> >::iterator iter = std::find(
3999 mOrderedConfigs.begin(), mOrderedConfigs.end(), removed);
4000 if (iter != mOrderedConfigs.end()) {
4001 mOrderedConfigs.erase(iter);
4002 }
4003
4004 mPublic.removeItem(entry);
4005 return removed;
4006}
4007
4008SortedVector<ConfigDescription> ResourceTable::Type::getUniqueConfigs() const {
4009 SortedVector<ConfigDescription> unique;
4010 const size_t entryCount = mOrderedConfigs.size();
4011 for (size_t i = 0; i < entryCount; i++) {
4012 if (mOrderedConfigs[i] == NULL) {
4013 continue;
4014 }
4015 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configs =
4016 mOrderedConfigs[i]->getEntries();
4017 const size_t configCount = configs.size();
4018 for (size_t j = 0; j < configCount; j++) {
4019 unique.add(configs.keyAt(j));
4020 }
4021 }
4022 return unique;
4023}
4024
Adam Lesinski282e1812014-01-23 18:17:42 -08004025status_t ResourceTable::Type::applyPublicEntryOrder()
4026{
4027 size_t N = mOrderedConfigs.size();
4028 Vector<sp<ConfigList> > origOrder(mOrderedConfigs);
4029 bool hasError = false;
4030
4031 size_t i;
4032 for (i=0; i<N; i++) {
4033 mOrderedConfigs.replaceAt(NULL, i);
4034 }
4035
4036 const size_t NP = mPublic.size();
4037 //printf("Ordering %d configs from %d public defs\n", N, NP);
4038 size_t j;
4039 for (j=0; j<NP; j++) {
4040 const String16& name = mPublic.keyAt(j);
4041 const Public& p = mPublic.valueAt(j);
4042 int32_t idx = Res_GETENTRY(p.ident);
4043 //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n",
4044 // String8(mName).string(), String8(name).string(), p.ident, N);
4045 bool found = false;
4046 for (i=0; i<N; i++) {
4047 sp<ConfigList> e = origOrder.itemAt(i);
4048 //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
4049 if (e->getName() == name) {
4050 if (idx >= (int32_t)mOrderedConfigs.size()) {
Adam Lesinski9b624c12014-11-19 17:49:26 -08004051 mOrderedConfigs.resize(idx + 1);
4052 }
4053
4054 if (mOrderedConfigs.itemAt(idx) == NULL) {
Adam Lesinski282e1812014-01-23 18:17:42 -08004055 e->setPublic(true);
4056 e->setPublicSourcePos(p.sourcePos);
4057 mOrderedConfigs.replaceAt(e, idx);
4058 origOrder.removeAt(i);
4059 N--;
4060 found = true;
4061 break;
4062 } else {
4063 sp<ConfigList> oe = mOrderedConfigs.itemAt(idx);
4064
4065 p.sourcePos.error("Multiple entry names declared for public entry"
4066 " identifier 0x%x in type %s (%s vs %s).\n"
4067 "%s:%d: Originally defined here.",
4068 idx+1, String8(mName).string(),
4069 String8(oe->getName()).string(),
4070 String8(name).string(),
4071 oe->getPublicSourcePos().file.string(),
4072 oe->getPublicSourcePos().line);
4073 hasError = true;
4074 }
4075 }
4076 }
4077
4078 if (!found) {
4079 p.sourcePos.error("Public symbol %s/%s declared here is not defined.",
4080 String8(mName).string(), String8(name).string());
4081 hasError = true;
4082 }
4083 }
4084
4085 //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size());
4086
4087 if (N != origOrder.size()) {
4088 printf("Internal error: remaining private symbol count mismatch\n");
4089 N = origOrder.size();
4090 }
4091
4092 j = 0;
4093 for (i=0; i<N; i++) {
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -07004094 const sp<ConfigList>& e = origOrder.itemAt(i);
Adam Lesinski282e1812014-01-23 18:17:42 -08004095 // There will always be enough room for the remaining entries.
4096 while (mOrderedConfigs.itemAt(j) != NULL) {
4097 j++;
4098 }
4099 mOrderedConfigs.replaceAt(e, j);
4100 j++;
4101 }
4102
Andreas Gampe2412f842014-09-30 20:55:57 -07004103 return hasError ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08004104}
4105
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004106ResourceTable::Package::Package(const String16& name, size_t packageId)
4107 : mName(name), mPackageId(packageId),
Adam Lesinski282e1812014-01-23 18:17:42 -08004108 mTypeStringsMapping(0xffffffff),
4109 mKeyStringsMapping(0xffffffff)
4110{
4111}
4112
4113sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
4114 const SourcePos& sourcePos,
4115 bool doSetIndex)
4116{
4117 sp<Type> t = mTypes.valueFor(type);
4118 if (t == NULL) {
4119 t = new Type(type, sourcePos);
4120 mTypes.add(type, t);
4121 mOrderedTypes.add(t);
4122 if (doSetIndex) {
4123 // For some reason the type's index is set to one plus the index
4124 // in the mOrderedTypes list, rather than just the index.
4125 t->setIndex(mOrderedTypes.size());
4126 }
4127 }
4128 return t;
4129}
4130
4131status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data)
4132{
Adam Lesinski282e1812014-01-23 18:17:42 -08004133 status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping);
4134 if (err != NO_ERROR) {
4135 fprintf(stderr, "ERROR: Type string data is corrupt!\n");
Adam Lesinski57079512014-07-29 11:51:35 -07004136 return err;
Adam Lesinski282e1812014-01-23 18:17:42 -08004137 }
Adam Lesinski57079512014-07-29 11:51:35 -07004138
4139 // Retain a reference to the new data after we've successfully replaced
4140 // all uses of the old reference (in setStrings() ).
4141 mTypeStringsData = data;
4142 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08004143}
4144
4145status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data)
4146{
Adam Lesinski282e1812014-01-23 18:17:42 -08004147 status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping);
4148 if (err != NO_ERROR) {
4149 fprintf(stderr, "ERROR: Key string data is corrupt!\n");
Adam Lesinski57079512014-07-29 11:51:35 -07004150 return err;
Adam Lesinski282e1812014-01-23 18:17:42 -08004151 }
Adam Lesinski57079512014-07-29 11:51:35 -07004152
4153 // Retain a reference to the new data after we've successfully replaced
4154 // all uses of the old reference (in setStrings() ).
4155 mKeyStringsData = data;
4156 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08004157}
4158
4159status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data,
4160 ResStringPool* strings,
4161 DefaultKeyedVector<String16, uint32_t>* mappings)
4162{
4163 if (data->getData() == NULL) {
4164 return UNKNOWN_ERROR;
4165 }
4166
Adam Lesinski282e1812014-01-23 18:17:42 -08004167 status_t err = strings->setTo(data->getData(), data->getSize());
4168 if (err == NO_ERROR) {
4169 const size_t N = strings->size();
4170 for (size_t i=0; i<N; i++) {
4171 size_t len;
4172 mappings->add(String16(strings->stringAt(i, &len)), i);
4173 }
4174 }
4175 return err;
4176}
4177
4178status_t ResourceTable::Package::applyPublicTypeOrder()
4179{
4180 size_t N = mOrderedTypes.size();
4181 Vector<sp<Type> > origOrder(mOrderedTypes);
4182
4183 size_t i;
4184 for (i=0; i<N; i++) {
4185 mOrderedTypes.replaceAt(NULL, i);
4186 }
4187
4188 for (i=0; i<N; i++) {
4189 sp<Type> t = origOrder.itemAt(i);
4190 int32_t idx = t->getPublicIndex();
4191 if (idx > 0) {
4192 idx--;
4193 while (idx >= (int32_t)mOrderedTypes.size()) {
4194 mOrderedTypes.add();
4195 }
4196 if (mOrderedTypes.itemAt(idx) != NULL) {
4197 sp<Type> ot = mOrderedTypes.itemAt(idx);
4198 t->getFirstPublicSourcePos().error("Multiple type names declared for public type"
4199 " identifier 0x%x (%s vs %s).\n"
4200 "%s:%d: Originally defined here.",
4201 idx, String8(ot->getName()).string(),
4202 String8(t->getName()).string(),
4203 ot->getFirstPublicSourcePos().file.string(),
4204 ot->getFirstPublicSourcePos().line);
4205 return UNKNOWN_ERROR;
4206 }
4207 mOrderedTypes.replaceAt(t, idx);
4208 origOrder.removeAt(i);
4209 i--;
4210 N--;
4211 }
4212 }
4213
4214 size_t j=0;
4215 for (i=0; i<N; i++) {
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -07004216 const sp<Type>& t = origOrder.itemAt(i);
Adam Lesinski282e1812014-01-23 18:17:42 -08004217 // There will always be enough room for the remaining types.
4218 while (mOrderedTypes.itemAt(j) != NULL) {
4219 j++;
4220 }
4221 mOrderedTypes.replaceAt(t, j);
4222 }
4223
4224 return NO_ERROR;
4225}
4226
Adam Lesinski9b624c12014-11-19 17:49:26 -08004227void ResourceTable::Package::movePrivateAttrs() {
4228 sp<Type> attr = mTypes.valueFor(String16("attr"));
4229 if (attr == NULL) {
4230 // Nothing to do.
4231 return;
4232 }
4233
4234 Vector<sp<ConfigList> > privateAttrs;
4235
4236 bool hasPublic = false;
4237 const Vector<sp<ConfigList> >& configs = attr->getOrderedConfigs();
4238 const size_t configCount = configs.size();
4239 for (size_t i = 0; i < configCount; i++) {
4240 if (configs[i] == NULL) {
4241 continue;
4242 }
4243
4244 if (attr->isPublic(configs[i]->getName())) {
4245 hasPublic = true;
4246 } else {
4247 privateAttrs.add(configs[i]);
4248 }
4249 }
4250
4251 // Only if we have public attributes do we create a separate type for
4252 // private attributes.
4253 if (!hasPublic) {
4254 return;
4255 }
4256
4257 // Create a new type for private attributes.
4258 sp<Type> privateAttrType = getType(String16(kAttrPrivateType), SourcePos());
4259
4260 const size_t privateAttrCount = privateAttrs.size();
4261 for (size_t i = 0; i < privateAttrCount; i++) {
4262 const sp<ConfigList>& cl = privateAttrs[i];
4263
4264 // Remove the private attributes from their current type.
4265 attr->removeEntry(cl->getName());
4266
4267 // Add it to the new type.
4268 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = cl->getEntries();
4269 const size_t entryCount = entries.size();
4270 for (size_t j = 0; j < entryCount; j++) {
4271 const sp<Entry>& oldEntry = entries[j];
4272 sp<Entry> entry = privateAttrType->getEntry(
4273 cl->getName(), oldEntry->getPos(), &entries.keyAt(j));
4274 *entry = *oldEntry;
4275 }
4276
4277 // Move the symbols to the new type.
4278
4279 }
4280}
4281
Adam Lesinski282e1812014-01-23 18:17:42 -08004282sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
4283{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004284 if (package != mAssetsPackage) {
4285 return NULL;
Adam Lesinski282e1812014-01-23 18:17:42 -08004286 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004287 return mPackages.valueFor(package);
Adam Lesinski282e1812014-01-23 18:17:42 -08004288}
4289
4290sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
4291 const String16& type,
4292 const SourcePos& sourcePos,
4293 bool doSetIndex)
4294{
4295 sp<Package> p = getPackage(package);
4296 if (p == NULL) {
4297 return NULL;
4298 }
4299 return p->getType(type, sourcePos, doSetIndex);
4300}
4301
4302sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
4303 const String16& type,
4304 const String16& name,
4305 const SourcePos& sourcePos,
4306 bool overlay,
4307 const ResTable_config* config,
4308 bool doSetIndex)
4309{
4310 sp<Type> t = getType(package, type, sourcePos, doSetIndex);
4311 if (t == NULL) {
4312 return NULL;
4313 }
4314 return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
4315}
4316
Adam Lesinskie572c012014-09-19 15:10:04 -07004317sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package,
4318 const String16& type, const String16& name) const
4319{
4320 const size_t packageCount = mOrderedPackages.size();
4321 for (size_t pi = 0; pi < packageCount; pi++) {
4322 const sp<Package>& p = mOrderedPackages[pi];
4323 if (p == NULL || p->getName() != package) {
4324 continue;
4325 }
4326
4327 const Vector<sp<Type> >& types = p->getOrderedTypes();
4328 const size_t typeCount = types.size();
4329 for (size_t ti = 0; ti < typeCount; ti++) {
4330 const sp<Type>& t = types[ti];
4331 if (t == NULL || t->getName() != type) {
4332 continue;
4333 }
4334
4335 const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs();
4336 const size_t configCount = configs.size();
4337 for (size_t ci = 0; ci < configCount; ci++) {
4338 const sp<ConfigList>& cl = configs[ci];
4339 if (cl == NULL || cl->getName() != name) {
4340 continue;
4341 }
4342
4343 return cl;
4344 }
4345 }
4346 }
4347 return NULL;
4348}
4349
Adam Lesinski282e1812014-01-23 18:17:42 -08004350sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
4351 const ResTable_config* config) const
4352{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004353 size_t pid = Res_GETPACKAGE(resID)+1;
Adam Lesinski282e1812014-01-23 18:17:42 -08004354 const size_t N = mOrderedPackages.size();
Adam Lesinski282e1812014-01-23 18:17:42 -08004355 sp<Package> p;
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004356 for (size_t i = 0; i < N; i++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08004357 sp<Package> check = mOrderedPackages[i];
4358 if (check->getAssignedId() == pid) {
4359 p = check;
4360 break;
4361 }
4362
4363 }
4364 if (p == NULL) {
4365 fprintf(stderr, "warning: Package not found for resource #%08x\n", resID);
4366 return NULL;
4367 }
4368
4369 int tid = Res_GETTYPE(resID);
4370 if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) {
4371 fprintf(stderr, "warning: Type not found for resource #%08x\n", resID);
4372 return NULL;
4373 }
4374 sp<Type> t = p->getOrderedTypes()[tid];
4375
4376 int eid = Res_GETENTRY(resID);
4377 if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) {
4378 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
4379 return NULL;
4380 }
4381
4382 sp<ConfigList> c = t->getOrderedConfigs()[eid];
4383 if (c == NULL) {
4384 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
4385 return NULL;
4386 }
4387
4388 ConfigDescription cdesc;
4389 if (config) cdesc = *config;
4390 sp<Entry> e = c->getEntries().valueFor(cdesc);
4391 if (c == NULL) {
4392 fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID);
4393 return NULL;
4394 }
4395
4396 return e;
4397}
4398
4399const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const
4400{
4401 sp<const Entry> e = getEntry(resID);
4402 if (e == NULL) {
4403 return NULL;
4404 }
4405
4406 const size_t N = e->getBag().size();
4407 for (size_t i=0; i<N; i++) {
4408 const Item& it = e->getBag().valueAt(i);
4409 if (it.bagKeyId == 0) {
4410 fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n",
4411 String8(e->getName()).string(),
4412 String8(e->getBag().keyAt(i)).string());
4413 }
4414 if (it.bagKeyId == attrID) {
4415 return &it;
4416 }
4417 }
4418
4419 return NULL;
4420}
4421
4422bool ResourceTable::getItemValue(
4423 uint32_t resID, uint32_t attrID, Res_value* outValue)
4424{
4425 const Item* item = getItem(resID, attrID);
4426
4427 bool res = false;
4428 if (item != NULL) {
4429 if (item->evaluating) {
4430 sp<const Entry> e = getEntry(resID);
4431 const size_t N = e->getBag().size();
4432 size_t i;
4433 for (i=0; i<N; i++) {
4434 if (&e->getBag().valueAt(i) == item) {
4435 break;
4436 }
4437 }
4438 fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n",
4439 String8(e->getName()).string(),
4440 String8(e->getBag().keyAt(i)).string());
4441 return false;
4442 }
4443 item->evaluating = true;
4444 res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId);
Andreas Gampe2412f842014-09-30 20:55:57 -07004445 if (kIsDebug) {
Adam Lesinski282e1812014-01-23 18:17:42 -08004446 if (res) {
4447 printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n",
4448 resID, attrID, String8(getEntry(resID)->getName()).string(),
4449 outValue->dataType, outValue->data);
4450 } else {
4451 printf("getItemValue of #%08x[#%08x]: failed\n",
4452 resID, attrID);
4453 }
Andreas Gampe2412f842014-09-30 20:55:57 -07004454 }
Adam Lesinski282e1812014-01-23 18:17:42 -08004455 item->evaluating = false;
4456 }
4457 return res;
4458}
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004459
4460/**
Adam Lesinski28994d82015-01-13 13:42:41 -08004461 * Returns the SDK version at which the attribute was
4462 * made public, or -1 if the resource ID is not an attribute
4463 * or is not public.
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004464 */
Adam Lesinski28994d82015-01-13 13:42:41 -08004465int ResourceTable::getPublicAttributeSdkLevel(uint32_t attrId) const {
4466 if (Res_GETPACKAGE(attrId) + 1 != 0x01 || Res_GETTYPE(attrId) + 1 != 0x01) {
4467 return -1;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004468 }
4469
4470 uint32_t specFlags;
4471 if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) {
Adam Lesinski28994d82015-01-13 13:42:41 -08004472 return -1;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004473 }
4474
Adam Lesinski28994d82015-01-13 13:42:41 -08004475 if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
4476 return -1;
4477 }
4478
4479 const size_t entryId = Res_GETENTRY(attrId);
4480 if (entryId <= 0x021c) {
4481 return 1;
4482 } else if (entryId <= 0x021d) {
4483 return 2;
4484 } else if (entryId <= 0x0269) {
4485 return SDK_CUPCAKE;
4486 } else if (entryId <= 0x028d) {
4487 return SDK_DONUT;
4488 } else if (entryId <= 0x02ad) {
4489 return SDK_ECLAIR;
4490 } else if (entryId <= 0x02b3) {
4491 return SDK_ECLAIR_0_1;
4492 } else if (entryId <= 0x02b5) {
4493 return SDK_ECLAIR_MR1;
4494 } else if (entryId <= 0x02bd) {
4495 return SDK_FROYO;
4496 } else if (entryId <= 0x02cb) {
4497 return SDK_GINGERBREAD;
4498 } else if (entryId <= 0x0361) {
4499 return SDK_HONEYCOMB;
4500 } else if (entryId <= 0x0366) {
4501 return SDK_HONEYCOMB_MR1;
4502 } else if (entryId <= 0x03a6) {
4503 return SDK_HONEYCOMB_MR2;
4504 } else if (entryId <= 0x03ae) {
4505 return SDK_JELLY_BEAN;
4506 } else if (entryId <= 0x03cc) {
4507 return SDK_JELLY_BEAN_MR1;
4508 } else if (entryId <= 0x03da) {
4509 return SDK_JELLY_BEAN_MR2;
4510 } else if (entryId <= 0x03f1) {
4511 return SDK_KITKAT;
4512 } else if (entryId <= 0x03f6) {
4513 return SDK_KITKAT_WATCH;
4514 } else if (entryId <= 0x04ce) {
4515 return SDK_LOLLIPOP;
4516 } else {
4517 // Anything else is marked as defined in
4518 // SDK_LOLLIPOP_MR1 since after this
4519 // version no attribute compat work
4520 // needs to be done.
4521 return SDK_LOLLIPOP_MR1;
4522 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004523}
4524
Adam Lesinski28994d82015-01-13 13:42:41 -08004525/**
4526 * First check the Manifest, then check the command line flag.
4527 */
4528static int getMinSdkVersion(const Bundle* bundle) {
4529 if (bundle->getManifestMinSdkVersion() != NULL && strlen(bundle->getManifestMinSdkVersion()) > 0) {
4530 return atoi(bundle->getManifestMinSdkVersion());
4531 } else if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
4532 return atoi(bundle->getMinSdkVersion());
Adam Lesinskie572c012014-09-19 15:10:04 -07004533 }
Adam Lesinski28994d82015-01-13 13:42:41 -08004534 return 0;
Adam Lesinskie572c012014-09-19 15:10:04 -07004535}
4536
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004537bool ResourceTable::shouldGenerateVersionedResource(
4538 const sp<ResourceTable::ConfigList>& configList,
4539 const ConfigDescription& sourceConfig,
4540 const int sdkVersionToGenerate) {
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004541 assert(sdkVersionToGenerate > sourceConfig.sdkVersion);
Adam Lesinski526d73b2016-07-18 17:01:14 -07004542 assert(configList != NULL);
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004543 const DefaultKeyedVector<ConfigDescription, sp<ResourceTable::Entry>>& entries
4544 = configList->getEntries();
4545 ssize_t idx = entries.indexOfKey(sourceConfig);
4546
4547 // The source config came from this list, so it should be here.
4548 assert(idx >= 0);
4549
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004550 // The next configuration either only varies in sdkVersion, or it is completely different
4551 // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004552
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004553 // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
4554 // qualifiers, so we need to iterate through the entire list to be sure there
4555 // are no higher sdk level versions of this resource.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004556 ConfigDescription tempConfig(sourceConfig);
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004557 for (size_t i = static_cast<size_t>(idx) + 1; i < entries.size(); i++) {
4558 const ConfigDescription& nextConfig = entries.keyAt(i);
4559 tempConfig.sdkVersion = nextConfig.sdkVersion;
4560 if (tempConfig == nextConfig) {
4561 // The two configs are the same, check the sdk version.
4562 return sdkVersionToGenerate < nextConfig.sdkVersion;
4563 }
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004564 }
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004565
4566 // No match was found, so we should generate the versioned resource.
4567 return true;
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004568}
4569
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004570/**
4571 * Modifies the entries in the resource table to account for compatibility
4572 * issues with older versions of Android.
4573 *
4574 * This primarily handles the issue of private/public attribute clashes
4575 * in framework resources.
4576 *
4577 * AAPT has traditionally assigned resource IDs to public attributes,
4578 * and then followed those public definitions with private attributes.
4579 *
4580 * --- PUBLIC ---
4581 * | 0x01010234 | attr/color
4582 * | 0x01010235 | attr/background
4583 *
4584 * --- PRIVATE ---
4585 * | 0x01010236 | attr/secret
4586 * | 0x01010237 | attr/shhh
4587 *
4588 * Each release, when attributes are added, they take the place of the private
4589 * attributes and the private attributes are shifted down again.
4590 *
4591 * --- PUBLIC ---
4592 * | 0x01010234 | attr/color
4593 * | 0x01010235 | attr/background
4594 * | 0x01010236 | attr/shinyNewAttr
4595 * | 0x01010237 | attr/highlyValuedFeature
4596 *
4597 * --- PRIVATE ---
4598 * | 0x01010238 | attr/secret
4599 * | 0x01010239 | attr/shhh
4600 *
4601 * Platform code may look for private attributes set in a theme. If an app
4602 * compiled against a newer version of the platform uses a new public
4603 * attribute that happens to have the same ID as the private attribute
4604 * the older platform is expecting, then the behavior is undefined.
4605 *
4606 * We get around this by detecting any newly defined attributes (in L),
4607 * copy the resource into a -v21 qualified resource, and delete the
4608 * attribute from the original resource. This ensures that older platforms
4609 * don't see the new attribute, but when running on L+ platforms, the
4610 * attribute will be respected.
4611 */
4612status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
Adam Lesinski28994d82015-01-13 13:42:41 -08004613 const int minSdk = getMinSdkVersion(bundle);
4614 if (minSdk >= SDK_LOLLIPOP_MR1) {
4615 // Lollipop MR1 and up handles public attributes differently, no
4616 // need to do any compat modifications.
Adam Lesinskie572c012014-09-19 15:10:04 -07004617 return NO_ERROR;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004618 }
4619
4620 const String16 attr16("attr");
4621
4622 const size_t packageCount = mOrderedPackages.size();
4623 for (size_t pi = 0; pi < packageCount; pi++) {
4624 sp<Package> p = mOrderedPackages.itemAt(pi);
4625 if (p == NULL || p->getTypes().size() == 0) {
4626 // Empty, skip!
4627 continue;
4628 }
4629
4630 const size_t typeCount = p->getOrderedTypes().size();
4631 for (size_t ti = 0; ti < typeCount; ti++) {
4632 sp<Type> t = p->getOrderedTypes().itemAt(ti);
4633 if (t == NULL) {
4634 continue;
4635 }
4636
4637 const size_t configCount = t->getOrderedConfigs().size();
4638 for (size_t ci = 0; ci < configCount; ci++) {
4639 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
4640 if (c == NULL) {
4641 continue;
4642 }
4643
4644 Vector<key_value_pair_t<ConfigDescription, sp<Entry> > > entriesToAdd;
4645 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries =
4646 c->getEntries();
4647 const size_t entryCount = entries.size();
4648 for (size_t ei = 0; ei < entryCount; ei++) {
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -07004649 const sp<Entry>& e = entries.valueAt(ei);
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004650 if (e == NULL || e->getType() != Entry::TYPE_BAG) {
4651 continue;
4652 }
4653
4654 const ConfigDescription& config = entries.keyAt(ei);
Adam Lesinski28994d82015-01-13 13:42:41 -08004655 if (config.sdkVersion >= SDK_LOLLIPOP_MR1) {
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004656 continue;
4657 }
4658
Adam Lesinski28994d82015-01-13 13:42:41 -08004659 KeyedVector<int, Vector<String16> > attributesToRemove;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004660 const KeyedVector<String16, Item>& bag = e->getBag();
4661 const size_t bagCount = bag.size();
4662 for (size_t bi = 0; bi < bagCount; bi++) {
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004663 const uint32_t attrId = getResId(bag.keyAt(bi), &attr16);
Adam Lesinski28994d82015-01-13 13:42:41 -08004664 const int sdkLevel = getPublicAttributeSdkLevel(attrId);
4665 if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
4666 AaptUtil::appendValue(attributesToRemove, sdkLevel, bag.keyAt(bi));
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004667 }
4668 }
4669
4670 if (attributesToRemove.isEmpty()) {
4671 continue;
4672 }
4673
Adam Lesinski28994d82015-01-13 13:42:41 -08004674 const size_t sdkCount = attributesToRemove.size();
4675 for (size_t i = 0; i < sdkCount; i++) {
4676 const int sdkLevel = attributesToRemove.keyAt(i);
4677
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004678 if (!shouldGenerateVersionedResource(c, config, sdkLevel)) {
4679 // There is a style that will override this generated one.
4680 continue;
4681 }
4682
Adam Lesinski28994d82015-01-13 13:42:41 -08004683 // Duplicate the entry under the same configuration
4684 // but with sdkVersion == sdkLevel.
4685 ConfigDescription newConfig(config);
4686 newConfig.sdkVersion = sdkLevel;
4687
4688 sp<Entry> newEntry = new Entry(*e);
4689
4690 // Remove all items that have a higher SDK level than
4691 // the one we are synthesizing.
4692 for (size_t j = 0; j < sdkCount; j++) {
4693 if (j == i) {
4694 continue;
4695 }
4696
4697 if (attributesToRemove.keyAt(j) > sdkLevel) {
4698 const size_t attrCount = attributesToRemove[j].size();
4699 for (size_t k = 0; k < attrCount; k++) {
4700 newEntry->removeFromBag(attributesToRemove[j][k]);
4701 }
4702 }
4703 }
4704
4705 entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
4706 newConfig, newEntry));
4707 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004708
4709 // Remove the attribute from the original.
4710 for (size_t i = 0; i < attributesToRemove.size(); i++) {
Adam Lesinski28994d82015-01-13 13:42:41 -08004711 for (size_t j = 0; j < attributesToRemove[i].size(); j++) {
4712 e->removeFromBag(attributesToRemove[i][j]);
4713 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004714 }
4715 }
4716
4717 const size_t entriesToAddCount = entriesToAdd.size();
4718 for (size_t i = 0; i < entriesToAddCount; i++) {
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004719 assert(entries.indexOfKey(entriesToAdd[i].key) < 0);
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004720
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004721 if (bundle->getVerbose()) {
4722 entriesToAdd[i].value->getPos()
4723 .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
Adam Lesinski28994d82015-01-13 13:42:41 -08004724 entriesToAdd[i].key.sdkVersion,
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004725 String8(p->getName()).string(),
4726 String8(t->getName()).string(),
4727 String8(entriesToAdd[i].value->getName()).string(),
4728 entriesToAdd[i].key.toString().string());
4729 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004730
Adam Lesinski978ab9d2014-09-24 19:02:52 -07004731 sp<Entry> newEntry = t->getEntry(c->getName(),
4732 entriesToAdd[i].value->getPos(),
4733 &entriesToAdd[i].key);
4734
4735 *newEntry = *entriesToAdd[i].value;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004736 }
4737 }
4738 }
4739 }
4740 return NO_ERROR;
4741}
Adam Lesinskie572c012014-09-19 15:10:04 -07004742
Yuichi Araki4d35cca2017-01-18 20:42:17 +09004743const String16 kTransitionElements[] = {
4744 String16("fade"),
4745 String16("changeBounds"),
4746 String16("slide"),
4747 String16("explode"),
4748 String16("changeImageTransform"),
4749 String16("changeTransform"),
4750 String16("changeClipBounds"),
4751 String16("autoTransition"),
4752 String16("recolor"),
4753 String16("changeScroll"),
4754 String16("transitionSet"),
4755 String16("transition"),
4756 String16("transitionManager"),
4757};
4758
4759static bool IsTransitionElement(const String16& name) {
4760 for (int i = 0, size = sizeof(kTransitionElements) / sizeof(kTransitionElements[0]);
4761 i < size; ++i) {
4762 if (name == kTransitionElements[i]) {
4763 return true;
4764 }
4765 }
4766 return false;
4767}
4768
Adam Lesinskicf1f1d92017-03-16 16:54:23 -07004769bool ResourceTable::versionForCompat(const Bundle* bundle, const String16& resourceName,
4770 const sp<AaptFile>& target, const sp<XMLNode>& root) {
4771 XMLNode* node = root.get();
4772 while (node->getType() != XMLNode::TYPE_ELEMENT) {
4773 // We're assuming the root element is what we're looking for, which can only be under a
4774 // bunch of namespace declarations.
4775 if (node->getChildren().size() != 1) {
4776 // Not sure what to do, bail.
4777 return false;
4778 }
4779 node = node->getChildren().itemAt(0).get();
4780 }
4781
4782 if (node->getElementNamespace().size() != 0) {
4783 // Not something we care about.
4784 return false;
4785 }
4786
4787 int versionedSdk = 0;
4788 if (node->getElementName() == String16("adaptive-icon")) {
4789 versionedSdk = SDK_O;
4790 }
4791
4792 const int minSdkVersion = getMinSdkVersion(bundle);
4793 const ConfigDescription config(target->getGroupEntry().toParams());
4794 if (versionedSdk <= minSdkVersion || versionedSdk <= config.sdkVersion) {
4795 return false;
4796 }
4797
4798 sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
4799 String16(target->getResourceType()), resourceName);
4800 if (!shouldGenerateVersionedResource(cl, config, versionedSdk)) {
4801 return false;
4802 }
4803
4804 // Remove the original entry.
4805 cl->removeEntry(config);
4806
4807 // We need to wholesale version this file.
4808 ConfigDescription newConfig(config);
4809 newConfig.sdkVersion = versionedSdk;
4810 sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
4811 AaptGroupEntry(newConfig), target->getResourceType());
4812 String8 resPath = String8::format("res/%s/%s.xml",
4813 newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
4814 String8(resourceName).string());
4815 resPath.convertToResPath();
4816
4817 // Add a resource table entry.
4818 addEntry(SourcePos(),
4819 String16(mAssets->getPackage()),
4820 String16(target->getResourceType()),
4821 resourceName,
4822 String16(resPath),
4823 NULL,
4824 &newConfig);
4825
4826 // Schedule this to be compiled.
4827 CompileResourceWorkItem item;
4828 item.resourceName = resourceName;
4829 item.resPath = resPath;
4830 item.file = newFile;
4831 item.xmlRoot = root->clone();
Adam Lesinski54b58ba2017-04-14 18:44:30 -07004832 item.needsCompiling = true;
Adam Lesinskicf1f1d92017-03-16 16:54:23 -07004833 mWorkQueue.push(item);
4834
4835 // Now mark the old entry as deleted.
4836 return true;
4837}
4838
Adam Lesinskie572c012014-09-19 15:10:04 -07004839status_t ResourceTable::modifyForCompat(const Bundle* bundle,
4840 const String16& resourceName,
4841 const sp<AaptFile>& target,
4842 const sp<XMLNode>& root) {
Adam Lesinski6e460562015-04-21 14:20:15 -07004843 const String16 vector16("vector");
4844 const String16 animatedVector16("animated-vector");
ztenghui010df882017-03-07 15:50:03 -08004845 const String16 pathInterpolator16("pathInterpolator");
ztenghui20554852017-03-21 16:28:57 -07004846 const String16 objectAnimator16("objectAnimator");
ztenghuiab2a38c2017-10-13 15:56:08 -07004847 const String16 gradient16("gradient");
Nick Butchere78a8162018-01-09 15:24:21 +00004848 const String16 animatedSelector16("animated-selector");
Adam Lesinski6e460562015-04-21 14:20:15 -07004849
Adam Lesinski28994d82015-01-13 13:42:41 -08004850 const int minSdk = getMinSdkVersion(bundle);
4851 if (minSdk >= SDK_LOLLIPOP_MR1) {
4852 // Lollipop MR1 and up handles public attributes differently, no
4853 // need to do any compat modifications.
Adam Lesinskie572c012014-09-19 15:10:04 -07004854 return NO_ERROR;
4855 }
4856
Adam Lesinski28994d82015-01-13 13:42:41 -08004857 const ConfigDescription config(target->getGroupEntry().toParams());
4858 if (target->getResourceType() == "" || config.sdkVersion >= SDK_LOLLIPOP_MR1) {
Adam Lesinski6e460562015-04-21 14:20:15 -07004859 // Skip resources that have no type (AndroidManifest.xml) or are already version qualified
4860 // with v21 or higher.
Adam Lesinskie572c012014-09-19 15:10:04 -07004861 return NO_ERROR;
4862 }
4863
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004864 sp<XMLNode> newRoot = NULL;
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004865 int sdkVersionToGenerate = SDK_LOLLIPOP_MR1;
Adam Lesinskie572c012014-09-19 15:10:04 -07004866
4867 Vector<sp<XMLNode> > nodesToVisit;
4868 nodesToVisit.push(root);
4869 while (!nodesToVisit.isEmpty()) {
4870 sp<XMLNode> node = nodesToVisit.top();
4871 nodesToVisit.pop();
4872
Adam Lesinski6e460562015-04-21 14:20:15 -07004873 if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 ||
ztenghui010df882017-03-07 15:50:03 -08004874 node->getElementName() == animatedVector16 ||
ztenghui20554852017-03-21 16:28:57 -07004875 node->getElementName() == objectAnimator16 ||
ztenghuiab2a38c2017-10-13 15:56:08 -07004876 node->getElementName() == pathInterpolator16 ||
Nick Butchere78a8162018-01-09 15:24:21 +00004877 node->getElementName() == gradient16 ||
4878 node->getElementName() == animatedSelector16)) {
Adam Lesinski6e460562015-04-21 14:20:15 -07004879 // We were told not to version vector tags, so skip the children here.
4880 continue;
4881 }
4882
Yuichi Araki4d35cca2017-01-18 20:42:17 +09004883 if (bundle->getNoVersionTransitions() && (IsTransitionElement(node->getElementName()))) {
4884 // We were told not to version transition tags, so skip the children here.
4885 continue;
4886 }
4887
Adam Lesinskie572c012014-09-19 15:10:04 -07004888 const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004889 for (size_t i = 0; i < attrs.size(); i++) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004890 const XMLNode::attribute_entry& attr = attrs[i];
Adam Lesinski28994d82015-01-13 13:42:41 -08004891 const int sdkLevel = getPublicAttributeSdkLevel(attr.nameResId);
4892 if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004893 if (newRoot == NULL) {
4894 newRoot = root->clone();
4895 }
4896
Adam Lesinski28994d82015-01-13 13:42:41 -08004897 // Find the smallest sdk version that we need to synthesize for
4898 // and do that one. Subsequent versions will be processed on
4899 // the next pass.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004900 sdkVersionToGenerate = std::min(sdkLevel, sdkVersionToGenerate);
Adam Lesinski28994d82015-01-13 13:42:41 -08004901
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004902 if (bundle->getVerbose()) {
4903 SourcePos(node->getFilename(), node->getStartLineNumber()).printf(
4904 "removing attribute %s%s%s from <%s>",
4905 String8(attr.ns).string(),
4906 (attr.ns.size() == 0 ? "" : ":"),
4907 String8(attr.name).string(),
4908 String8(node->getElementName()).string());
4909 }
4910 node->removeAttribute(i);
4911 i--;
Adam Lesinskie572c012014-09-19 15:10:04 -07004912 }
4913 }
4914
4915 // Schedule a visit to the children.
4916 const Vector<sp<XMLNode> >& children = node->getChildren();
4917 const size_t childCount = children.size();
4918 for (size_t i = 0; i < childCount; i++) {
4919 nodesToVisit.push(children[i]);
4920 }
4921 }
4922
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004923 if (newRoot == NULL) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004924 return NO_ERROR;
4925 }
4926
Adam Lesinskie572c012014-09-19 15:10:04 -07004927 // Look to see if we already have an overriding v21 configuration.
4928 sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
4929 String16(target->getResourceType()), resourceName);
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004930 if (shouldGenerateVersionedResource(cl, config, sdkVersionToGenerate)) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004931 // We don't have an overriding entry for v21, so we must duplicate this one.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004932 ConfigDescription newConfig(config);
4933 newConfig.sdkVersion = sdkVersionToGenerate;
Adam Lesinskie572c012014-09-19 15:10:04 -07004934 sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
4935 AaptGroupEntry(newConfig), target->getResourceType());
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004936 String8 resPath = String8::format("res/%s/%s.xml",
Adam Lesinskie572c012014-09-19 15:10:04 -07004937 newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004938 String8(resourceName).string());
Adam Lesinskie572c012014-09-19 15:10:04 -07004939 resPath.convertToResPath();
4940
4941 // Add a resource table entry.
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004942 if (bundle->getVerbose()) {
4943 SourcePos(target->getSourceFile(), -1).printf(
4944 "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
Adam Lesinski28994d82015-01-13 13:42:41 -08004945 newConfig.sdkVersion,
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004946 mAssets->getPackage().string(),
4947 newFile->getResourceType().string(),
4948 String8(resourceName).string(),
4949 newConfig.toString().string());
4950 }
Adam Lesinskie572c012014-09-19 15:10:04 -07004951
4952 addEntry(SourcePos(),
4953 String16(mAssets->getPackage()),
4954 String16(target->getResourceType()),
4955 resourceName,
4956 String16(resPath),
4957 NULL,
4958 &newConfig);
4959
4960 // Schedule this to be compiled.
4961 CompileResourceWorkItem item;
4962 item.resourceName = resourceName;
4963 item.resPath = resPath;
4964 item.file = newFile;
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004965 item.xmlRoot = newRoot;
4966 item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
4967 // to do it again.
Adam Lesinskie572c012014-09-19 15:10:04 -07004968 mWorkQueue.push(item);
4969 }
Adam Lesinskie572c012014-09-19 15:10:04 -07004970 return NO_ERROR;
4971}
Adam Lesinskide7de472014-11-03 12:03:08 -08004972
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004973void ResourceTable::getDensityVaryingResources(
4974 KeyedVector<Symbol, Vector<SymbolDefinition> >& resources) {
Adam Lesinskide7de472014-11-03 12:03:08 -08004975 const ConfigDescription nullConfig;
4976
4977 const size_t packageCount = mOrderedPackages.size();
4978 for (size_t p = 0; p < packageCount; p++) {
4979 const Vector<sp<Type> >& types = mOrderedPackages[p]->getOrderedTypes();
4980 const size_t typeCount = types.size();
4981 for (size_t t = 0; t < typeCount; t++) {
Adam Lesinski081d1b42016-08-15 18:45:00 -07004982 const sp<Type>& type = types[t];
4983 if (type == NULL) {
4984 continue;
4985 }
4986
4987 const Vector<sp<ConfigList> >& configs = type->getOrderedConfigs();
Adam Lesinskide7de472014-11-03 12:03:08 -08004988 const size_t configCount = configs.size();
4989 for (size_t c = 0; c < configCount; c++) {
Adam Lesinski081d1b42016-08-15 18:45:00 -07004990 const sp<ConfigList>& configList = configs[c];
4991 if (configList == NULL) {
4992 continue;
4993 }
4994
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004995 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configEntries
Adam Lesinski081d1b42016-08-15 18:45:00 -07004996 = configList->getEntries();
Adam Lesinskide7de472014-11-03 12:03:08 -08004997 const size_t configEntryCount = configEntries.size();
4998 for (size_t ce = 0; ce < configEntryCount; ce++) {
Adam Lesinski081d1b42016-08-15 18:45:00 -07004999 const sp<Entry>& entry = configEntries.valueAt(ce);
5000 if (entry == NULL) {
5001 continue;
5002 }
5003
Adam Lesinskide7de472014-11-03 12:03:08 -08005004 const ConfigDescription& config = configEntries.keyAt(ce);
5005 if (AaptConfig::isDensityOnly(config)) {
5006 // This configuration only varies with regards to density.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07005007 const Symbol symbol(
5008 mOrderedPackages[p]->getName(),
Adam Lesinski081d1b42016-08-15 18:45:00 -07005009 type->getName(),
5010 configList->getName(),
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07005011 getResId(mOrderedPackages[p], types[t],
Adam Lesinski081d1b42016-08-15 18:45:00 -07005012 configList->getEntryIndex()));
Adam Lesinskide7de472014-11-03 12:03:08 -08005013
Adam Lesinski081d1b42016-08-15 18:45:00 -07005014
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07005015 AaptUtil::appendValue(resources, symbol,
5016 SymbolDefinition(symbol, config, entry->getPos()));
Adam Lesinskide7de472014-11-03 12:03:08 -08005017 }
5018 }
5019 }
5020 }
5021 }
5022}
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07005023
5024static String16 buildNamespace(const String16& package) {
5025 return String16("http://schemas.android.com/apk/res/") + package;
5026}
5027
5028static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
5029 const Vector<sp<XMLNode> >& children = parent->getChildren();
5030 sp<XMLNode> onlyChild;
5031 for (size_t i = 0; i < children.size(); i++) {
5032 if (children[i]->getType() != XMLNode::TYPE_CDATA) {
5033 if (onlyChild != NULL) {
5034 return NULL;
5035 }
5036 onlyChild = children[i];
5037 }
5038 }
5039 return onlyChild;
5040}
5041
5042/**
5043 * Detects use of the `bundle' format and extracts nested resources into their own top level
5044 * resources. The bundle format looks like this:
5045 *
5046 * <!-- res/drawable/bundle.xml -->
5047 * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
5048 * <aapt:attr name="android:drawable">
5049 * <vector android:width="60dp"
5050 * android:height="60dp">
5051 * <path android:name="v"
5052 * android:fillColor="#000000"
5053 * android:pathData="M300,70 l 0,-70 70,..." />
5054 * </vector>
5055 * </aapt:attr>
5056 * </animated-vector>
5057 *
5058 * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
5059 * into a new high-level resource, assigning it a name and ID. Then value of the `name`
5060 * attribute must be a resource attribute. That resource attribute is inserted into the parent
5061 * with the reference to the extracted resource as the value.
5062 *
5063 * <!-- res/drawable/bundle.xml -->
5064 * <animated-vector android:drawable="@drawable/bundle_1.xml">
5065 * </animated-vector>
5066 *
5067 * <!-- res/drawable/bundle_1.xml -->
5068 * <vector android:width="60dp"
5069 * android:height="60dp">
5070 * <path android:name="v"
5071 * android:fillColor="#000000"
5072 * android:pathData="M300,70 l 0,-70 70,..." />
5073 * </vector>
5074 */
5075status_t ResourceTable::processBundleFormat(const Bundle* bundle,
5076 const String16& resourceName,
5077 const sp<AaptFile>& target,
5078 const sp<XMLNode>& root) {
5079 Vector<sp<XMLNode> > namespaces;
5080 if (root->getType() == XMLNode::TYPE_NAMESPACE) {
5081 namespaces.push(root);
5082 }
5083 return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
5084}
5085
5086status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
5087 const String16& resourceName,
5088 const sp<AaptFile>& target,
5089 const sp<XMLNode>& parent,
5090 Vector<sp<XMLNode> >* namespaces) {
5091 const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
5092 const String16 kName16("name");
5093 const String16 kAttr16("attr");
5094 const String16 kAssetPackage16(mAssets->getPackage());
5095
5096 Vector<sp<XMLNode> >& children = parent->getChildren();
5097 for (size_t i = 0; i < children.size(); i++) {
5098 const sp<XMLNode>& child = children[i];
5099
5100 if (child->getType() == XMLNode::TYPE_CDATA) {
5101 continue;
5102 } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
5103 namespaces->push(child);
5104 }
5105
5106 if (child->getElementNamespace() != kAaptNamespaceUri16 ||
5107 child->getElementName() != kAttr16) {
5108 status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
5109 namespaces);
5110 if (result != NO_ERROR) {
5111 return result;
5112 }
5113
5114 if (child->getType() == XMLNode::TYPE_NAMESPACE) {
5115 namespaces->pop();
5116 }
5117 continue;
5118 }
5119
5120 // This is the <aapt:attr> tag. Look for the 'name' attribute.
5121 SourcePos source(child->getFilename(), child->getStartLineNumber());
5122
5123 sp<XMLNode> nestedRoot = findOnlyChildElement(child);
5124 if (nestedRoot == NULL) {
5125 source.error("<%s:%s> must have exactly one child element",
5126 String8(child->getElementNamespace()).string(),
5127 String8(child->getElementName()).string());
5128 return UNKNOWN_ERROR;
5129 }
5130
5131 // Find the special attribute 'parent-attr'. This attribute's value contains
5132 // the resource attribute for which this element should be assigned in the parent.
5133 const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
5134 if (attr == NULL) {
5135 source.error("inline resource definition must specify an attribute via 'name'");
5136 return UNKNOWN_ERROR;
5137 }
5138
5139 // Parse the attribute name.
5140 const char* errorMsg = NULL;
5141 String16 attrPackage, attrType, attrName;
5142 bool result = ResTable::expandResourceRef(attr->string.string(),
5143 attr->string.size(),
5144 &attrPackage, &attrType, &attrName,
5145 &kAttr16, &kAssetPackage16,
5146 &errorMsg, NULL);
5147 if (!result) {
5148 source.error("invalid attribute name for 'name': %s", errorMsg);
5149 return UNKNOWN_ERROR;
5150 }
5151
5152 if (attrType != kAttr16) {
5153 // The value of the 'name' attribute must be an attribute reference.
5154 source.error("value of 'name' must be an attribute reference.");
5155 return UNKNOWN_ERROR;
5156 }
5157
5158 // Generate a name for this nested resource and try to add it to the table.
5159 // We do this in a loop because the name may be taken, in which case we will
5160 // increment a suffix until we succeed.
5161 String8 nestedResourceName;
5162 String8 nestedResourcePath;
5163 int suffix = 1;
5164 while (true) {
5165 // This child element will be extracted into its own resource file.
5166 // Generate a name and path for it from its parent.
5167 nestedResourceName = String8::format("%s_%d",
5168 String8(resourceName).string(), suffix++);
5169 nestedResourcePath = String8::format("res/%s/%s.xml",
5170 target->getGroupEntry().toDirName(target->getResourceType())
5171 .string(),
5172 nestedResourceName.string());
5173
5174 // Lookup or create the entry for this name.
5175 sp<Entry> entry = getEntry(kAssetPackage16,
5176 String16(target->getResourceType()),
5177 String16(nestedResourceName),
5178 source,
5179 false,
5180 &target->getGroupEntry().toParams(),
5181 true);
5182 if (entry == NULL) {
5183 return UNKNOWN_ERROR;
5184 }
5185
5186 if (entry->getType() == Entry::TYPE_UNKNOWN) {
5187 // The value for this resource has never been set,
5188 // meaning we're good!
5189 entry->setItem(source, String16(nestedResourcePath));
5190 break;
5191 }
5192
5193 // We failed (name already exists), so try with a different name
5194 // (increment the suffix).
5195 }
5196
5197 if (bundle->getVerbose()) {
5198 source.printf("generating nested resource %s:%s/%s",
5199 mAssets->getPackage().string(), target->getResourceType().string(),
5200 nestedResourceName.string());
5201 }
5202
5203 // Build the attribute reference and assign it to the parent.
5204 String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
5205 mAssets->getPackage().string(), target->getResourceType().string(),
5206 nestedResourceName.string()));
5207
5208 String16 attrNs = buildNamespace(attrPackage);
5209 if (parent->getAttribute(attrNs, attrName) != NULL) {
5210 SourcePos(parent->getFilename(), parent->getStartLineNumber())
5211 .error("parent of nested resource already defines attribute '%s:%s'",
5212 String8(attrPackage).string(), String8(attrName).string());
5213 return UNKNOWN_ERROR;
5214 }
5215
5216 // Add the reference to the inline resource.
5217 parent->addAttribute(attrNs, attrName, nestedResourceRef);
5218
5219 // Remove the <aapt:attr> child element from here.
5220 children.removeAt(i);
5221 i--;
5222
5223 // Append all namespace declarations that we've seen on this branch in the XML tree
5224 // to this resource.
5225 // We do this because the order of namespace declarations and prefix usage is determined
5226 // by the developer and we do not want to override any decisions. Be conservative.
5227 for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
5228 const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
5229 sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
5230 ns->getNamespaceUri());
5231 newNs->addChild(nestedRoot);
5232 nestedRoot = newNs;
5233 }
5234
5235 // Schedule compilation of the nested resource.
5236 CompileResourceWorkItem workItem;
5237 workItem.resPath = nestedResourcePath;
5238 workItem.resourceName = String16(nestedResourceName);
5239 workItem.xmlRoot = nestedRoot;
5240 workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
5241 target->getResourceType());
5242 mWorkQueue.push(workItem);
5243 }
5244 return NO_ERROR;
5245}