blob: beff6041b9a3a3323679cd63c08efab345f09621 [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"
13
14#include <androidfw/ResourceTypes.h>
15#include <utils/ByteOrder.h>
Adam Lesinski82a2dd82014-09-17 18:34:15 -070016#include <utils/TypeHelpers.h>
Adam Lesinski282e1812014-01-23 18:17:42 -080017#include <stdarg.h>
18
19#define NOISY(x) //x
20
Adam Lesinskie572c012014-09-19 15:10:04 -070021status_t compileXmlFile(const Bundle* bundle,
22 const sp<AaptAssets>& assets,
23 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080024 const sp<AaptFile>& target,
25 ResourceTable* table,
26 int options)
27{
28 sp<XMLNode> root = XMLNode::parse(target);
29 if (root == NULL) {
30 return UNKNOWN_ERROR;
31 }
Anton Krumina2ef5c02014-03-12 14:46:44 -070032
Adam Lesinskie572c012014-09-19 15:10:04 -070033 return compileXmlFile(bundle, assets, resourceName, root, target, table, options);
Adam Lesinski282e1812014-01-23 18:17:42 -080034}
35
Adam Lesinskie572c012014-09-19 15:10:04 -070036status_t compileXmlFile(const Bundle* bundle,
37 const sp<AaptAssets>& assets,
38 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080039 const sp<AaptFile>& target,
40 const sp<AaptFile>& outTarget,
41 ResourceTable* table,
42 int options)
43{
44 sp<XMLNode> root = XMLNode::parse(target);
45 if (root == NULL) {
46 return UNKNOWN_ERROR;
47 }
48
Adam Lesinskie572c012014-09-19 15:10:04 -070049 return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options);
Adam Lesinski282e1812014-01-23 18:17:42 -080050}
51
Adam Lesinskie572c012014-09-19 15:10:04 -070052status_t compileXmlFile(const Bundle* bundle,
53 const sp<AaptAssets>& assets,
54 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080055 const sp<XMLNode>& root,
56 const sp<AaptFile>& target,
57 ResourceTable* table,
58 int options)
59{
60 if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
61 root->removeWhitespace(true, NULL);
62 } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
63 root->removeWhitespace(false, NULL);
64 }
65
66 if ((options&XML_COMPILE_UTF8) != 0) {
67 root->setUTF8(true);
68 }
69
70 bool hasErrors = false;
71
72 if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
73 status_t err = root->assignResourceIds(assets, table);
74 if (err != NO_ERROR) {
75 hasErrors = true;
76 }
77 }
78
79 status_t err = root->parseValues(assets, table);
80 if (err != NO_ERROR) {
81 hasErrors = true;
82 }
83
84 if (hasErrors) {
85 return UNKNOWN_ERROR;
86 }
Adam Lesinskie572c012014-09-19 15:10:04 -070087
88 if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) {
89 return UNKNOWN_ERROR;
90 }
Adam Lesinski282e1812014-01-23 18:17:42 -080091
92 NOISY(printf("Input XML Resource:\n"));
93 NOISY(root->print());
94 err = root->flatten(target,
95 (options&XML_COMPILE_STRIP_COMMENTS) != 0,
96 (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
97 if (err != NO_ERROR) {
98 return err;
99 }
100
101 NOISY(printf("Output XML Resource:\n"));
102 NOISY(ResXMLTree tree;
103 tree.setTo(target->getData(), target->getSize());
104 printXMLBlock(&tree));
105
106 target->setCompressionMethod(ZipEntry::kCompressDeflated);
107
108 return err;
109}
110
111#undef NOISY
112#define NOISY(x) //x
113
114struct flag_entry
115{
116 const char16_t* name;
117 size_t nameLen;
118 uint32_t value;
119 const char* description;
120};
121
122static const char16_t referenceArray[] =
123 { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' };
124static const char16_t stringArray[] =
125 { 's', 't', 'r', 'i', 'n', 'g' };
126static const char16_t integerArray[] =
127 { 'i', 'n', 't', 'e', 'g', 'e', 'r' };
128static const char16_t booleanArray[] =
129 { 'b', 'o', 'o', 'l', 'e', 'a', 'n' };
130static const char16_t colorArray[] =
131 { 'c', 'o', 'l', 'o', 'r' };
132static const char16_t floatArray[] =
133 { 'f', 'l', 'o', 'a', 't' };
134static const char16_t dimensionArray[] =
135 { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' };
136static const char16_t fractionArray[] =
137 { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' };
138static const char16_t enumArray[] =
139 { 'e', 'n', 'u', 'm' };
140static const char16_t flagsArray[] =
141 { 'f', 'l', 'a', 'g', 's' };
142
143static const flag_entry gFormatFlags[] = {
144 { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
145 "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
146 "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
147 { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
148 "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
149 { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
150 "an integer value, such as \"<code>100</code>\"." },
151 { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
152 "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
153 { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
154 "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
155 "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
156 { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
157 "a floating point value, such as \"<code>1.2</code>\"."},
158 { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
159 "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
160 "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
161 "in (inches), mm (millimeters)." },
162 { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
163 "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
164 "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
165 "some parent container." },
166 { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
167 { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
168 { NULL, 0, 0, NULL }
169};
170
171static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' };
172
173static const flag_entry l10nRequiredFlags[] = {
174 { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL },
175 { NULL, 0, 0, NULL }
176};
177
178static const char16_t nulStr[] = { 0 };
179
180static uint32_t parse_flags(const char16_t* str, size_t len,
181 const flag_entry* flags, bool* outError = NULL)
182{
183 while (len > 0 && isspace(*str)) {
184 str++;
185 len--;
186 }
187 while (len > 0 && isspace(str[len-1])) {
188 len--;
189 }
190
191 const char16_t* const end = str + len;
192 uint32_t value = 0;
193
194 while (str < end) {
195 const char16_t* div = str;
196 while (div < end && *div != '|') {
197 div++;
198 }
199
200 const flag_entry* cur = flags;
201 while (cur->name) {
202 if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
203 value |= cur->value;
204 break;
205 }
206 cur++;
207 }
208
209 if (!cur->name) {
210 if (outError) *outError = true;
211 return 0;
212 }
213
214 str = div < end ? div+1 : div;
215 }
216
217 if (outError) *outError = false;
218 return value;
219}
220
221static String16 mayOrMust(int type, int flags)
222{
223 if ((type&(~flags)) == 0) {
224 return String16("<p>Must");
225 }
226
227 return String16("<p>May");
228}
229
230static void appendTypeInfo(ResourceTable* outTable, const String16& pkg,
231 const String16& typeName, const String16& ident, int type,
232 const flag_entry* flags)
233{
234 bool hadType = false;
235 while (flags->name) {
236 if ((type&flags->value) != 0 && flags->description != NULL) {
237 String16 fullMsg(mayOrMust(type, flags->value));
238 fullMsg.append(String16(" be "));
239 fullMsg.append(String16(flags->description));
240 outTable->appendTypeComment(pkg, typeName, ident, fullMsg);
241 hadType = true;
242 }
243 flags++;
244 }
245 if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) {
246 outTable->appendTypeComment(pkg, typeName, ident,
247 String16("<p>This may also be a reference to a resource (in the form\n"
248 "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
249 "theme attribute (in the form\n"
250 "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
251 "containing a value of this type."));
252 }
253}
254
255struct PendingAttribute
256{
257 const String16 myPackage;
258 const SourcePos sourcePos;
259 const bool appendComment;
260 int32_t type;
261 String16 ident;
262 String16 comment;
263 bool hasErrors;
264 bool added;
265
266 PendingAttribute(String16 _package, const sp<AaptFile>& in,
267 ResXMLTree& block, bool _appendComment)
268 : myPackage(_package)
269 , sourcePos(in->getPrintableSource(), block.getLineNumber())
270 , appendComment(_appendComment)
271 , type(ResTable_map::TYPE_ANY)
272 , hasErrors(false)
273 , added(false)
274 {
275 }
276
277 status_t createIfNeeded(ResourceTable* outTable)
278 {
279 if (added || hasErrors) {
280 return NO_ERROR;
281 }
282 added = true;
283
284 String16 attr16("attr");
285
286 if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
287 sourcePos.error("Attribute \"%s\" has already been defined\n",
288 String8(ident).string());
289 hasErrors = true;
290 return UNKNOWN_ERROR;
291 }
292
293 char numberStr[16];
294 sprintf(numberStr, "%d", type);
295 status_t err = outTable->addBag(sourcePos, myPackage,
296 attr16, ident, String16(""),
297 String16("^type"),
298 String16(numberStr), NULL, NULL);
299 if (err != NO_ERROR) {
300 hasErrors = true;
301 return err;
302 }
303 outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
304 //printf("Attribute %s comment: %s\n", String8(ident).string(),
305 // String8(comment).string());
306 return err;
307 }
308};
309
310static status_t compileAttribute(const sp<AaptFile>& in,
311 ResXMLTree& block,
312 const String16& myPackage,
313 ResourceTable* outTable,
314 String16* outIdent = NULL,
315 bool inStyleable = false)
316{
317 PendingAttribute attr(myPackage, in, block, inStyleable);
318
319 const String16 attr16("attr");
320 const String16 id16("id");
321
322 // Attribute type constants.
323 const String16 enum16("enum");
324 const String16 flag16("flag");
325
326 ResXMLTree::event_code_t code;
327 size_t len;
328 status_t err;
329
330 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
331 if (identIdx >= 0) {
332 attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
333 if (outIdent) {
334 *outIdent = attr.ident;
335 }
336 } else {
337 attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
338 attr.hasErrors = true;
339 }
340
341 attr.comment = String16(
342 block.getComment(&len) ? block.getComment(&len) : nulStr);
343
344 ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
345 if (typeIdx >= 0) {
346 String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len));
347 attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);
348 if (attr.type == 0) {
349 attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
350 String8(typeStr).string());
351 attr.hasErrors = true;
352 }
353 attr.createIfNeeded(outTable);
354 } else if (!inStyleable) {
355 // Attribute definitions outside of styleables always define the
356 // attribute as a generic value.
357 attr.createIfNeeded(outTable);
358 }
359
360 //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
361
362 ssize_t minIdx = block.indexOfAttribute(NULL, "min");
363 if (minIdx >= 0) {
364 String16 val = String16(block.getAttributeStringValue(minIdx, &len));
365 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
366 attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
367 String8(val).string());
368 attr.hasErrors = true;
369 }
370 attr.createIfNeeded(outTable);
371 if (!attr.hasErrors) {
372 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
373 String16(""), String16("^min"), String16(val), NULL, NULL);
374 if (err != NO_ERROR) {
375 attr.hasErrors = true;
376 }
377 }
378 }
379
380 ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
381 if (maxIdx >= 0) {
382 String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
383 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
384 attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
385 String8(val).string());
386 attr.hasErrors = true;
387 }
388 attr.createIfNeeded(outTable);
389 if (!attr.hasErrors) {
390 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
391 String16(""), String16("^max"), String16(val), NULL, NULL);
392 attr.hasErrors = true;
393 }
394 }
395
396 if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
397 attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
398 attr.hasErrors = true;
399 }
400
401 ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
402 if (l10nIdx >= 0) {
Adam Lesinski4bf58102014-11-03 11:21:19 -0800403 const char16_t* str = block.getAttributeStringValue(l10nIdx, &len);
Adam Lesinski282e1812014-01-23 18:17:42 -0800404 bool error;
405 uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
406 if (error) {
407 attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
408 String8(str).string());
409 attr.hasErrors = true;
410 }
411 attr.createIfNeeded(outTable);
412 if (!attr.hasErrors) {
413 char buf[11];
414 sprintf(buf, "%d", l10n_required);
415 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
416 String16(""), String16("^l10n"), String16(buf), NULL, NULL);
417 if (err != NO_ERROR) {
418 attr.hasErrors = true;
419 }
420 }
421 }
422
423 String16 enumOrFlagsComment;
424
425 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
426 if (code == ResXMLTree::START_TAG) {
427 uint32_t localType = 0;
428 if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
429 localType = ResTable_map::TYPE_ENUM;
430 } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
431 localType = ResTable_map::TYPE_FLAGS;
432 } else {
433 SourcePos(in->getPrintableSource(), block.getLineNumber())
434 .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
435 String8(block.getElementName(&len)).string());
436 return UNKNOWN_ERROR;
437 }
438
439 attr.createIfNeeded(outTable);
440
441 if (attr.type == ResTable_map::TYPE_ANY) {
442 // No type was explicitly stated, so supplying enum tags
443 // implicitly creates an enum or flag.
444 attr.type = 0;
445 }
446
447 if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
448 // Wasn't originally specified as an enum, so update its type.
449 attr.type |= localType;
450 if (!attr.hasErrors) {
451 char numberStr[16];
452 sprintf(numberStr, "%d", attr.type);
453 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
454 myPackage, attr16, attr.ident, String16(""),
455 String16("^type"), String16(numberStr), NULL, NULL, true);
456 if (err != NO_ERROR) {
457 attr.hasErrors = true;
458 }
459 }
460 } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
461 if (localType == ResTable_map::TYPE_ENUM) {
462 SourcePos(in->getPrintableSource(), block.getLineNumber())
463 .error("<enum> attribute can not be used inside a flags format\n");
464 attr.hasErrors = true;
465 } else {
466 SourcePos(in->getPrintableSource(), block.getLineNumber())
467 .error("<flag> attribute can not be used inside a enum format\n");
468 attr.hasErrors = true;
469 }
470 }
471
472 String16 itemIdent;
473 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
474 if (itemIdentIdx >= 0) {
475 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
476 } else {
477 SourcePos(in->getPrintableSource(), block.getLineNumber())
478 .error("A 'name' attribute is required for <enum> or <flag>\n");
479 attr.hasErrors = true;
480 }
481
482 String16 value;
483 ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
484 if (valueIdx >= 0) {
485 value = String16(block.getAttributeStringValue(valueIdx, &len));
486 } else {
487 SourcePos(in->getPrintableSource(), block.getLineNumber())
488 .error("A 'value' attribute is required for <enum> or <flag>\n");
489 attr.hasErrors = true;
490 }
491 if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
492 SourcePos(in->getPrintableSource(), block.getLineNumber())
493 .error("Tag <enum> or <flag> 'value' attribute must be a number,"
494 " not \"%s\"\n",
495 String8(value).string());
496 attr.hasErrors = true;
497 }
498
Adam Lesinski282e1812014-01-23 18:17:42 -0800499 if (!attr.hasErrors) {
500 if (enumOrFlagsComment.size() == 0) {
501 enumOrFlagsComment.append(mayOrMust(attr.type,
502 ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
503 enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
504 ? String16(" be one of the following constant values.")
505 : String16(" be one or more (separated by '|') of the following constant values."));
506 enumOrFlagsComment.append(String16("</p>\n<table>\n"
507 "<colgroup align=\"left\" />\n"
508 "<colgroup align=\"left\" />\n"
509 "<colgroup align=\"left\" />\n"
510 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>"));
511 }
512
513 enumOrFlagsComment.append(String16("\n<tr><td><code>"));
514 enumOrFlagsComment.append(itemIdent);
515 enumOrFlagsComment.append(String16("</code></td><td>"));
516 enumOrFlagsComment.append(value);
517 enumOrFlagsComment.append(String16("</td><td>"));
518 if (block.getComment(&len)) {
519 enumOrFlagsComment.append(String16(block.getComment(&len)));
520 }
521 enumOrFlagsComment.append(String16("</td></tr>"));
522
523 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
524 myPackage,
525 attr16, attr.ident, String16(""),
526 itemIdent, value, NULL, NULL, false, true);
527 if (err != NO_ERROR) {
528 attr.hasErrors = true;
529 }
530 }
531 } else if (code == ResXMLTree::END_TAG) {
532 if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
533 break;
534 }
535 if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
536 if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
537 SourcePos(in->getPrintableSource(), block.getLineNumber())
538 .error("Found tag </%s> where </enum> is expected\n",
539 String8(block.getElementName(&len)).string());
540 return UNKNOWN_ERROR;
541 }
542 } else {
543 if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
544 SourcePos(in->getPrintableSource(), block.getLineNumber())
545 .error("Found tag </%s> where </flag> is expected\n",
546 String8(block.getElementName(&len)).string());
547 return UNKNOWN_ERROR;
548 }
549 }
550 }
551 }
552
553 if (!attr.hasErrors && attr.added) {
554 appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
555 }
556
557 if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
558 enumOrFlagsComment.append(String16("\n</table>"));
559 outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
560 }
561
562
563 return NO_ERROR;
564}
565
566bool localeIsDefined(const ResTable_config& config)
567{
568 return config.locale == 0;
569}
570
571status_t parseAndAddBag(Bundle* bundle,
572 const sp<AaptFile>& in,
573 ResXMLTree* block,
574 const ResTable_config& config,
575 const String16& myPackage,
576 const String16& curType,
577 const String16& ident,
578 const String16& parentIdent,
579 const String16& itemIdent,
580 int32_t curFormat,
581 bool isFormatted,
582 const String16& product,
Anton Krumina2ef5c02014-03-12 14:46:44 -0700583 PseudolocalizationMethod pseudolocalize,
Adam Lesinski282e1812014-01-23 18:17:42 -0800584 const bool overwrite,
585 ResourceTable* outTable)
586{
587 status_t err;
588 const String16 item16("item");
Anton Krumina2ef5c02014-03-12 14:46:44 -0700589
Adam Lesinski282e1812014-01-23 18:17:42 -0800590 String16 str;
591 Vector<StringPool::entry_style_span> spans;
592 err = parseStyledString(bundle, in->getPrintableSource().string(),
593 block, item16, &str, &spans, isFormatted,
594 pseudolocalize);
595 if (err != NO_ERROR) {
596 return err;
597 }
598
599 NOISY(printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d "
600 " pid=%s, bag=%s, id=%s: %s\n",
601 config.language[0], config.language[1],
602 config.country[0], config.country[1],
603 config.orientation, config.density,
604 String8(parentIdent).string(),
605 String8(ident).string(),
606 String8(itemIdent).string(),
607 String8(str).string()));
608
609 err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
610 myPackage, curType, ident, parentIdent, itemIdent, str,
611 &spans, &config, overwrite, false, curFormat);
612 return err;
613}
614
615/*
616 * Returns true if needle is one of the elements in the comma-separated list
617 * haystack, false otherwise.
618 */
619bool isInProductList(const String16& needle, const String16& haystack) {
620 const char16_t *needle2 = needle.string();
621 const char16_t *haystack2 = haystack.string();
622 size_t needlesize = needle.size();
623
624 while (*haystack2 != '\0') {
625 if (strncmp16(haystack2, needle2, needlesize) == 0) {
626 if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') {
627 return true;
628 }
629 }
630
631 while (*haystack2 != '\0' && *haystack2 != ',') {
632 haystack2++;
633 }
634 if (*haystack2 == ',') {
635 haystack2++;
636 }
637 }
638
639 return false;
640}
641
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700642/*
643 * A simple container that holds a resource type and name. It is ordered first by type then
644 * by name.
645 */
646struct type_ident_pair_t {
647 String16 type;
648 String16 ident;
649
650 type_ident_pair_t() { };
651 type_ident_pair_t(const String16& t, const String16& i) : type(t), ident(i) { }
652 type_ident_pair_t(const type_ident_pair_t& o) : type(o.type), ident(o.ident) { }
653 inline bool operator < (const type_ident_pair_t& o) const {
654 int cmp = compare_type(type, o.type);
655 if (cmp < 0) {
656 return true;
657 } else if (cmp > 0) {
658 return false;
659 } else {
660 return strictly_order_type(ident, o.ident);
661 }
662 }
663};
664
665
Adam Lesinski282e1812014-01-23 18:17:42 -0800666status_t parseAndAddEntry(Bundle* bundle,
667 const sp<AaptFile>& in,
668 ResXMLTree* block,
669 const ResTable_config& config,
670 const String16& myPackage,
671 const String16& curType,
672 const String16& ident,
673 const String16& curTag,
674 bool curIsStyled,
675 int32_t curFormat,
676 bool isFormatted,
677 const String16& product,
Anton Krumina2ef5c02014-03-12 14:46:44 -0700678 PseudolocalizationMethod pseudolocalize,
Adam Lesinski282e1812014-01-23 18:17:42 -0800679 const bool overwrite,
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700680 KeyedVector<type_ident_pair_t, bool>* skippedResourceNames,
Adam Lesinski282e1812014-01-23 18:17:42 -0800681 ResourceTable* outTable)
682{
683 status_t err;
684
685 String16 str;
686 Vector<StringPool::entry_style_span> spans;
687 err = parseStyledString(bundle, in->getPrintableSource().string(), block,
688 curTag, &str, curIsStyled ? &spans : NULL,
689 isFormatted, pseudolocalize);
690
691 if (err < NO_ERROR) {
692 return err;
693 }
694
695 /*
696 * If a product type was specified on the command line
697 * and also in the string, and the two are not the same,
698 * return without adding the string.
699 */
700
701 const char *bundleProduct = bundle->getProduct();
702 if (bundleProduct == NULL) {
703 bundleProduct = "";
704 }
705
706 if (product.size() != 0) {
707 /*
708 * If the command-line-specified product is empty, only "default"
709 * matches. Other variants are skipped. This is so generation
710 * of the R.java file when the product is not known is predictable.
711 */
712
713 if (bundleProduct[0] == '\0') {
714 if (strcmp16(String16("default").string(), product.string()) != 0) {
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700715 /*
716 * This string has a product other than 'default'. Do not add it,
717 * but record it so that if we do not see the same string with
718 * product 'default' or no product, then report an error.
719 */
720 skippedResourceNames->replaceValueFor(
721 type_ident_pair_t(curType, ident), true);
Adam Lesinski282e1812014-01-23 18:17:42 -0800722 return NO_ERROR;
723 }
724 } else {
725 /*
726 * The command-line product is not empty.
727 * If the product for this string is on the command-line list,
728 * it matches. "default" also matches, but only if nothing
729 * else has matched already.
730 */
731
732 if (isInProductList(product, String16(bundleProduct))) {
733 ;
734 } else if (strcmp16(String16("default").string(), product.string()) == 0 &&
735 !outTable->hasBagOrEntry(myPackage, curType, ident, config)) {
736 ;
737 } else {
738 return NO_ERROR;
739 }
740 }
741 }
742
743 NOISY(printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n",
744 config.language[0], config.language[1],
745 config.country[0], config.country[1],
746 config.orientation, config.density,
747 String8(ident).string(), String8(str).string()));
748
749 err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
750 myPackage, curType, ident, str, &spans, &config,
751 false, curFormat, overwrite);
752
753 return err;
754}
755
756status_t compileResourceFile(Bundle* bundle,
757 const sp<AaptAssets>& assets,
758 const sp<AaptFile>& in,
759 const ResTable_config& defParams,
760 const bool overwrite,
761 ResourceTable* outTable)
762{
763 ResXMLTree block;
764 status_t err = parseXMLResource(in, &block, false, true);
765 if (err != NO_ERROR) {
766 return err;
767 }
768
769 // Top-level tag.
770 const String16 resources16("resources");
771
772 // Identifier declaration tags.
773 const String16 declare_styleable16("declare-styleable");
774 const String16 attr16("attr");
775
776 // Data creation organizational tags.
777 const String16 string16("string");
778 const String16 drawable16("drawable");
779 const String16 color16("color");
780 const String16 bool16("bool");
781 const String16 integer16("integer");
782 const String16 dimen16("dimen");
783 const String16 fraction16("fraction");
784 const String16 style16("style");
785 const String16 plurals16("plurals");
786 const String16 array16("array");
787 const String16 string_array16("string-array");
788 const String16 integer_array16("integer-array");
789 const String16 public16("public");
790 const String16 public_padding16("public-padding");
791 const String16 private_symbols16("private-symbols");
792 const String16 java_symbol16("java-symbol");
793 const String16 add_resource16("add-resource");
794 const String16 skip16("skip");
795 const String16 eat_comment16("eat-comment");
796
797 // Data creation tags.
798 const String16 bag16("bag");
799 const String16 item16("item");
800
801 // Attribute type constants.
802 const String16 enum16("enum");
803
804 // plural values
805 const String16 other16("other");
806 const String16 quantityOther16("^other");
807 const String16 zero16("zero");
808 const String16 quantityZero16("^zero");
809 const String16 one16("one");
810 const String16 quantityOne16("^one");
811 const String16 two16("two");
812 const String16 quantityTwo16("^two");
813 const String16 few16("few");
814 const String16 quantityFew16("^few");
815 const String16 many16("many");
816 const String16 quantityMany16("^many");
817
818 // useful attribute names and special values
819 const String16 name16("name");
820 const String16 translatable16("translatable");
821 const String16 formatted16("formatted");
822 const String16 false16("false");
823
824 const String16 myPackage(assets->getPackage());
825
826 bool hasErrors = false;
827
828 bool fileIsTranslatable = true;
829 if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) {
830 fileIsTranslatable = false;
831 }
832
833 DefaultKeyedVector<String16, uint32_t> nextPublicId(0);
834
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700835 // Stores the resource names that were skipped. Typically this happens when
836 // AAPT is invoked without a product specified and a resource has no
837 // 'default' product attribute.
838 KeyedVector<type_ident_pair_t, bool> skippedResourceNames;
839
Adam Lesinski282e1812014-01-23 18:17:42 -0800840 ResXMLTree::event_code_t code;
841 do {
842 code = block.next();
843 } while (code == ResXMLTree::START_NAMESPACE);
844
845 size_t len;
846 if (code != ResXMLTree::START_TAG) {
847 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
848 "No start tag found\n");
849 return UNKNOWN_ERROR;
850 }
851 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
852 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
853 "Invalid start tag %s\n", String8(block.getElementName(&len)).string());
854 return UNKNOWN_ERROR;
855 }
856
857 ResTable_config curParams(defParams);
858
859 ResTable_config pseudoParams(curParams);
Anton Krumina2ef5c02014-03-12 14:46:44 -0700860 pseudoParams.language[0] = 'e';
861 pseudoParams.language[1] = 'n';
862 pseudoParams.country[0] = 'X';
863 pseudoParams.country[1] = 'A';
864
865 ResTable_config pseudoBidiParams(curParams);
866 pseudoBidiParams.language[0] = 'a';
867 pseudoBidiParams.language[1] = 'r';
868 pseudoBidiParams.country[0] = 'X';
869 pseudoBidiParams.country[1] = 'B';
Adam Lesinski282e1812014-01-23 18:17:42 -0800870
Igor Viarheichyk47843df2014-05-01 17:04:39 -0700871 // We should skip resources for pseudolocales if they were
872 // already added automatically. This is a fix for a transition period when
873 // manually pseudolocalized resources may be expected.
874 // TODO: remove this check after next SDK version release.
875 if ((bundle->getPseudolocalize() & PSEUDO_ACCENTED &&
876 curParams.locale == pseudoParams.locale) ||
877 (bundle->getPseudolocalize() & PSEUDO_BIDI &&
878 curParams.locale == pseudoBidiParams.locale)) {
879 SourcePos(in->getPrintableSource(), 0).warning(
880 "Resource file %s is skipped as pseudolocalization"
881 " was done automatically.",
882 in->getPrintableSource().string());
883 return NO_ERROR;
884 }
885
Adam Lesinski282e1812014-01-23 18:17:42 -0800886 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
887 if (code == ResXMLTree::START_TAG) {
888 const String16* curTag = NULL;
889 String16 curType;
890 int32_t curFormat = ResTable_map::TYPE_ANY;
891 bool curIsBag = false;
892 bool curIsBagReplaceOnOverwrite = false;
893 bool curIsStyled = false;
894 bool curIsPseudolocalizable = false;
895 bool curIsFormatted = fileIsTranslatable;
896 bool localHasErrors = false;
897
898 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
899 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
900 && code != ResXMLTree::BAD_DOCUMENT) {
901 if (code == ResXMLTree::END_TAG) {
902 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
903 break;
904 }
905 }
906 }
907 continue;
908
909 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
910 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
911 && code != ResXMLTree::BAD_DOCUMENT) {
912 if (code == ResXMLTree::END_TAG) {
913 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
914 break;
915 }
916 }
917 }
918 continue;
919
920 } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
921 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
922
923 String16 type;
924 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
925 if (typeIdx < 0) {
926 srcPos.error("A 'type' attribute is required for <public>\n");
927 hasErrors = localHasErrors = true;
928 }
929 type = String16(block.getAttributeStringValue(typeIdx, &len));
930
931 String16 name;
932 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
933 if (nameIdx < 0) {
934 srcPos.error("A 'name' attribute is required for <public>\n");
935 hasErrors = localHasErrors = true;
936 }
937 name = String16(block.getAttributeStringValue(nameIdx, &len));
938
939 uint32_t ident = 0;
940 ssize_t identIdx = block.indexOfAttribute(NULL, "id");
941 if (identIdx >= 0) {
942 const char16_t* identStr = block.getAttributeStringValue(identIdx, &len);
943 Res_value identValue;
944 if (!ResTable::stringToInt(identStr, len, &identValue)) {
945 srcPos.error("Given 'id' attribute is not an integer: %s\n",
946 String8(block.getAttributeStringValue(identIdx, &len)).string());
947 hasErrors = localHasErrors = true;
948 } else {
949 ident = identValue.data;
950 nextPublicId.replaceValueFor(type, ident+1);
951 }
952 } else if (nextPublicId.indexOfKey(type) < 0) {
953 srcPos.error("No 'id' attribute supplied <public>,"
954 " and no previous id defined in this file.\n");
955 hasErrors = localHasErrors = true;
956 } else if (!localHasErrors) {
957 ident = nextPublicId.valueFor(type);
958 nextPublicId.replaceValueFor(type, ident+1);
959 }
960
961 if (!localHasErrors) {
962 err = outTable->addPublic(srcPos, myPackage, type, name, ident);
963 if (err < NO_ERROR) {
964 hasErrors = localHasErrors = true;
965 }
966 }
967 if (!localHasErrors) {
968 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
969 if (symbols != NULL) {
970 symbols = symbols->addNestedSymbol(String8(type), srcPos);
971 }
972 if (symbols != NULL) {
973 symbols->makeSymbolPublic(String8(name), srcPos);
974 String16 comment(
975 block.getComment(&len) ? block.getComment(&len) : nulStr);
976 symbols->appendComment(String8(name), comment, srcPos);
977 } else {
978 srcPos.error("Unable to create symbols!\n");
979 hasErrors = localHasErrors = true;
980 }
981 }
982
983 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
984 if (code == ResXMLTree::END_TAG) {
985 if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
986 break;
987 }
988 }
989 }
990 continue;
991
992 } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
993 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
994
995 String16 type;
996 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
997 if (typeIdx < 0) {
998 srcPos.error("A 'type' attribute is required for <public-padding>\n");
999 hasErrors = localHasErrors = true;
1000 }
1001 type = String16(block.getAttributeStringValue(typeIdx, &len));
1002
1003 String16 name;
1004 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1005 if (nameIdx < 0) {
1006 srcPos.error("A 'name' attribute is required for <public-padding>\n");
1007 hasErrors = localHasErrors = true;
1008 }
1009 name = String16(block.getAttributeStringValue(nameIdx, &len));
1010
1011 uint32_t start = 0;
1012 ssize_t startIdx = block.indexOfAttribute(NULL, "start");
1013 if (startIdx >= 0) {
1014 const char16_t* startStr = block.getAttributeStringValue(startIdx, &len);
1015 Res_value startValue;
1016 if (!ResTable::stringToInt(startStr, len, &startValue)) {
1017 srcPos.error("Given 'start' attribute is not an integer: %s\n",
1018 String8(block.getAttributeStringValue(startIdx, &len)).string());
1019 hasErrors = localHasErrors = true;
1020 } else {
1021 start = startValue.data;
1022 }
1023 } else if (nextPublicId.indexOfKey(type) < 0) {
1024 srcPos.error("No 'start' attribute supplied <public-padding>,"
1025 " and no previous id defined in this file.\n");
1026 hasErrors = localHasErrors = true;
1027 } else if (!localHasErrors) {
1028 start = nextPublicId.valueFor(type);
1029 }
1030
1031 uint32_t end = 0;
1032 ssize_t endIdx = block.indexOfAttribute(NULL, "end");
1033 if (endIdx >= 0) {
1034 const char16_t* endStr = block.getAttributeStringValue(endIdx, &len);
1035 Res_value endValue;
1036 if (!ResTable::stringToInt(endStr, len, &endValue)) {
1037 srcPos.error("Given 'end' attribute is not an integer: %s\n",
1038 String8(block.getAttributeStringValue(endIdx, &len)).string());
1039 hasErrors = localHasErrors = true;
1040 } else {
1041 end = endValue.data;
1042 }
1043 } else {
1044 srcPos.error("No 'end' attribute supplied <public-padding>\n");
1045 hasErrors = localHasErrors = true;
1046 }
1047
1048 if (end >= start) {
1049 nextPublicId.replaceValueFor(type, end+1);
1050 } else {
1051 srcPos.error("Padding start '%ul' is after end '%ul'\n",
1052 start, end);
1053 hasErrors = localHasErrors = true;
1054 }
1055
1056 String16 comment(
1057 block.getComment(&len) ? block.getComment(&len) : nulStr);
1058 for (uint32_t curIdent=start; curIdent<=end; curIdent++) {
1059 if (localHasErrors) {
1060 break;
1061 }
1062 String16 curName(name);
1063 char buf[64];
1064 sprintf(buf, "%d", (int)(end-curIdent+1));
1065 curName.append(String16(buf));
1066
1067 err = outTable->addEntry(srcPos, myPackage, type, curName,
1068 String16("padding"), NULL, &curParams, false,
1069 ResTable_map::TYPE_STRING, overwrite);
1070 if (err < NO_ERROR) {
1071 hasErrors = localHasErrors = true;
1072 break;
1073 }
1074 err = outTable->addPublic(srcPos, myPackage, type,
1075 curName, curIdent);
1076 if (err < NO_ERROR) {
1077 hasErrors = localHasErrors = true;
1078 break;
1079 }
1080 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1081 if (symbols != NULL) {
1082 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1083 }
1084 if (symbols != NULL) {
1085 symbols->makeSymbolPublic(String8(curName), srcPos);
1086 symbols->appendComment(String8(curName), comment, srcPos);
1087 } else {
1088 srcPos.error("Unable to create symbols!\n");
1089 hasErrors = localHasErrors = true;
1090 }
1091 }
1092
1093 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1094 if (code == ResXMLTree::END_TAG) {
1095 if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
1096 break;
1097 }
1098 }
1099 }
1100 continue;
1101
1102 } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
1103 String16 pkg;
1104 ssize_t pkgIdx = block.indexOfAttribute(NULL, "package");
1105 if (pkgIdx < 0) {
1106 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1107 "A 'package' attribute is required for <private-symbols>\n");
1108 hasErrors = localHasErrors = true;
1109 }
1110 pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
1111 if (!localHasErrors) {
1112 assets->setSymbolsPrivatePackage(String8(pkg));
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), private_symbols16.string()) == 0) {
1118 break;
1119 }
1120 }
1121 }
1122 continue;
1123
1124 } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
1125 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1126
1127 String16 type;
1128 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1129 if (typeIdx < 0) {
1130 srcPos.error("A 'type' attribute is required for <public>\n");
1131 hasErrors = localHasErrors = true;
1132 }
1133 type = String16(block.getAttributeStringValue(typeIdx, &len));
1134
1135 String16 name;
1136 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1137 if (nameIdx < 0) {
1138 srcPos.error("A 'name' attribute is required for <public>\n");
1139 hasErrors = localHasErrors = true;
1140 }
1141 name = String16(block.getAttributeStringValue(nameIdx, &len));
1142
1143 sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R"));
1144 if (symbols != NULL) {
1145 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1146 }
1147 if (symbols != NULL) {
1148 symbols->makeSymbolJavaSymbol(String8(name), srcPos);
1149 String16 comment(
1150 block.getComment(&len) ? block.getComment(&len) : nulStr);
1151 symbols->appendComment(String8(name), comment, srcPos);
1152 } else {
1153 srcPos.error("Unable to create symbols!\n");
1154 hasErrors = localHasErrors = true;
1155 }
1156
1157 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1158 if (code == ResXMLTree::END_TAG) {
1159 if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
1160 break;
1161 }
1162 }
1163 }
1164 continue;
1165
1166
1167 } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
1168 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1169
1170 String16 typeName;
1171 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1172 if (typeIdx < 0) {
1173 srcPos.error("A 'type' attribute is required for <add-resource>\n");
1174 hasErrors = localHasErrors = true;
1175 }
1176 typeName = String16(block.getAttributeStringValue(typeIdx, &len));
1177
1178 String16 name;
1179 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1180 if (nameIdx < 0) {
1181 srcPos.error("A 'name' attribute is required for <add-resource>\n");
1182 hasErrors = localHasErrors = true;
1183 }
1184 name = String16(block.getAttributeStringValue(nameIdx, &len));
1185
1186 outTable->canAddEntry(srcPos, myPackage, typeName, name);
1187
1188 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1189 if (code == ResXMLTree::END_TAG) {
1190 if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
1191 break;
1192 }
1193 }
1194 }
1195 continue;
1196
1197 } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
1198 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1199
1200 String16 ident;
1201 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
1202 if (identIdx < 0) {
1203 srcPos.error("A 'name' attribute is required for <declare-styleable>\n");
1204 hasErrors = localHasErrors = true;
1205 }
1206 ident = String16(block.getAttributeStringValue(identIdx, &len));
1207
1208 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1209 if (!localHasErrors) {
1210 if (symbols != NULL) {
1211 symbols = symbols->addNestedSymbol(String8("styleable"), srcPos);
1212 }
1213 sp<AaptSymbols> styleSymbols = symbols;
1214 if (symbols != NULL) {
1215 symbols = symbols->addNestedSymbol(String8(ident), srcPos);
1216 }
1217 if (symbols == NULL) {
1218 srcPos.error("Unable to create symbols!\n");
1219 return UNKNOWN_ERROR;
1220 }
1221
1222 String16 comment(
1223 block.getComment(&len) ? block.getComment(&len) : nulStr);
1224 styleSymbols->appendComment(String8(ident), comment, srcPos);
1225 } else {
1226 symbols = NULL;
1227 }
1228
1229 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1230 if (code == ResXMLTree::START_TAG) {
1231 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
1232 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1233 && code != ResXMLTree::BAD_DOCUMENT) {
1234 if (code == ResXMLTree::END_TAG) {
1235 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
1236 break;
1237 }
1238 }
1239 }
1240 continue;
1241 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
1242 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1243 && code != ResXMLTree::BAD_DOCUMENT) {
1244 if (code == ResXMLTree::END_TAG) {
1245 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
1246 break;
1247 }
1248 }
1249 }
1250 continue;
1251 } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) {
1252 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1253 "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n",
1254 String8(block.getElementName(&len)).string());
1255 return UNKNOWN_ERROR;
1256 }
1257
1258 String16 comment(
1259 block.getComment(&len) ? block.getComment(&len) : nulStr);
1260 String16 itemIdent;
1261 err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true);
1262 if (err != NO_ERROR) {
1263 hasErrors = localHasErrors = true;
1264 }
1265
1266 if (symbols != NULL) {
1267 SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber());
1268 symbols->addSymbol(String8(itemIdent), 0, srcPos);
1269 symbols->appendComment(String8(itemIdent), comment, srcPos);
1270 //printf("Attribute %s comment: %s\n", String8(itemIdent).string(),
1271 // String8(comment).string());
1272 }
1273 } else if (code == ResXMLTree::END_TAG) {
1274 if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
1275 break;
1276 }
1277
1278 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1279 "Found tag </%s> where </attr> is expected\n",
1280 String8(block.getElementName(&len)).string());
1281 return UNKNOWN_ERROR;
1282 }
1283 }
1284 continue;
1285
1286 } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
1287 err = compileAttribute(in, block, myPackage, outTable, NULL);
1288 if (err != NO_ERROR) {
1289 hasErrors = true;
1290 }
1291 continue;
1292
1293 } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) {
1294 curTag = &item16;
1295 ssize_t attri = block.indexOfAttribute(NULL, "type");
1296 if (attri >= 0) {
1297 curType = String16(block.getAttributeStringValue(attri, &len));
1298 ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
1299 if (formatIdx >= 0) {
1300 String16 formatStr = String16(block.getAttributeStringValue(
1301 formatIdx, &len));
1302 curFormat = parse_flags(formatStr.string(), formatStr.size(),
1303 gFormatFlags);
1304 if (curFormat == 0) {
1305 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1306 "Tag <item> 'format' attribute value \"%s\" not valid\n",
1307 String8(formatStr).string());
1308 hasErrors = localHasErrors = true;
1309 }
1310 }
1311 } else {
1312 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1313 "A 'type' attribute is required for <item>\n");
1314 hasErrors = localHasErrors = true;
1315 }
1316 curIsStyled = true;
1317 } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
1318 // Note the existence and locale of every string we process
Narayan Kamath91447d82014-01-21 15:32:36 +00001319 char rawLocale[RESTABLE_MAX_LOCALE_LEN];
1320 curParams.getBcp47Locale(rawLocale);
Adam Lesinski282e1812014-01-23 18:17:42 -08001321 String8 locale(rawLocale);
1322 String16 name;
1323 String16 translatable;
1324 String16 formatted;
1325
1326 size_t n = block.getAttributeCount();
1327 for (size_t i = 0; i < n; i++) {
1328 size_t length;
Adam Lesinski4bf58102014-11-03 11:21:19 -08001329 const char16_t* attr = block.getAttributeName(i, &length);
Adam Lesinski282e1812014-01-23 18:17:42 -08001330 if (strcmp16(attr, name16.string()) == 0) {
1331 name.setTo(block.getAttributeStringValue(i, &length));
1332 } else if (strcmp16(attr, translatable16.string()) == 0) {
1333 translatable.setTo(block.getAttributeStringValue(i, &length));
1334 } else if (strcmp16(attr, formatted16.string()) == 0) {
1335 formatted.setTo(block.getAttributeStringValue(i, &length));
1336 }
1337 }
1338
1339 if (name.size() > 0) {
1340 if (translatable == false16) {
1341 curIsFormatted = false;
1342 // Untranslatable strings must only exist in the default [empty] locale
1343 if (locale.size() > 0) {
Adam Lesinskia01a9372014-03-20 18:04:57 -07001344 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
1345 "string '%s' marked untranslatable but exists in locale '%s'\n",
1346 String8(name).string(),
Adam Lesinski282e1812014-01-23 18:17:42 -08001347 locale.string());
1348 // hasErrors = localHasErrors = true;
1349 } else {
1350 // Intentionally empty block:
1351 //
1352 // Don't add untranslatable strings to the localization table; that
1353 // way if we later see localizations of them, they'll be flagged as
1354 // having no default translation.
1355 }
1356 } else {
Adam Lesinskia01a9372014-03-20 18:04:57 -07001357 outTable->addLocalization(
1358 name,
1359 locale,
1360 SourcePos(in->getPrintableSource(), block.getLineNumber()));
Adam Lesinski282e1812014-01-23 18:17:42 -08001361 }
1362
1363 if (formatted == false16) {
1364 curIsFormatted = false;
1365 }
1366 }
1367
1368 curTag = &string16;
1369 curType = string16;
1370 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
1371 curIsStyled = true;
Igor Viarheichyk84410b02014-04-30 11:56:42 -07001372 curIsPseudolocalizable = fileIsTranslatable && (translatable != false16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001373 } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) {
1374 curTag = &drawable16;
1375 curType = drawable16;
1376 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
1377 } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) {
1378 curTag = &color16;
1379 curType = color16;
1380 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
1381 } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
1382 curTag = &bool16;
1383 curType = bool16;
1384 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
1385 } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
1386 curTag = &integer16;
1387 curType = integer16;
1388 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
1389 } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) {
1390 curTag = &dimen16;
1391 curType = dimen16;
1392 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION;
1393 } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) {
1394 curTag = &fraction16;
1395 curType = fraction16;
1396 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION;
1397 } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) {
1398 curTag = &bag16;
1399 curIsBag = true;
1400 ssize_t attri = block.indexOfAttribute(NULL, "type");
1401 if (attri >= 0) {
1402 curType = String16(block.getAttributeStringValue(attri, &len));
1403 } else {
1404 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1405 "A 'type' attribute is required for <bag>\n");
1406 hasErrors = localHasErrors = true;
1407 }
1408 } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) {
1409 curTag = &style16;
1410 curType = style16;
1411 curIsBag = true;
1412 } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) {
1413 curTag = &plurals16;
1414 curType = plurals16;
1415 curIsBag = true;
Anton Krumina2ef5c02014-03-12 14:46:44 -07001416 curIsPseudolocalizable = fileIsTranslatable;
Adam Lesinski282e1812014-01-23 18:17:42 -08001417 } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
1418 curTag = &array16;
1419 curType = array16;
1420 curIsBag = true;
1421 curIsBagReplaceOnOverwrite = true;
1422 ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
1423 if (formatIdx >= 0) {
1424 String16 formatStr = String16(block.getAttributeStringValue(
1425 formatIdx, &len));
1426 curFormat = parse_flags(formatStr.string(), formatStr.size(),
1427 gFormatFlags);
1428 if (curFormat == 0) {
1429 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1430 "Tag <array> 'format' attribute value \"%s\" not valid\n",
1431 String8(formatStr).string());
1432 hasErrors = localHasErrors = true;
1433 }
1434 }
1435 } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
1436 // Check whether these strings need valid formats.
1437 // (simplified form of what string16 does above)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001438 bool isTranslatable = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001439 size_t n = block.getAttributeCount();
Narayan Kamath9a9fa162013-12-18 13:27:30 +00001440
1441 // Pseudolocalizable by default, unless this string array isn't
1442 // translatable.
Adam Lesinski282e1812014-01-23 18:17:42 -08001443 for (size_t i = 0; i < n; i++) {
1444 size_t length;
Adam Lesinski4bf58102014-11-03 11:21:19 -08001445 const char16_t* attr = block.getAttributeName(i, &length);
Narayan Kamath9a9fa162013-12-18 13:27:30 +00001446 if (strcmp16(attr, formatted16.string()) == 0) {
Adam Lesinski4bf58102014-11-03 11:21:19 -08001447 const char16_t* value = block.getAttributeStringValue(i, &length);
Adam Lesinski282e1812014-01-23 18:17:42 -08001448 if (strcmp16(value, false16.string()) == 0) {
1449 curIsFormatted = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001450 }
Anton Krumina2ef5c02014-03-12 14:46:44 -07001451 } else if (strcmp16(attr, translatable16.string()) == 0) {
Adam Lesinski4bf58102014-11-03 11:21:19 -08001452 const char16_t* value = block.getAttributeStringValue(i, &length);
Anton Krumina2ef5c02014-03-12 14:46:44 -07001453 if (strcmp16(value, false16.string()) == 0) {
1454 isTranslatable = false;
1455 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001456 }
1457 }
1458
1459 curTag = &string_array16;
1460 curType = array16;
1461 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
1462 curIsBag = true;
1463 curIsBagReplaceOnOverwrite = true;
Anton Krumina2ef5c02014-03-12 14:46:44 -07001464 curIsPseudolocalizable = isTranslatable && fileIsTranslatable;
Adam Lesinski282e1812014-01-23 18:17:42 -08001465 } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
1466 curTag = &integer_array16;
1467 curType = array16;
1468 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
1469 curIsBag = true;
1470 curIsBagReplaceOnOverwrite = true;
1471 } else {
1472 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1473 "Found tag %s where item is expected\n",
1474 String8(block.getElementName(&len)).string());
1475 return UNKNOWN_ERROR;
1476 }
1477
1478 String16 ident;
1479 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
1480 if (identIdx >= 0) {
1481 ident = String16(block.getAttributeStringValue(identIdx, &len));
1482 } else {
1483 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1484 "A 'name' attribute is required for <%s>\n",
1485 String8(*curTag).string());
1486 hasErrors = localHasErrors = true;
1487 }
1488
1489 String16 product;
1490 identIdx = block.indexOfAttribute(NULL, "product");
1491 if (identIdx >= 0) {
1492 product = String16(block.getAttributeStringValue(identIdx, &len));
1493 }
1494
1495 String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
1496
1497 if (curIsBag) {
1498 // Figure out the parent of this bag...
1499 String16 parentIdent;
1500 ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent");
1501 if (parentIdentIdx >= 0) {
1502 parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len));
1503 } else {
1504 ssize_t sep = ident.findLast('.');
1505 if (sep >= 0) {
1506 parentIdent.setTo(ident, sep);
1507 }
1508 }
1509
1510 if (!localHasErrors) {
1511 err = outTable->startBag(SourcePos(in->getPrintableSource(),
1512 block.getLineNumber()), myPackage, curType, ident,
1513 parentIdent, &curParams,
1514 overwrite, curIsBagReplaceOnOverwrite);
1515 if (err != NO_ERROR) {
1516 hasErrors = localHasErrors = true;
1517 }
1518 }
1519
1520 ssize_t elmIndex = 0;
1521 char elmIndexStr[14];
1522 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1523 && code != ResXMLTree::BAD_DOCUMENT) {
1524
1525 if (code == ResXMLTree::START_TAG) {
1526 if (strcmp16(block.getElementName(&len), item16.string()) != 0) {
1527 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1528 "Tag <%s> can not appear inside <%s>, only <item>\n",
1529 String8(block.getElementName(&len)).string(),
1530 String8(*curTag).string());
1531 return UNKNOWN_ERROR;
1532 }
1533
1534 String16 itemIdent;
1535 if (curType == array16) {
1536 sprintf(elmIndexStr, "^index_%d", (int)elmIndex++);
1537 itemIdent = String16(elmIndexStr);
1538 } else if (curType == plurals16) {
1539 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity");
1540 if (itemIdentIdx >= 0) {
1541 String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len));
1542 if (quantity16 == other16) {
1543 itemIdent = quantityOther16;
1544 }
1545 else if (quantity16 == zero16) {
1546 itemIdent = quantityZero16;
1547 }
1548 else if (quantity16 == one16) {
1549 itemIdent = quantityOne16;
1550 }
1551 else if (quantity16 == two16) {
1552 itemIdent = quantityTwo16;
1553 }
1554 else if (quantity16 == few16) {
1555 itemIdent = quantityFew16;
1556 }
1557 else if (quantity16 == many16) {
1558 itemIdent = quantityMany16;
1559 }
1560 else {
1561 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1562 "Illegal 'quantity' attribute is <item> inside <plurals>\n");
1563 hasErrors = localHasErrors = true;
1564 }
1565 } else {
1566 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1567 "A 'quantity' attribute is required for <item> inside <plurals>\n");
1568 hasErrors = localHasErrors = true;
1569 }
1570 } else {
1571 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
1572 if (itemIdentIdx >= 0) {
1573 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
1574 } else {
1575 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1576 "A 'name' attribute is required for <item>\n");
1577 hasErrors = localHasErrors = true;
1578 }
1579 }
1580
1581 ResXMLParser::ResXMLPosition parserPosition;
1582 block.getPosition(&parserPosition);
1583
1584 err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
1585 ident, parentIdent, itemIdent, curFormat, curIsFormatted,
Anton Krumina2ef5c02014-03-12 14:46:44 -07001586 product, NO_PSEUDOLOCALIZATION, overwrite, outTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001587 if (err == NO_ERROR) {
1588 if (curIsPseudolocalizable && localeIsDefined(curParams)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001589 && bundle->getPseudolocalize() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001590 // pseudolocalize here
Anton Krumina2ef5c02014-03-12 14:46:44 -07001591 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
1592 PSEUDO_ACCENTED) {
1593 block.setPosition(parserPosition);
1594 err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
1595 curType, ident, parentIdent, itemIdent, curFormat,
1596 curIsFormatted, product, PSEUDO_ACCENTED,
1597 overwrite, outTable);
1598 }
1599 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
1600 PSEUDO_BIDI) {
1601 block.setPosition(parserPosition);
1602 err = parseAndAddBag(bundle, in, &block, pseudoBidiParams, myPackage,
1603 curType, ident, parentIdent, itemIdent, curFormat,
1604 curIsFormatted, product, PSEUDO_BIDI,
1605 overwrite, outTable);
1606 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001607 }
Anton Krumina2ef5c02014-03-12 14:46:44 -07001608 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001609 if (err != NO_ERROR) {
1610 hasErrors = localHasErrors = true;
1611 }
1612 } else if (code == ResXMLTree::END_TAG) {
1613 if (strcmp16(block.getElementName(&len), curTag->string()) != 0) {
1614 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1615 "Found tag </%s> where </%s> is expected\n",
1616 String8(block.getElementName(&len)).string(),
1617 String8(*curTag).string());
1618 return UNKNOWN_ERROR;
1619 }
1620 break;
1621 }
1622 }
1623 } else {
1624 ResXMLParser::ResXMLPosition parserPosition;
1625 block.getPosition(&parserPosition);
1626
1627 err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
1628 *curTag, curIsStyled, curFormat, curIsFormatted,
Anton Krumina2ef5c02014-03-12 14:46:44 -07001629 product, NO_PSEUDOLOCALIZATION, overwrite, &skippedResourceNames, outTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001630
1631 if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
1632 hasErrors = localHasErrors = true;
1633 }
1634 else if (err == NO_ERROR) {
1635 if (curIsPseudolocalizable && localeIsDefined(curParams)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001636 && bundle->getPseudolocalize() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001637 // pseudolocalize here
Anton Krumina2ef5c02014-03-12 14:46:44 -07001638 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
1639 PSEUDO_ACCENTED) {
1640 block.setPosition(parserPosition);
1641 err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
1642 ident, *curTag, curIsStyled, curFormat,
1643 curIsFormatted, product,
1644 PSEUDO_ACCENTED, overwrite, &skippedResourceNames, outTable);
1645 }
1646 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
1647 PSEUDO_BIDI) {
1648 block.setPosition(parserPosition);
1649 err = parseAndAddEntry(bundle, in, &block, pseudoBidiParams,
1650 myPackage, curType, ident, *curTag, curIsStyled, curFormat,
1651 curIsFormatted, product,
1652 PSEUDO_BIDI, overwrite, &skippedResourceNames, outTable);
1653 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001654 if (err != NO_ERROR) {
1655 hasErrors = localHasErrors = true;
1656 }
1657 }
1658 }
1659 }
1660
1661#if 0
1662 if (comment.size() > 0) {
1663 printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(),
1664 String8(curType).string(), String8(ident).string(),
1665 String8(comment).string());
1666 }
1667#endif
1668 if (!localHasErrors) {
1669 outTable->appendComment(myPackage, curType, ident, comment, false);
1670 }
1671 }
1672 else if (code == ResXMLTree::END_TAG) {
1673 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
1674 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1675 "Unexpected end tag %s\n", String8(block.getElementName(&len)).string());
1676 return UNKNOWN_ERROR;
1677 }
1678 }
1679 else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) {
1680 }
1681 else if (code == ResXMLTree::TEXT) {
1682 if (isWhitespace(block.getText(&len))) {
1683 continue;
1684 }
1685 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1686 "Found text \"%s\" where item tag is expected\n",
1687 String8(block.getText(&len)).string());
1688 return UNKNOWN_ERROR;
1689 }
1690 }
1691
Adam Lesinski8ff15b42013-10-07 16:54:01 -07001692 // For every resource defined, there must be exist one variant with a product attribute
1693 // set to 'default' (or no product attribute at all).
1694 // We check to see that for every resource that was ignored because of a mismatched
1695 // product attribute, some product variant of that resource was processed.
1696 for (size_t i = 0; i < skippedResourceNames.size(); i++) {
1697 if (skippedResourceNames[i]) {
1698 const type_ident_pair_t& p = skippedResourceNames.keyAt(i);
1699 if (!outTable->hasBagOrEntry(myPackage, p.type, p.ident)) {
1700 const char* bundleProduct =
1701 (bundle->getProduct() == NULL) ? "" : bundle->getProduct();
1702 fprintf(stderr, "In resource file %s: %s\n",
1703 in->getPrintableSource().string(),
1704 curParams.toString().string());
1705
1706 fprintf(stderr, "\t%s '%s' does not match product %s.\n"
1707 "\tYou may have forgotten to include a 'default' product variant"
1708 " of the resource.\n",
1709 String8(p.type).string(), String8(p.ident).string(),
1710 bundleProduct[0] == 0 ? "default" : bundleProduct);
1711 return UNKNOWN_ERROR;
1712 }
1713 }
1714 }
1715
Adam Lesinski282e1812014-01-23 18:17:42 -08001716 return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
1717}
1718
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001719ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
1720 : mAssetsPackage(assetsPackage)
1721 , mPackageType(type)
1722 , mTypeIdOffset(0)
1723 , mNumLocal(0)
1724 , mBundle(bundle)
Adam Lesinski282e1812014-01-23 18:17:42 -08001725{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001726 ssize_t packageId = -1;
1727 switch (mPackageType) {
1728 case App:
1729 case AppFeature:
1730 packageId = 0x7f;
1731 break;
1732
1733 case System:
1734 packageId = 0x01;
1735 break;
1736
1737 case SharedLibrary:
1738 packageId = 0x00;
1739 break;
1740
1741 default:
1742 assert(0);
1743 break;
1744 }
1745 sp<Package> package = new Package(mAssetsPackage, packageId);
1746 mPackages.add(assetsPackage, package);
1747 mOrderedPackages.add(package);
1748
1749 // Every resource table always has one first entry, the bag attributes.
1750 const SourcePos unknown(String8("????"), 0);
1751 getType(mAssetsPackage, String16("attr"), unknown);
1752}
1753
1754static uint32_t findLargestTypeIdForPackage(const ResTable& table, const String16& packageName) {
1755 const size_t basePackageCount = table.getBasePackageCount();
1756 for (size_t i = 0; i < basePackageCount; i++) {
1757 if (packageName == table.getBasePackageName(i)) {
1758 return table.getLastTypeIdForPackage(i);
1759 }
1760 }
1761 return 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08001762}
1763
1764status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
1765{
1766 status_t err = assets->buildIncludedResources(bundle);
1767 if (err != NO_ERROR) {
1768 return err;
1769 }
1770
Adam Lesinski282e1812014-01-23 18:17:42 -08001771 mAssets = assets;
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001772 mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
Adam Lesinski282e1812014-01-23 18:17:42 -08001773
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001774 const String8& featureAfter = bundle->getFeatureAfterPackage();
1775 if (!featureAfter.isEmpty()) {
1776 AssetManager featureAssetManager;
1777 if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
1778 fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
1779 featureAfter.string());
1780 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001781 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001782
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001783 const ResTable& featureTable = featureAssetManager.getResources(false);
1784 mTypeIdOffset = max(mTypeIdOffset,
1785 findLargestTypeIdForPackage(featureTable, mAssetsPackage));
1786 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001787
1788 return NO_ERROR;
1789}
1790
1791status_t ResourceTable::addPublic(const SourcePos& sourcePos,
1792 const String16& package,
1793 const String16& type,
1794 const String16& name,
1795 const uint32_t ident)
1796{
1797 uint32_t rid = mAssets->getIncludedResources()
1798 .identifierForName(name.string(), name.size(),
1799 type.string(), type.size(),
1800 package.string(), package.size());
1801 if (rid != 0) {
1802 sourcePos.error("Error declaring public resource %s/%s for included package %s\n",
1803 String8(type).string(), String8(name).string(),
1804 String8(package).string());
1805 return UNKNOWN_ERROR;
1806 }
1807
1808 sp<Type> t = getType(package, type, sourcePos);
1809 if (t == NULL) {
1810 return UNKNOWN_ERROR;
1811 }
1812 return t->addPublic(sourcePos, name, ident);
1813}
1814
1815status_t ResourceTable::addEntry(const SourcePos& sourcePos,
1816 const String16& package,
1817 const String16& type,
1818 const String16& name,
1819 const String16& value,
1820 const Vector<StringPool::entry_style_span>* style,
1821 const ResTable_config* params,
1822 const bool doSetIndex,
1823 const int32_t format,
1824 const bool overwrite)
1825{
Adam Lesinski282e1812014-01-23 18:17:42 -08001826 uint32_t rid = mAssets->getIncludedResources()
1827 .identifierForName(name.string(), name.size(),
1828 type.string(), type.size(),
1829 package.string(), package.size());
1830 if (rid != 0) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001831 sourcePos.error("Resource entry %s/%s is already defined in package %s.",
1832 String8(type).string(), String8(name).string(), String8(package).string());
1833 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001834 }
1835
Adam Lesinski282e1812014-01-23 18:17:42 -08001836 sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
1837 params, doSetIndex);
1838 if (e == NULL) {
1839 return UNKNOWN_ERROR;
1840 }
1841 status_t err = e->setItem(sourcePos, value, style, format, overwrite);
1842 if (err == NO_ERROR) {
1843 mNumLocal++;
1844 }
1845 return err;
1846}
1847
1848status_t ResourceTable::startBag(const SourcePos& sourcePos,
1849 const String16& package,
1850 const String16& type,
1851 const String16& name,
1852 const String16& bagParent,
1853 const ResTable_config* params,
1854 bool overlay,
1855 bool replace, bool isId)
1856{
1857 status_t result = NO_ERROR;
1858
1859 // Check for adding entries in other packages... for now we do
1860 // nothing. We need to do the right thing here to support skinning.
1861 uint32_t rid = mAssets->getIncludedResources()
1862 .identifierForName(name.string(), name.size(),
1863 type.string(), type.size(),
1864 package.string(), package.size());
1865 if (rid != 0) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001866 sourcePos.error("Resource entry %s/%s is already defined in package %s.",
1867 String8(type).string(), String8(name).string(), String8(package).string());
1868 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001869 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001870
Adam Lesinski282e1812014-01-23 18:17:42 -08001871 if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) {
1872 bool canAdd = false;
1873 sp<Package> p = mPackages.valueFor(package);
1874 if (p != NULL) {
1875 sp<Type> t = p->getTypes().valueFor(type);
1876 if (t != NULL) {
1877 if (t->getCanAddEntries().indexOf(name) >= 0) {
1878 canAdd = true;
1879 }
1880 }
1881 }
1882 if (!canAdd) {
1883 sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n",
1884 String8(name).string());
1885 return UNKNOWN_ERROR;
1886 }
1887 }
1888 sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params);
1889 if (e == NULL) {
1890 return UNKNOWN_ERROR;
1891 }
1892
1893 // If a parent is explicitly specified, set it.
1894 if (bagParent.size() > 0) {
1895 e->setParent(bagParent);
1896 }
1897
1898 if ((result = e->makeItABag(sourcePos)) != NO_ERROR) {
1899 return result;
1900 }
1901
1902 if (overlay && replace) {
1903 return e->emptyBag(sourcePos);
1904 }
1905 return result;
1906}
1907
1908status_t ResourceTable::addBag(const SourcePos& sourcePos,
1909 const String16& package,
1910 const String16& type,
1911 const String16& name,
1912 const String16& bagParent,
1913 const String16& bagKey,
1914 const String16& value,
1915 const Vector<StringPool::entry_style_span>* style,
1916 const ResTable_config* params,
1917 bool replace, bool isId, const int32_t format)
1918{
1919 // Check for adding entries in other packages... for now we do
1920 // nothing. We need to do the right thing here to support skinning.
1921 uint32_t rid = mAssets->getIncludedResources()
1922 .identifierForName(name.string(), name.size(),
1923 type.string(), type.size(),
1924 package.string(), package.size());
1925 if (rid != 0) {
1926 return NO_ERROR;
1927 }
1928
1929#if 0
1930 if (name == String16("left")) {
1931 printf("Adding bag left: file=%s, line=%d, type=%s\n",
1932 sourcePos.file.striing(), sourcePos.line, String8(type).string());
1933 }
1934#endif
1935 sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params);
1936 if (e == NULL) {
1937 return UNKNOWN_ERROR;
1938 }
1939
1940 // If a parent is explicitly specified, set it.
1941 if (bagParent.size() > 0) {
1942 e->setParent(bagParent);
1943 }
1944
1945 const bool first = e->getBag().indexOfKey(bagKey) < 0;
1946 status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format);
1947 if (err == NO_ERROR && first) {
1948 mNumLocal++;
1949 }
1950 return err;
1951}
1952
1953bool ResourceTable::hasBagOrEntry(const String16& package,
1954 const String16& type,
1955 const String16& name) const
1956{
1957 // First look for this in the included resources...
1958 uint32_t rid = mAssets->getIncludedResources()
1959 .identifierForName(name.string(), name.size(),
1960 type.string(), type.size(),
1961 package.string(), package.size());
1962 if (rid != 0) {
1963 return true;
1964 }
1965
1966 sp<Package> p = mPackages.valueFor(package);
1967 if (p != NULL) {
1968 sp<Type> t = p->getTypes().valueFor(type);
1969 if (t != NULL) {
1970 sp<ConfigList> c = t->getConfigs().valueFor(name);
1971 if (c != NULL) return true;
1972 }
1973 }
1974
1975 return false;
1976}
1977
1978bool ResourceTable::hasBagOrEntry(const String16& package,
1979 const String16& type,
1980 const String16& name,
1981 const ResTable_config& config) const
1982{
1983 // First look for this in the included resources...
1984 uint32_t rid = mAssets->getIncludedResources()
1985 .identifierForName(name.string(), name.size(),
1986 type.string(), type.size(),
1987 package.string(), package.size());
1988 if (rid != 0) {
1989 return true;
1990 }
1991
1992 sp<Package> p = mPackages.valueFor(package);
1993 if (p != NULL) {
1994 sp<Type> t = p->getTypes().valueFor(type);
1995 if (t != NULL) {
1996 sp<ConfigList> c = t->getConfigs().valueFor(name);
1997 if (c != NULL) {
1998 sp<Entry> e = c->getEntries().valueFor(config);
1999 if (e != NULL) {
2000 return true;
2001 }
2002 }
2003 }
2004 }
2005
2006 return false;
2007}
2008
2009bool ResourceTable::hasBagOrEntry(const String16& ref,
2010 const String16* defType,
2011 const String16* defPackage)
2012{
2013 String16 package, type, name;
2014 if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name,
2015 defType, defPackage ? defPackage:&mAssetsPackage, NULL)) {
2016 return false;
2017 }
2018 return hasBagOrEntry(package, type, name);
2019}
2020
2021bool ResourceTable::appendComment(const String16& package,
2022 const String16& type,
2023 const String16& name,
2024 const String16& comment,
2025 bool onlyIfEmpty)
2026{
2027 if (comment.size() <= 0) {
2028 return true;
2029 }
2030
2031 sp<Package> p = mPackages.valueFor(package);
2032 if (p != NULL) {
2033 sp<Type> t = p->getTypes().valueFor(type);
2034 if (t != NULL) {
2035 sp<ConfigList> c = t->getConfigs().valueFor(name);
2036 if (c != NULL) {
2037 c->appendComment(comment, onlyIfEmpty);
2038 return true;
2039 }
2040 }
2041 }
2042 return false;
2043}
2044
2045bool ResourceTable::appendTypeComment(const String16& package,
2046 const String16& type,
2047 const String16& name,
2048 const String16& comment)
2049{
2050 if (comment.size() <= 0) {
2051 return true;
2052 }
2053
2054 sp<Package> p = mPackages.valueFor(package);
2055 if (p != NULL) {
2056 sp<Type> t = p->getTypes().valueFor(type);
2057 if (t != NULL) {
2058 sp<ConfigList> c = t->getConfigs().valueFor(name);
2059 if (c != NULL) {
2060 c->appendTypeComment(comment);
2061 return true;
2062 }
2063 }
2064 }
2065 return false;
2066}
2067
2068void ResourceTable::canAddEntry(const SourcePos& pos,
2069 const String16& package, const String16& type, const String16& name)
2070{
2071 sp<Type> t = getType(package, type, pos);
2072 if (t != NULL) {
2073 t->canAddEntry(name);
2074 }
2075}
2076
2077size_t ResourceTable::size() const {
2078 return mPackages.size();
2079}
2080
2081size_t ResourceTable::numLocalResources() const {
2082 return mNumLocal;
2083}
2084
2085bool ResourceTable::hasResources() const {
2086 return mNumLocal > 0;
2087}
2088
Adam Lesinski27f69f42014-08-21 13:19:12 -07002089sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
2090 const bool isBase)
Adam Lesinski282e1812014-01-23 18:17:42 -08002091{
2092 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
Adam Lesinski27f69f42014-08-21 13:19:12 -07002093 status_t err = flatten(bundle, filter, data, isBase);
Adam Lesinski282e1812014-01-23 18:17:42 -08002094 return err == NO_ERROR ? data : NULL;
2095}
2096
2097inline uint32_t ResourceTable::getResId(const sp<Package>& p,
2098 const sp<Type>& t,
2099 uint32_t nameId)
2100{
2101 return makeResId(p->getAssignedId(), t->getIndex(), nameId);
2102}
2103
2104uint32_t ResourceTable::getResId(const String16& package,
2105 const String16& type,
2106 const String16& name,
2107 bool onlyPublic) const
2108{
2109 uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic);
2110 if (id != 0) return id; // cache hit
2111
Adam Lesinski282e1812014-01-23 18:17:42 -08002112 // First look for this in the included resources...
2113 uint32_t specFlags = 0;
2114 uint32_t rid = mAssets->getIncludedResources()
2115 .identifierForName(name.string(), name.size(),
2116 type.string(), type.size(),
2117 package.string(), package.size(),
2118 &specFlags);
2119 if (rid != 0) {
2120 if (onlyPublic) {
2121 if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
2122 return 0;
2123 }
2124 }
2125
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002126 return ResourceIdCache::store(package, type, name, onlyPublic, rid);
Adam Lesinski282e1812014-01-23 18:17:42 -08002127 }
2128
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002129 sp<Package> p = mPackages.valueFor(package);
2130 if (p == NULL) return 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08002131 sp<Type> t = p->getTypes().valueFor(type);
2132 if (t == NULL) return 0;
2133 sp<ConfigList> c = t->getConfigs().valueFor(name);
2134 if (c == NULL) return 0;
2135 int32_t ei = c->getEntryIndex();
2136 if (ei < 0) return 0;
2137
2138 return ResourceIdCache::store(package, type, name, onlyPublic,
2139 getResId(p, t, ei));
2140}
2141
2142uint32_t ResourceTable::getResId(const String16& ref,
2143 const String16* defType,
2144 const String16* defPackage,
2145 const char** outErrorMsg,
2146 bool onlyPublic) const
2147{
2148 String16 package, type, name;
2149 bool refOnlyPublic = true;
2150 if (!ResTable::expandResourceRef(
2151 ref.string(), ref.size(), &package, &type, &name,
2152 defType, defPackage ? defPackage:&mAssetsPackage,
2153 outErrorMsg, &refOnlyPublic)) {
2154 NOISY(printf("Expanding resource: ref=%s\n",
2155 String8(ref).string()));
2156 NOISY(printf("Expanding resource: defType=%s\n",
2157 defType ? String8(*defType).string() : "NULL"));
2158 NOISY(printf("Expanding resource: defPackage=%s\n",
2159 defPackage ? String8(*defPackage).string() : "NULL"));
2160 NOISY(printf("Expanding resource: ref=%s\n", String8(ref).string()));
2161 NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n",
2162 String8(package).string(), String8(type).string(),
2163 String8(name).string()));
2164 return 0;
2165 }
2166 uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic);
2167 NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
2168 String8(package).string(), String8(type).string(),
2169 String8(name).string(), res));
2170 if (res == 0) {
2171 if (outErrorMsg)
2172 *outErrorMsg = "No resource found that matches the given name";
2173 }
2174 return res;
2175}
2176
2177bool ResourceTable::isValidResourceName(const String16& s)
2178{
2179 const char16_t* p = s.string();
2180 bool first = true;
2181 while (*p) {
2182 if ((*p >= 'a' && *p <= 'z')
2183 || (*p >= 'A' && *p <= 'Z')
2184 || *p == '_'
2185 || (!first && *p >= '0' && *p <= '9')) {
2186 first = false;
2187 p++;
2188 continue;
2189 }
2190 return false;
2191 }
2192 return true;
2193}
2194
2195bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
2196 const String16& str,
2197 bool preserveSpaces, bool coerceType,
2198 uint32_t attrID,
2199 const Vector<StringPool::entry_style_span>* style,
2200 String16* outStr, void* accessorCookie,
2201 uint32_t attrType, const String8* configTypeName,
2202 const ConfigDescription* config)
2203{
2204 String16 finalStr;
2205
2206 bool res = true;
2207 if (style == NULL || style->size() == 0) {
2208 // Text is not styled so it can be any type... let's figure it out.
2209 res = mAssets->getIncludedResources()
2210 .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces,
2211 coerceType, attrID, NULL, &mAssetsPackage, this,
2212 accessorCookie, attrType);
2213 } else {
2214 // Styled text can only be a string, and while collecting the style
2215 // information we have already processed that string!
2216 outValue->size = sizeof(Res_value);
2217 outValue->res0 = 0;
2218 outValue->dataType = outValue->TYPE_STRING;
2219 outValue->data = 0;
2220 finalStr = str;
2221 }
2222
2223 if (!res) {
2224 return false;
2225 }
2226
2227 if (outValue->dataType == outValue->TYPE_STRING) {
2228 // Should do better merging styles.
2229 if (pool) {
2230 String8 configStr;
2231 if (config != NULL) {
2232 configStr = config->toString();
2233 } else {
2234 configStr = "(null)";
2235 }
2236 NOISY(printf("Adding to pool string style #%d config %s: %s\n",
2237 style != NULL ? style->size() : 0,
2238 configStr.string(), String8(finalStr).string()));
2239 if (style != NULL && style->size() > 0) {
2240 outValue->data = pool->add(finalStr, *style, configTypeName, config);
2241 } else {
2242 outValue->data = pool->add(finalStr, true, configTypeName, config);
2243 }
2244 } else {
2245 // Caller will fill this in later.
2246 outValue->data = 0;
2247 }
2248
2249 if (outStr) {
2250 *outStr = finalStr;
2251 }
2252
2253 }
2254
2255 return true;
2256}
2257
2258uint32_t ResourceTable::getCustomResource(
2259 const String16& package, const String16& type, const String16& name) const
2260{
2261 //printf("getCustomResource: %s %s %s\n", String8(package).string(),
2262 // String8(type).string(), String8(name).string());
2263 sp<Package> p = mPackages.valueFor(package);
2264 if (p == NULL) return 0;
2265 sp<Type> t = p->getTypes().valueFor(type);
2266 if (t == NULL) return 0;
2267 sp<ConfigList> c = t->getConfigs().valueFor(name);
2268 if (c == NULL) return 0;
2269 int32_t ei = c->getEntryIndex();
2270 if (ei < 0) return 0;
2271 return getResId(p, t, ei);
2272}
2273
2274uint32_t ResourceTable::getCustomResourceWithCreation(
2275 const String16& package, const String16& type, const String16& name,
2276 const bool createIfNotFound)
2277{
2278 uint32_t resId = getCustomResource(package, type, name);
2279 if (resId != 0 || !createIfNotFound) {
2280 return resId;
2281 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002282
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002283 if (mAssetsPackage != package) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002284 mCurrentXmlPos.error("creating resource for external package %s: %s/%s.",
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002285 String8(package).string(), String8(type).string(), String8(name).string());
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002286 if (package == String16("android")) {
2287 mCurrentXmlPos.printf("did you mean to use @+id instead of @+android:id?");
2288 }
2289 return 0;
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002290 }
2291
2292 String16 value("false");
Adam Lesinski282e1812014-01-23 18:17:42 -08002293 status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
2294 if (status == NO_ERROR) {
2295 resId = getResId(package, type, name);
2296 return resId;
2297 }
2298 return 0;
2299}
2300
2301uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const
2302{
2303 return origPackage;
2304}
2305
2306bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType)
2307{
2308 //printf("getAttributeType #%08x\n", attrID);
2309 Res_value value;
2310 if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) {
2311 //printf("getAttributeType #%08x (%s): #%08x\n", attrID,
2312 // String8(getEntry(attrID)->getName()).string(), value.data);
2313 *outType = value.data;
2314 return true;
2315 }
2316 return false;
2317}
2318
2319bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin)
2320{
2321 //printf("getAttributeMin #%08x\n", attrID);
2322 Res_value value;
2323 if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) {
2324 *outMin = value.data;
2325 return true;
2326 }
2327 return false;
2328}
2329
2330bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax)
2331{
2332 //printf("getAttributeMax #%08x\n", attrID);
2333 Res_value value;
2334 if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) {
2335 *outMax = value.data;
2336 return true;
2337 }
2338 return false;
2339}
2340
2341uint32_t ResourceTable::getAttributeL10N(uint32_t attrID)
2342{
2343 //printf("getAttributeL10N #%08x\n", attrID);
2344 Res_value value;
2345 if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) {
2346 return value.data;
2347 }
2348 return ResTable_map::L10N_NOT_REQUIRED;
2349}
2350
2351bool ResourceTable::getLocalizationSetting()
2352{
2353 return mBundle->getRequireLocalization();
2354}
2355
2356void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...)
2357{
2358 if (accessorCookie != NULL && fmt != NULL) {
2359 AccessorCookie* ac = (AccessorCookie*)accessorCookie;
2360 int retval=0;
2361 char buf[1024];
2362 va_list ap;
2363 va_start(ap, fmt);
2364 retval = vsnprintf(buf, sizeof(buf), fmt, ap);
2365 va_end(ap);
2366 ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n",
2367 buf, ac->attr.string(), ac->value.string());
2368 }
2369}
2370
2371bool ResourceTable::getAttributeKeys(
2372 uint32_t attrID, Vector<String16>* outKeys)
2373{
2374 sp<const Entry> e = getEntry(attrID);
2375 if (e != NULL) {
2376 const size_t N = e->getBag().size();
2377 for (size_t i=0; i<N; i++) {
2378 const String16& key = e->getBag().keyAt(i);
2379 if (key.size() > 0 && key.string()[0] != '^') {
2380 outKeys->add(key);
2381 }
2382 }
2383 return true;
2384 }
2385 return false;
2386}
2387
2388bool ResourceTable::getAttributeEnum(
2389 uint32_t attrID, const char16_t* name, size_t nameLen,
2390 Res_value* outValue)
2391{
2392 //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string());
2393 String16 nameStr(name, nameLen);
2394 sp<const Entry> e = getEntry(attrID);
2395 if (e != NULL) {
2396 const size_t N = e->getBag().size();
2397 for (size_t i=0; i<N; i++) {
2398 //printf("Comparing %s to %s\n", String8(name, nameLen).string(),
2399 // String8(e->getBag().keyAt(i)).string());
2400 if (e->getBag().keyAt(i) == nameStr) {
2401 return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue);
2402 }
2403 }
2404 }
2405 return false;
2406}
2407
2408bool ResourceTable::getAttributeFlags(
2409 uint32_t attrID, const char16_t* name, size_t nameLen,
2410 Res_value* outValue)
2411{
2412 outValue->dataType = Res_value::TYPE_INT_HEX;
2413 outValue->data = 0;
2414
2415 //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string());
2416 String16 nameStr(name, nameLen);
2417 sp<const Entry> e = getEntry(attrID);
2418 if (e != NULL) {
2419 const size_t N = e->getBag().size();
2420
2421 const char16_t* end = name + nameLen;
2422 const char16_t* pos = name;
2423 while (pos < end) {
2424 const char16_t* start = pos;
2425 while (pos < end && *pos != '|') {
2426 pos++;
2427 }
2428
2429 String16 nameStr(start, pos-start);
2430 size_t i;
2431 for (i=0; i<N; i++) {
2432 //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(),
2433 // String8(e->getBag().keyAt(i)).string());
2434 if (e->getBag().keyAt(i) == nameStr) {
2435 Res_value val;
2436 bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val);
2437 if (!got) {
2438 return false;
2439 }
2440 //printf("Got value: 0x%08x\n", val.data);
2441 outValue->data |= val.data;
2442 break;
2443 }
2444 }
2445
2446 if (i >= N) {
2447 // Didn't find this flag identifier.
2448 return false;
2449 }
2450 pos++;
2451 }
2452
2453 return true;
2454 }
2455 return false;
2456}
2457
2458status_t ResourceTable::assignResourceIds()
2459{
2460 const size_t N = mOrderedPackages.size();
2461 size_t pi;
2462 status_t firstError = NO_ERROR;
2463
2464 // First generate all bag attributes and assign indices.
2465 for (pi=0; pi<N; pi++) {
2466 sp<Package> p = mOrderedPackages.itemAt(pi);
2467 if (p == NULL || p->getTypes().size() == 0) {
2468 // Empty, skip!
2469 continue;
2470 }
2471
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002472 // This has no sense for packages being built as AppFeature (aka with a non-zero offset).
Adam Lesinski282e1812014-01-23 18:17:42 -08002473 status_t err = p->applyPublicTypeOrder();
2474 if (err != NO_ERROR && firstError == NO_ERROR) {
2475 firstError = err;
2476 }
2477
2478 // Generate attributes...
2479 const size_t N = p->getOrderedTypes().size();
2480 size_t ti;
2481 for (ti=0; ti<N; ti++) {
2482 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2483 if (t == NULL) {
2484 continue;
2485 }
2486 const size_t N = t->getOrderedConfigs().size();
2487 for (size_t ci=0; ci<N; ci++) {
2488 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2489 if (c == NULL) {
2490 continue;
2491 }
2492 const size_t N = c->getEntries().size();
2493 for (size_t ei=0; ei<N; ei++) {
2494 sp<Entry> e = c->getEntries().valueAt(ei);
2495 if (e == NULL) {
2496 continue;
2497 }
2498 status_t err = e->generateAttributes(this, p->getName());
2499 if (err != NO_ERROR && firstError == NO_ERROR) {
2500 firstError = err;
2501 }
2502 }
2503 }
2504 }
2505
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002506 uint32_t typeIdOffset = 0;
2507 if (mPackageType == AppFeature && p->getName() == mAssetsPackage) {
2508 typeIdOffset = mTypeIdOffset;
2509 }
2510
Adam Lesinski282e1812014-01-23 18:17:42 -08002511 const SourcePos unknown(String8("????"), 0);
2512 sp<Type> attr = p->getType(String16("attr"), unknown);
2513
2514 // Assign indices...
Adam Lesinski43a0df02014-08-18 17:14:57 -07002515 const size_t typeCount = p->getOrderedTypes().size();
2516 for (size_t ti = 0; ti < typeCount; ti++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002517 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2518 if (t == NULL) {
2519 continue;
2520 }
Adam Lesinski43a0df02014-08-18 17:14:57 -07002521
Adam Lesinski282e1812014-01-23 18:17:42 -08002522 err = t->applyPublicEntryOrder();
2523 if (err != NO_ERROR && firstError == NO_ERROR) {
2524 firstError = err;
2525 }
2526
2527 const size_t N = t->getOrderedConfigs().size();
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002528 t->setIndex(ti + 1 + typeIdOffset);
Adam Lesinski282e1812014-01-23 18:17:42 -08002529
2530 LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
2531 "First type is not attr!");
2532
2533 for (size_t ei=0; ei<N; ei++) {
2534 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
2535 if (c == NULL) {
2536 continue;
2537 }
2538 c->setEntryIndex(ei);
2539 }
2540 }
2541
2542 // Assign resource IDs to keys in bags...
Adam Lesinski43a0df02014-08-18 17:14:57 -07002543 for (size_t ti = 0; ti < typeCount; ti++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002544 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2545 if (t == NULL) {
2546 continue;
2547 }
2548 const size_t N = t->getOrderedConfigs().size();
2549 for (size_t ci=0; ci<N; ci++) {
2550 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2551 //printf("Ordered config #%d: %p\n", ci, c.get());
2552 const size_t N = c->getEntries().size();
2553 for (size_t ei=0; ei<N; ei++) {
2554 sp<Entry> e = c->getEntries().valueAt(ei);
2555 if (e == NULL) {
2556 continue;
2557 }
2558 status_t err = e->assignResourceIds(this, p->getName());
2559 if (err != NO_ERROR && firstError == NO_ERROR) {
2560 firstError = err;
2561 }
2562 }
2563 }
2564 }
2565 }
2566 return firstError;
2567}
2568
2569status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) {
2570 const size_t N = mOrderedPackages.size();
2571 size_t pi;
2572
2573 for (pi=0; pi<N; pi++) {
2574 sp<Package> p = mOrderedPackages.itemAt(pi);
2575 if (p->getTypes().size() == 0) {
2576 // Empty, skip!
2577 continue;
2578 }
2579
2580 const size_t N = p->getOrderedTypes().size();
2581 size_t ti;
2582
2583 for (ti=0; ti<N; ti++) {
2584 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2585 if (t == NULL) {
2586 continue;
2587 }
2588 const size_t N = t->getOrderedConfigs().size();
Adam Lesinski3fb8c9b2014-09-09 16:05:10 -07002589 sp<AaptSymbols> typeSymbols =
2590 outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
2591 if (typeSymbols == NULL) {
2592 return UNKNOWN_ERROR;
2593 }
2594
Adam Lesinski282e1812014-01-23 18:17:42 -08002595 for (size_t ci=0; ci<N; ci++) {
2596 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2597 if (c == NULL) {
2598 continue;
2599 }
2600 uint32_t rid = getResId(p, t, ci);
2601 if (rid == 0) {
2602 return UNKNOWN_ERROR;
2603 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002604 if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002605 typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
2606
2607 String16 comment(c->getComment());
2608 typeSymbols->appendComment(String8(c->getName()), comment, c->getPos());
Adam Lesinski8ff15b42013-10-07 16:54:01 -07002609 //printf("Type symbol [%08x] %s comment: %s\n", rid,
2610 // String8(c->getName()).string(), String8(comment).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002611 comment = c->getTypeComment();
2612 typeSymbols->appendTypeComment(String8(c->getName()), comment);
Adam Lesinski282e1812014-01-23 18:17:42 -08002613 }
2614 }
2615 }
2616 }
2617 return NO_ERROR;
2618}
2619
2620
2621void
Adam Lesinskia01a9372014-03-20 18:04:57 -07002622ResourceTable::addLocalization(const String16& name, const String8& locale, const SourcePos& src)
Adam Lesinski282e1812014-01-23 18:17:42 -08002623{
Adam Lesinskia01a9372014-03-20 18:04:57 -07002624 mLocalizations[name][locale] = src;
Adam Lesinski282e1812014-01-23 18:17:42 -08002625}
2626
2627
2628/*!
2629 * Flag various sorts of localization problems. '+' indicates checks already implemented;
2630 * '-' indicates checks that will be implemented in the future.
2631 *
2632 * + A localized string for which no default-locale version exists => warning
2633 * + A string for which no version in an explicitly-requested locale exists => warning
2634 * + A localized translation of an translateable="false" string => warning
2635 * - A localized string not provided in every locale used by the table
2636 */
2637status_t
2638ResourceTable::validateLocalizations(void)
2639{
2640 status_t err = NO_ERROR;
2641 const String8 defaultLocale;
2642
2643 // For all strings...
Adam Lesinskia01a9372014-03-20 18:04:57 -07002644 for (map<String16, map<String8, SourcePos> >::iterator nameIter = mLocalizations.begin();
Adam Lesinski282e1812014-01-23 18:17:42 -08002645 nameIter != mLocalizations.end();
2646 nameIter++) {
Adam Lesinskia01a9372014-03-20 18:04:57 -07002647 const map<String8, SourcePos>& configSrcMap = nameIter->second;
Adam Lesinski282e1812014-01-23 18:17:42 -08002648
2649 // Look for strings with no default localization
Adam Lesinskia01a9372014-03-20 18:04:57 -07002650 if (configSrcMap.count(defaultLocale) == 0) {
2651 SourcePos().warning("string '%s' has no default translation.",
2652 String8(nameIter->first).string());
2653 if (mBundle->getVerbose()) {
2654 for (map<String8, SourcePos>::const_iterator locales = configSrcMap.begin();
2655 locales != configSrcMap.end();
2656 locales++) {
2657 locales->second.printf("locale %s found", locales->first.string());
2658 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002659 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002660 // !!! TODO: throw an error here in some circumstances
2661 }
2662
2663 // Check that all requested localizations are present for this string
Adam Lesinskifab50872014-04-16 14:40:42 -07002664 if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) {
2665 const char* allConfigs = mBundle->getConfigurations().string();
Adam Lesinski282e1812014-01-23 18:17:42 -08002666 const char* start = allConfigs;
2667 const char* comma;
2668
Adam Lesinskia01a9372014-03-20 18:04:57 -07002669 set<String8> missingConfigs;
2670 AaptLocaleValue locale;
Adam Lesinski282e1812014-01-23 18:17:42 -08002671 do {
2672 String8 config;
2673 comma = strchr(start, ',');
2674 if (comma != NULL) {
2675 config.setTo(start, comma - start);
2676 start = comma + 1;
2677 } else {
2678 config.setTo(start);
2679 }
2680
Adam Lesinskia01a9372014-03-20 18:04:57 -07002681 if (!locale.initFromFilterString(config)) {
2682 continue;
2683 }
2684
Anton Krumina2ef5c02014-03-12 14:46:44 -07002685 // don't bother with the pseudolocale "en_XA" or "ar_XB"
2686 if (config != "en_XA" && config != "ar_XB") {
Adam Lesinskia01a9372014-03-20 18:04:57 -07002687 if (configSrcMap.find(config) == configSrcMap.end()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002688 // okay, no specific localization found. it's possible that we are
2689 // requiring a specific regional localization [e.g. de_DE] but there is an
2690 // available string in the generic language localization [e.g. de];
2691 // consider that string to have fulfilled the localization requirement.
2692 String8 region(config.string(), 2);
Adam Lesinskia01a9372014-03-20 18:04:57 -07002693 if (configSrcMap.find(region) == configSrcMap.end() &&
2694 configSrcMap.count(defaultLocale) == 0) {
2695 missingConfigs.insert(config);
Adam Lesinski282e1812014-01-23 18:17:42 -08002696 }
2697 }
2698 }
Adam Lesinskia01a9372014-03-20 18:04:57 -07002699 } while (comma != NULL);
2700
2701 if (!missingConfigs.empty()) {
2702 String8 configStr;
2703 for (set<String8>::iterator iter = missingConfigs.begin();
2704 iter != missingConfigs.end();
2705 iter++) {
2706 configStr.appendFormat(" %s", iter->string());
2707 }
2708 SourcePos().warning("string '%s' is missing %u required localizations:%s",
2709 String8(nameIter->first).string(),
2710 (unsigned int)missingConfigs.size(),
2711 configStr.string());
2712 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002713 }
2714 }
2715
2716 return err;
2717}
2718
Adam Lesinski27f69f42014-08-21 13:19:12 -07002719status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
2720 const sp<AaptFile>& dest,
2721 const bool isBase)
Adam Lesinski282e1812014-01-23 18:17:42 -08002722{
Adam Lesinski282e1812014-01-23 18:17:42 -08002723 const ConfigDescription nullConfig;
2724
2725 const size_t N = mOrderedPackages.size();
2726 size_t pi;
2727
2728 const static String16 mipmap16("mipmap");
2729
2730 bool useUTF8 = !bundle->getUTF16StringsOption();
2731
Adam Lesinskide898ff2014-01-29 18:20:45 -08002732 // The libraries this table references.
2733 Vector<sp<Package> > libraryPackages;
Adam Lesinski6022deb2014-08-20 14:59:19 -07002734 const ResTable& table = mAssets->getIncludedResources();
2735 const size_t basePackageCount = table.getBasePackageCount();
2736 for (size_t i = 0; i < basePackageCount; i++) {
2737 size_t packageId = table.getBasePackageId(i);
2738 String16 packageName(table.getBasePackageName(i));
2739 if (packageId > 0x01 && packageId != 0x7f &&
2740 packageName != String16("android")) {
2741 libraryPackages.add(sp<Package>(new Package(packageName, packageId)));
2742 }
2743 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08002744
Adam Lesinski282e1812014-01-23 18:17:42 -08002745 // Iterate through all data, collecting all values (strings,
2746 // references, etc).
2747 StringPool valueStrings(useUTF8);
2748 Vector<sp<Entry> > allEntries;
2749 for (pi=0; pi<N; pi++) {
2750 sp<Package> p = mOrderedPackages.itemAt(pi);
2751 if (p->getTypes().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002752 continue;
2753 }
2754
2755 StringPool typeStrings(useUTF8);
2756 StringPool keyStrings(useUTF8);
2757
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002758 ssize_t stringsAdded = 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08002759 const size_t N = p->getOrderedTypes().size();
2760 for (size_t ti=0; ti<N; ti++) {
2761 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2762 if (t == NULL) {
2763 typeStrings.add(String16("<empty>"), false);
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002764 stringsAdded++;
Adam Lesinski282e1812014-01-23 18:17:42 -08002765 continue;
2766 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002767
2768 while (stringsAdded < t->getIndex() - 1) {
2769 typeStrings.add(String16("<empty>"), false);
2770 stringsAdded++;
2771 }
2772
Adam Lesinski282e1812014-01-23 18:17:42 -08002773 const String16 typeName(t->getName());
2774 typeStrings.add(typeName, false);
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002775 stringsAdded++;
Adam Lesinski282e1812014-01-23 18:17:42 -08002776
2777 // This is a hack to tweak the sorting order of the final strings,
2778 // to put stuff that is generally not language-specific first.
2779 String8 configTypeName(typeName);
2780 if (configTypeName == "drawable" || configTypeName == "layout"
2781 || configTypeName == "color" || configTypeName == "anim"
2782 || configTypeName == "interpolator" || configTypeName == "animator"
2783 || configTypeName == "xml" || configTypeName == "menu"
2784 || configTypeName == "mipmap" || configTypeName == "raw") {
2785 configTypeName = "1complex";
2786 } else {
2787 configTypeName = "2value";
2788 }
2789
Adam Lesinski27f69f42014-08-21 13:19:12 -07002790 // mipmaps don't get filtered, so they will
2791 // allways end up in the base. Make sure they
2792 // don't end up in a split.
2793 if (typeName == mipmap16 && !isBase) {
2794 continue;
2795 }
2796
Adam Lesinski282e1812014-01-23 18:17:42 -08002797 const bool filterable = (typeName != mipmap16);
2798
2799 const size_t N = t->getOrderedConfigs().size();
2800 for (size_t ci=0; ci<N; ci++) {
2801 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2802 if (c == NULL) {
2803 continue;
2804 }
2805 const size_t N = c->getEntries().size();
2806 for (size_t ei=0; ei<N; ei++) {
2807 ConfigDescription config = c->getEntries().keyAt(ei);
Adam Lesinskifab50872014-04-16 14:40:42 -07002808 if (filterable && !filter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002809 continue;
2810 }
2811 sp<Entry> e = c->getEntries().valueAt(ei);
2812 if (e == NULL) {
2813 continue;
2814 }
2815 e->setNameIndex(keyStrings.add(e->getName(), true));
2816
2817 // If this entry has no values for other configs,
2818 // and is the default config, then it is special. Otherwise
2819 // we want to add it with the config info.
2820 ConfigDescription* valueConfig = NULL;
2821 if (N != 1 || config == nullConfig) {
2822 valueConfig = &config;
2823 }
2824
2825 status_t err = e->prepareFlatten(&valueStrings, this,
2826 &configTypeName, &config);
2827 if (err != NO_ERROR) {
2828 return err;
2829 }
2830 allEntries.add(e);
2831 }
2832 }
2833 }
2834
2835 p->setTypeStrings(typeStrings.createStringBlock());
2836 p->setKeyStrings(keyStrings.createStringBlock());
2837 }
2838
2839 if (bundle->getOutputAPKFile() != NULL) {
2840 // Now we want to sort the value strings for better locality. This will
2841 // cause the positions of the strings to change, so we need to go back
2842 // through out resource entries and update them accordingly. Only need
2843 // to do this if actually writing the output file.
2844 valueStrings.sortByConfig();
2845 for (pi=0; pi<allEntries.size(); pi++) {
2846 allEntries[pi]->remapStringValue(&valueStrings);
2847 }
2848 }
2849
2850 ssize_t strAmt = 0;
Adam Lesinskide898ff2014-01-29 18:20:45 -08002851
Adam Lesinski282e1812014-01-23 18:17:42 -08002852 // Now build the array of package chunks.
2853 Vector<sp<AaptFile> > flatPackages;
2854 for (pi=0; pi<N; pi++) {
2855 sp<Package> p = mOrderedPackages.itemAt(pi);
2856 if (p->getTypes().size() == 0) {
2857 // Empty, skip!
2858 continue;
2859 }
2860
2861 const size_t N = p->getTypeStrings().size();
2862
2863 const size_t baseSize = sizeof(ResTable_package);
2864
2865 // Start the package data.
2866 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
2867 ResTable_package* header = (ResTable_package*)data->editData(baseSize);
2868 if (header == NULL) {
2869 fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
2870 return NO_MEMORY;
2871 }
2872 memset(header, 0, sizeof(*header));
2873 header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
2874 header->header.headerSize = htods(sizeof(*header));
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002875 header->id = htodl(static_cast<uint32_t>(p->getAssignedId()));
Adam Lesinski282e1812014-01-23 18:17:42 -08002876 strcpy16_htod(header->name, p->getName().string());
2877
2878 // Write the string blocks.
2879 const size_t typeStringsStart = data->getSize();
2880 sp<AaptFile> strFile = p->getTypeStringsData();
2881 ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
2882 #if PRINT_STRING_METRICS
2883 fprintf(stderr, "**** type strings: %d\n", amt);
2884 #endif
2885 strAmt += amt;
2886 if (amt < 0) {
2887 return amt;
2888 }
2889 const size_t keyStringsStart = data->getSize();
2890 strFile = p->getKeyStringsData();
2891 amt = data->writeData(strFile->getData(), strFile->getSize());
2892 #if PRINT_STRING_METRICS
2893 fprintf(stderr, "**** key strings: %d\n", amt);
2894 #endif
2895 strAmt += amt;
2896 if (amt < 0) {
2897 return amt;
2898 }
2899
Adam Lesinski27f69f42014-08-21 13:19:12 -07002900 if (isBase) {
2901 status_t err = flattenLibraryTable(data, libraryPackages);
2902 if (err != NO_ERROR) {
2903 fprintf(stderr, "ERROR: failed to write library table\n");
2904 return err;
2905 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08002906 }
2907
Adam Lesinski282e1812014-01-23 18:17:42 -08002908 // Build the type chunks inside of this package.
2909 for (size_t ti=0; ti<N; ti++) {
2910 // Retrieve them in the same order as the type string block.
2911 size_t len;
2912 String16 typeName(p->getTypeStrings().stringAt(ti, &len));
2913 sp<Type> t = p->getTypes().valueFor(typeName);
2914 LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
2915 "Type name %s not found",
2916 String8(typeName).string());
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002917 if (t == NULL) {
2918 continue;
2919 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002920 const bool filterable = (typeName != mipmap16);
Adam Lesinski27f69f42014-08-21 13:19:12 -07002921 const bool skipEntireType = (typeName == mipmap16 && !isBase);
Adam Lesinski282e1812014-01-23 18:17:42 -08002922
2923 const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
2924
2925 // Until a non-NO_ENTRY value has been written for a resource,
2926 // that resource is invalid; validResources[i] represents
2927 // the item at t->getOrderedConfigs().itemAt(i).
2928 Vector<bool> validResources;
2929 validResources.insertAt(false, 0, N);
2930
2931 // First write the typeSpec chunk, containing information about
2932 // each resource entry in this type.
2933 {
2934 const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
2935 const size_t typeSpecStart = data->getSize();
2936 ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
2937 (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
2938 if (tsHeader == NULL) {
2939 fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n");
2940 return NO_MEMORY;
2941 }
2942 memset(tsHeader, 0, sizeof(*tsHeader));
2943 tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
2944 tsHeader->header.headerSize = htods(sizeof(*tsHeader));
2945 tsHeader->header.size = htodl(typeSpecSize);
2946 tsHeader->id = ti+1;
2947 tsHeader->entryCount = htodl(N);
2948
2949 uint32_t* typeSpecFlags = (uint32_t*)
2950 (((uint8_t*)data->editData())
2951 + typeSpecStart + sizeof(ResTable_typeSpec));
2952 memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
2953
2954 for (size_t ei=0; ei<N; ei++) {
2955 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
2956 if (cl->getPublic()) {
2957 typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
2958 }
Adam Lesinski27f69f42014-08-21 13:19:12 -07002959
2960 if (skipEntireType) {
2961 continue;
2962 }
2963
Adam Lesinski282e1812014-01-23 18:17:42 -08002964 const size_t CN = cl->getEntries().size();
2965 for (size_t ci=0; ci<CN; ci++) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002966 if (filterable && !filter->match(cl->getEntries().keyAt(ci))) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002967 continue;
2968 }
2969 for (size_t cj=ci+1; cj<CN; cj++) {
Adam Lesinskifab50872014-04-16 14:40:42 -07002970 if (filterable && !filter->match(cl->getEntries().keyAt(cj))) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002971 continue;
2972 }
2973 typeSpecFlags[ei] |= htodl(
2974 cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
2975 }
2976 }
2977 }
2978 }
2979
Adam Lesinski27f69f42014-08-21 13:19:12 -07002980 if (skipEntireType) {
2981 continue;
2982 }
2983
Adam Lesinski282e1812014-01-23 18:17:42 -08002984 // We need to write one type chunk for each configuration for
2985 // which we have entries in this type.
2986 const size_t NC = t->getUniqueConfigs().size();
2987
2988 const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
2989
2990 for (size_t ci=0; ci<NC; ci++) {
2991 ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
2992
2993 NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
2994 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
2995 "sw%ddp w%ddp h%ddp dir:%d\n",
2996 ti+1,
2997 config.mcc, config.mnc,
2998 config.language[0] ? config.language[0] : '-',
2999 config.language[1] ? config.language[1] : '-',
3000 config.country[0] ? config.country[0] : '-',
3001 config.country[1] ? config.country[1] : '-',
3002 config.orientation,
3003 config.uiMode,
3004 config.touchscreen,
3005 config.density,
3006 config.keyboard,
3007 config.inputFlags,
3008 config.navigation,
3009 config.screenWidth,
3010 config.screenHeight,
3011 config.smallestScreenWidthDp,
3012 config.screenWidthDp,
3013 config.screenHeightDp,
3014 config.layoutDirection));
3015
Adam Lesinskifab50872014-04-16 14:40:42 -07003016 if (filterable && !filter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003017 continue;
3018 }
3019
3020 const size_t typeStart = data->getSize();
3021
3022 ResTable_type* tHeader = (ResTable_type*)
3023 (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
3024 if (tHeader == NULL) {
3025 fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
3026 return NO_MEMORY;
3027 }
3028
3029 memset(tHeader, 0, sizeof(*tHeader));
3030 tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
3031 tHeader->header.headerSize = htods(sizeof(*tHeader));
3032 tHeader->id = ti+1;
3033 tHeader->entryCount = htodl(N);
3034 tHeader->entriesStart = htodl(typeSize);
3035 tHeader->config = config;
3036 NOISY(printf("Writing type %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
3037 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
3038 "sw%ddp w%ddp h%ddp dir:%d\n",
3039 ti+1,
3040 tHeader->config.mcc, tHeader->config.mnc,
3041 tHeader->config.language[0] ? tHeader->config.language[0] : '-',
3042 tHeader->config.language[1] ? tHeader->config.language[1] : '-',
3043 tHeader->config.country[0] ? tHeader->config.country[0] : '-',
3044 tHeader->config.country[1] ? tHeader->config.country[1] : '-',
3045 tHeader->config.orientation,
3046 tHeader->config.uiMode,
3047 tHeader->config.touchscreen,
3048 tHeader->config.density,
3049 tHeader->config.keyboard,
3050 tHeader->config.inputFlags,
3051 tHeader->config.navigation,
3052 tHeader->config.screenWidth,
3053 tHeader->config.screenHeight,
3054 tHeader->config.smallestScreenWidthDp,
3055 tHeader->config.screenWidthDp,
3056 tHeader->config.screenHeightDp,
3057 tHeader->config.layoutDirection));
3058 tHeader->config.swapHtoD();
3059
3060 // Build the entries inside of this type.
3061 for (size_t ei=0; ei<N; ei++) {
3062 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
3063 sp<Entry> e = cl->getEntries().valueFor(config);
3064
3065 // Set the offset for this entry in its type.
3066 uint32_t* index = (uint32_t*)
3067 (((uint8_t*)data->editData())
3068 + typeStart + sizeof(ResTable_type));
3069 if (e != NULL) {
3070 index[ei] = htodl(data->getSize()-typeStart-typeSize);
3071
3072 // Create the entry.
3073 ssize_t amt = e->flatten(bundle, data, cl->getPublic());
3074 if (amt < 0) {
3075 return amt;
3076 }
3077 validResources.editItemAt(ei) = true;
3078 } else {
3079 index[ei] = htodl(ResTable_type::NO_ENTRY);
3080 }
3081 }
3082
3083 // Fill in the rest of the type information.
3084 tHeader = (ResTable_type*)
3085 (((uint8_t*)data->editData()) + typeStart);
3086 tHeader->header.size = htodl(data->getSize()-typeStart);
3087 }
3088
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003089 // If we're building splits, then each invocation of the flattening
3090 // step will have 'missing' entries. Don't warn/error for this case.
3091 if (bundle->getSplitConfigurations().isEmpty()) {
3092 bool missing_entry = false;
3093 const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
3094 "error" : "warning";
3095 for (size_t i = 0; i < N; ++i) {
3096 if (!validResources[i]) {
3097 sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
3098 fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
3099 String8(typeName).string(), String8(c->getName()).string(),
3100 Res_MAKEID(p->getAssignedId() - 1, ti, i));
3101 missing_entry = true;
3102 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003103 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003104 if (bundle->getErrorOnMissingConfigEntry() && missing_entry) {
3105 fprintf(stderr, "Error: Missing entries, quit!\n");
3106 return NOT_ENOUGH_DATA;
3107 }
Ying Wangcd28bd32013-11-14 17:12:10 -08003108 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003109 }
3110
3111 // Fill in the rest of the package information.
3112 header = (ResTable_package*)data->editData();
3113 header->header.size = htodl(data->getSize());
3114 header->typeStrings = htodl(typeStringsStart);
3115 header->lastPublicType = htodl(p->getTypeStrings().size());
3116 header->keyStrings = htodl(keyStringsStart);
3117 header->lastPublicKey = htodl(p->getKeyStrings().size());
3118
3119 flatPackages.add(data);
3120 }
3121
3122 // And now write out the final chunks.
3123 const size_t dataStart = dest->getSize();
3124
3125 {
3126 // blah
3127 ResTable_header header;
3128 memset(&header, 0, sizeof(header));
3129 header.header.type = htods(RES_TABLE_TYPE);
3130 header.header.headerSize = htods(sizeof(header));
3131 header.packageCount = htodl(flatPackages.size());
3132 status_t err = dest->writeData(&header, sizeof(header));
3133 if (err != NO_ERROR) {
3134 fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
3135 return err;
3136 }
3137 }
3138
3139 ssize_t strStart = dest->getSize();
Adam Lesinskifab50872014-04-16 14:40:42 -07003140 status_t err = valueStrings.writeStringBlock(dest);
Adam Lesinski282e1812014-01-23 18:17:42 -08003141 if (err != NO_ERROR) {
3142 return err;
3143 }
3144
3145 ssize_t amt = (dest->getSize()-strStart);
3146 strAmt += amt;
3147 #if PRINT_STRING_METRICS
3148 fprintf(stderr, "**** value strings: %d\n", amt);
3149 fprintf(stderr, "**** total strings: %d\n", strAmt);
3150 #endif
Adam Lesinskide898ff2014-01-29 18:20:45 -08003151
Adam Lesinski282e1812014-01-23 18:17:42 -08003152 for (pi=0; pi<flatPackages.size(); pi++) {
3153 err = dest->writeData(flatPackages[pi]->getData(),
3154 flatPackages[pi]->getSize());
3155 if (err != NO_ERROR) {
3156 fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n");
3157 return err;
3158 }
3159 }
3160
3161 ResTable_header* header = (ResTable_header*)
3162 (((uint8_t*)dest->getData()) + dataStart);
3163 header->header.size = htodl(dest->getSize() - dataStart);
3164
3165 NOISY(aout << "Resource table:"
3166 << HexDump(dest->getData(), dest->getSize()) << endl);
3167
3168 #if PRINT_STRING_METRICS
3169 fprintf(stderr, "**** total resource table size: %d / %d%% strings\n",
3170 dest->getSize(), (strAmt*100)/dest->getSize());
3171 #endif
3172
3173 return NO_ERROR;
3174}
3175
Adam Lesinskide898ff2014-01-29 18:20:45 -08003176status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) {
3177 // Write out the library table if necessary
3178 if (libs.size() > 0) {
3179 NOISY(fprintf(stderr, "Writing library reference table\n"));
3180
3181 const size_t libStart = dest->getSize();
3182 const size_t count = libs.size();
Adam Lesinski6022deb2014-08-20 14:59:19 -07003183 ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange(
3184 libStart, sizeof(ResTable_lib_header));
Adam Lesinskide898ff2014-01-29 18:20:45 -08003185
3186 memset(libHeader, 0, sizeof(*libHeader));
3187 libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE);
3188 libHeader->header.headerSize = htods(sizeof(*libHeader));
3189 libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count));
3190 libHeader->count = htodl(count);
3191
3192 // Write the library entries
3193 for (size_t i = 0; i < count; i++) {
3194 const size_t entryStart = dest->getSize();
3195 sp<Package> libPackage = libs[i];
3196 NOISY(fprintf(stderr, " Entry %s -> 0x%02x\n",
3197 String8(libPackage->getName()).string(),
3198 (uint8_t)libPackage->getAssignedId()));
3199
Adam Lesinski6022deb2014-08-20 14:59:19 -07003200 ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange(
3201 entryStart, sizeof(ResTable_lib_entry));
Adam Lesinskide898ff2014-01-29 18:20:45 -08003202 memset(entry, 0, sizeof(*entry));
3203 entry->packageId = htodl(libPackage->getAssignedId());
3204 strcpy16_htod(entry->packageName, libPackage->getName().string());
3205 }
3206 }
3207 return NO_ERROR;
3208}
3209
Adam Lesinski282e1812014-01-23 18:17:42 -08003210void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp)
3211{
3212 fprintf(fp,
3213 "<!-- This file contains <public> resource definitions for all\n"
3214 " resources that were generated from the source data. -->\n"
3215 "\n"
3216 "<resources>\n");
3217
3218 writePublicDefinitions(package, fp, true);
3219 writePublicDefinitions(package, fp, false);
3220
3221 fprintf(fp,
3222 "\n"
3223 "</resources>\n");
3224}
3225
3226void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub)
3227{
3228 bool didHeader = false;
3229
3230 sp<Package> pkg = mPackages.valueFor(package);
3231 if (pkg != NULL) {
3232 const size_t NT = pkg->getOrderedTypes().size();
3233 for (size_t i=0; i<NT; i++) {
3234 sp<Type> t = pkg->getOrderedTypes().itemAt(i);
3235 if (t == NULL) {
3236 continue;
3237 }
3238
3239 bool didType = false;
3240
3241 const size_t NC = t->getOrderedConfigs().size();
3242 for (size_t j=0; j<NC; j++) {
3243 sp<ConfigList> c = t->getOrderedConfigs().itemAt(j);
3244 if (c == NULL) {
3245 continue;
3246 }
3247
3248 if (c->getPublic() != pub) {
3249 continue;
3250 }
3251
3252 if (!didType) {
3253 fprintf(fp, "\n");
3254 didType = true;
3255 }
3256 if (!didHeader) {
3257 if (pub) {
3258 fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n");
3259 fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n");
3260 } else {
3261 fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n");
3262 fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n");
3263 }
3264 didHeader = true;
3265 }
3266 if (!pub) {
3267 const size_t NE = c->getEntries().size();
3268 for (size_t k=0; k<NE; k++) {
3269 const SourcePos& pos = c->getEntries().valueAt(k)->getPos();
3270 if (pos.file != "") {
3271 fprintf(fp," <!-- Declared at %s:%d -->\n",
3272 pos.file.string(), pos.line);
3273 }
3274 }
3275 }
3276 fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n",
3277 String8(t->getName()).string(),
3278 String8(c->getName()).string(),
3279 getResId(pkg, t, c->getEntryIndex()));
3280 }
3281 }
3282 }
3283}
3284
3285ResourceTable::Item::Item(const SourcePos& _sourcePos,
3286 bool _isId,
3287 const String16& _value,
3288 const Vector<StringPool::entry_style_span>* _style,
3289 int32_t _format)
3290 : sourcePos(_sourcePos)
3291 , isId(_isId)
3292 , value(_value)
3293 , format(_format)
3294 , bagKeyId(0)
3295 , evaluating(false)
3296{
3297 if (_style) {
3298 style = *_style;
3299 }
3300}
3301
Adam Lesinski82a2dd82014-09-17 18:34:15 -07003302ResourceTable::Entry::Entry(const Entry& entry)
3303 : RefBase()
3304 , mName(entry.mName)
3305 , mParent(entry.mParent)
3306 , mType(entry.mType)
3307 , mItem(entry.mItem)
3308 , mItemFormat(entry.mItemFormat)
3309 , mBag(entry.mBag)
3310 , mNameIndex(entry.mNameIndex)
3311 , mParentId(entry.mParentId)
3312 , mPos(entry.mPos) {}
3313
Adam Lesinski978ab9d2014-09-24 19:02:52 -07003314ResourceTable::Entry& ResourceTable::Entry::operator=(const Entry& entry) {
3315 mName = entry.mName;
3316 mParent = entry.mParent;
3317 mType = entry.mType;
3318 mItem = entry.mItem;
3319 mItemFormat = entry.mItemFormat;
3320 mBag = entry.mBag;
3321 mNameIndex = entry.mNameIndex;
3322 mParentId = entry.mParentId;
3323 mPos = entry.mPos;
3324 return *this;
3325}
3326
Adam Lesinski282e1812014-01-23 18:17:42 -08003327status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
3328{
3329 if (mType == TYPE_BAG) {
3330 return NO_ERROR;
3331 }
3332 if (mType == TYPE_UNKNOWN) {
3333 mType = TYPE_BAG;
3334 return NO_ERROR;
3335 }
3336 sourcePos.error("Resource entry %s is already defined as a single item.\n"
3337 "%s:%d: Originally defined here.\n",
3338 String8(mName).string(),
3339 mItem.sourcePos.file.string(), mItem.sourcePos.line);
3340 return UNKNOWN_ERROR;
3341}
3342
3343status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
3344 const String16& value,
3345 const Vector<StringPool::entry_style_span>* style,
3346 int32_t format,
3347 const bool overwrite)
3348{
3349 Item item(sourcePos, false, value, style);
3350
3351 if (mType == TYPE_BAG) {
Adam Lesinski43a0df02014-08-18 17:14:57 -07003352 if (mBag.size() == 0) {
3353 sourcePos.error("Resource entry %s is already defined as a bag.",
3354 String8(mName).string());
3355 } else {
3356 const Item& item(mBag.valueAt(0));
3357 sourcePos.error("Resource entry %s is already defined as a bag.\n"
3358 "%s:%d: Originally defined here.\n",
3359 String8(mName).string(),
3360 item.sourcePos.file.string(), item.sourcePos.line);
3361 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003362 return UNKNOWN_ERROR;
3363 }
3364 if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
3365 sourcePos.error("Resource entry %s is already defined.\n"
3366 "%s:%d: Originally defined here.\n",
3367 String8(mName).string(),
3368 mItem.sourcePos.file.string(), mItem.sourcePos.line);
3369 return UNKNOWN_ERROR;
3370 }
3371
3372 mType = TYPE_ITEM;
3373 mItem = item;
3374 mItemFormat = format;
3375 return NO_ERROR;
3376}
3377
3378status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
3379 const String16& key, const String16& value,
3380 const Vector<StringPool::entry_style_span>* style,
3381 bool replace, bool isId, int32_t format)
3382{
3383 status_t err = makeItABag(sourcePos);
3384 if (err != NO_ERROR) {
3385 return err;
3386 }
3387
3388 Item item(sourcePos, isId, value, style, format);
3389
3390 // XXX NOTE: there is an error if you try to have a bag with two keys,
3391 // one an attr and one an id, with the same name. Not something we
3392 // currently ever have to worry about.
3393 ssize_t origKey = mBag.indexOfKey(key);
3394 if (origKey >= 0) {
3395 if (!replace) {
3396 const Item& item(mBag.valueAt(origKey));
3397 sourcePos.error("Resource entry %s already has bag item %s.\n"
3398 "%s:%d: Originally defined here.\n",
3399 String8(mName).string(), String8(key).string(),
3400 item.sourcePos.file.string(), item.sourcePos.line);
3401 return UNKNOWN_ERROR;
3402 }
3403 //printf("Replacing %s with %s\n",
3404 // String8(mBag.valueFor(key).value).string(), String8(value).string());
3405 mBag.replaceValueFor(key, item);
3406 }
3407
3408 mBag.add(key, item);
3409 return NO_ERROR;
3410}
3411
Adam Lesinski82a2dd82014-09-17 18:34:15 -07003412status_t ResourceTable::Entry::removeFromBag(const String16& key) {
3413 if (mType != Entry::TYPE_BAG) {
3414 return NO_ERROR;
3415 }
3416
3417 if (mBag.removeItem(key) >= 0) {
3418 return NO_ERROR;
3419 }
3420 return UNKNOWN_ERROR;
3421}
3422
Adam Lesinski282e1812014-01-23 18:17:42 -08003423status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos)
3424{
3425 status_t err = makeItABag(sourcePos);
3426 if (err != NO_ERROR) {
3427 return err;
3428 }
3429
3430 mBag.clear();
3431 return NO_ERROR;
3432}
3433
3434status_t ResourceTable::Entry::generateAttributes(ResourceTable* table,
3435 const String16& package)
3436{
3437 const String16 attr16("attr");
3438 const String16 id16("id");
3439 const size_t N = mBag.size();
3440 for (size_t i=0; i<N; i++) {
3441 const String16& key = mBag.keyAt(i);
3442 const Item& it = mBag.valueAt(i);
3443 if (it.isId) {
3444 if (!table->hasBagOrEntry(key, &id16, &package)) {
3445 String16 value("false");
Adam Lesinski43a0df02014-08-18 17:14:57 -07003446 NOISY(fprintf(stderr, "Generating %s:id/%s\n",
3447 String8(package).string(),
3448 String8(key).string()));
Adam Lesinski282e1812014-01-23 18:17:42 -08003449 status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package,
3450 id16, key, value);
3451 if (err != NO_ERROR) {
3452 return err;
3453 }
3454 }
3455 } else if (!table->hasBagOrEntry(key, &attr16, &package)) {
3456
3457#if 1
3458// fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n",
3459// String8(key).string());
3460// const Item& item(mBag.valueAt(i));
3461// fprintf(stderr, "Referenced from file %s line %d\n",
3462// item.sourcePos.file.string(), item.sourcePos.line);
3463// return UNKNOWN_ERROR;
3464#else
3465 char numberStr[16];
3466 sprintf(numberStr, "%d", ResTable_map::TYPE_ANY);
3467 status_t err = table->addBag(SourcePos("<generated>", 0), package,
3468 attr16, key, String16(""),
3469 String16("^type"),
3470 String16(numberStr), NULL, NULL);
3471 if (err != NO_ERROR) {
3472 return err;
3473 }
3474#endif
3475 }
3476 }
3477 return NO_ERROR;
3478}
3479
3480status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
3481 const String16& package)
3482{
3483 bool hasErrors = false;
3484
3485 if (mType == TYPE_BAG) {
3486 const char* errorMsg;
3487 const String16 style16("style");
3488 const String16 attr16("attr");
3489 const String16 id16("id");
3490 mParentId = 0;
3491 if (mParent.size() > 0) {
3492 mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
3493 if (mParentId == 0) {
3494 mPos.error("Error retrieving parent for item: %s '%s'.\n",
3495 errorMsg, String8(mParent).string());
3496 hasErrors = true;
3497 }
3498 }
3499 const size_t N = mBag.size();
3500 for (size_t i=0; i<N; i++) {
3501 const String16& key = mBag.keyAt(i);
3502 Item& it = mBag.editValueAt(i);
3503 it.bagKeyId = table->getResId(key,
3504 it.isId ? &id16 : &attr16, NULL, &errorMsg);
3505 //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId);
3506 if (it.bagKeyId == 0) {
3507 it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
3508 String8(it.isId ? id16 : attr16).string(),
3509 String8(key).string());
3510 hasErrors = true;
3511 }
3512 }
3513 }
3514 return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
3515}
3516
3517status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table,
3518 const String8* configTypeName, const ConfigDescription* config)
3519{
3520 if (mType == TYPE_ITEM) {
3521 Item& it = mItem;
3522 AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
3523 if (!table->stringToValue(&it.parsedValue, strings,
3524 it.value, false, true, 0,
3525 &it.style, NULL, &ac, mItemFormat,
3526 configTypeName, config)) {
3527 return UNKNOWN_ERROR;
3528 }
3529 } else if (mType == TYPE_BAG) {
3530 const size_t N = mBag.size();
3531 for (size_t i=0; i<N; i++) {
3532 const String16& key = mBag.keyAt(i);
3533 Item& it = mBag.editValueAt(i);
3534 AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
3535 if (!table->stringToValue(&it.parsedValue, strings,
3536 it.value, false, true, it.bagKeyId,
3537 &it.style, NULL, &ac, it.format,
3538 configTypeName, config)) {
3539 return UNKNOWN_ERROR;
3540 }
3541 }
3542 } else {
3543 mPos.error("Error: entry %s is not a single item or a bag.\n",
3544 String8(mName).string());
3545 return UNKNOWN_ERROR;
3546 }
3547 return NO_ERROR;
3548}
3549
3550status_t ResourceTable::Entry::remapStringValue(StringPool* strings)
3551{
3552 if (mType == TYPE_ITEM) {
3553 Item& it = mItem;
3554 if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
3555 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
3556 }
3557 } else if (mType == TYPE_BAG) {
3558 const size_t N = mBag.size();
3559 for (size_t i=0; i<N; i++) {
3560 Item& it = mBag.editValueAt(i);
3561 if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
3562 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
3563 }
3564 }
3565 } else {
3566 mPos.error("Error: entry %s is not a single item or a bag.\n",
3567 String8(mName).string());
3568 return UNKNOWN_ERROR;
3569 }
3570 return NO_ERROR;
3571}
3572
3573ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic)
3574{
3575 size_t amt = 0;
3576 ResTable_entry header;
3577 memset(&header, 0, sizeof(header));
3578 header.size = htods(sizeof(header));
3579 const type ty = this != NULL ? mType : TYPE_ITEM;
3580 if (this != NULL) {
3581 if (ty == TYPE_BAG) {
3582 header.flags |= htods(header.FLAG_COMPLEX);
3583 }
3584 if (isPublic) {
3585 header.flags |= htods(header.FLAG_PUBLIC);
3586 }
3587 header.key.index = htodl(mNameIndex);
3588 }
3589 if (ty != TYPE_BAG) {
3590 status_t err = data->writeData(&header, sizeof(header));
3591 if (err != NO_ERROR) {
3592 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
3593 return err;
3594 }
3595
3596 const Item& it = mItem;
3597 Res_value par;
3598 memset(&par, 0, sizeof(par));
3599 par.size = htods(it.parsedValue.size);
3600 par.dataType = it.parsedValue.dataType;
3601 par.res0 = it.parsedValue.res0;
3602 par.data = htodl(it.parsedValue.data);
3603 #if 0
3604 printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n",
3605 String8(mName).string(), it.parsedValue.dataType,
3606 it.parsedValue.data, par.res0);
3607 #endif
3608 err = data->writeData(&par, it.parsedValue.size);
3609 if (err != NO_ERROR) {
3610 fprintf(stderr, "ERROR: out of memory creating Res_value\n");
3611 return err;
3612 }
3613 amt += it.parsedValue.size;
3614 } else {
3615 size_t N = mBag.size();
3616 size_t i;
3617 // Create correct ordering of items.
3618 KeyedVector<uint32_t, const Item*> items;
3619 for (i=0; i<N; i++) {
3620 const Item& it = mBag.valueAt(i);
3621 items.add(it.bagKeyId, &it);
3622 }
3623 N = items.size();
3624
3625 ResTable_map_entry mapHeader;
3626 memcpy(&mapHeader, &header, sizeof(header));
3627 mapHeader.size = htods(sizeof(mapHeader));
3628 mapHeader.parent.ident = htodl(mParentId);
3629 mapHeader.count = htodl(N);
3630 status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
3631 if (err != NO_ERROR) {
3632 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
3633 return err;
3634 }
3635
3636 for (i=0; i<N; i++) {
3637 const Item& it = *items.valueAt(i);
3638 ResTable_map map;
3639 map.name.ident = htodl(it.bagKeyId);
3640 map.value.size = htods(it.parsedValue.size);
3641 map.value.dataType = it.parsedValue.dataType;
3642 map.value.res0 = it.parsedValue.res0;
3643 map.value.data = htodl(it.parsedValue.data);
3644 err = data->writeData(&map, sizeof(map));
3645 if (err != NO_ERROR) {
3646 fprintf(stderr, "ERROR: out of memory creating Res_value\n");
3647 return err;
3648 }
3649 amt += sizeof(map);
3650 }
3651 }
3652 return amt;
3653}
3654
3655void ResourceTable::ConfigList::appendComment(const String16& comment,
3656 bool onlyIfEmpty)
3657{
3658 if (comment.size() <= 0) {
3659 return;
3660 }
3661 if (onlyIfEmpty && mComment.size() > 0) {
3662 return;
3663 }
3664 if (mComment.size() > 0) {
3665 mComment.append(String16("\n"));
3666 }
3667 mComment.append(comment);
3668}
3669
3670void ResourceTable::ConfigList::appendTypeComment(const String16& comment)
3671{
3672 if (comment.size() <= 0) {
3673 return;
3674 }
3675 if (mTypeComment.size() > 0) {
3676 mTypeComment.append(String16("\n"));
3677 }
3678 mTypeComment.append(comment);
3679}
3680
3681status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos,
3682 const String16& name,
3683 const uint32_t ident)
3684{
3685 #if 0
3686 int32_t entryIdx = Res_GETENTRY(ident);
3687 if (entryIdx < 0) {
3688 sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n",
3689 String8(mName).string(), String8(name).string(), ident);
3690 return UNKNOWN_ERROR;
3691 }
3692 #endif
3693
3694 int32_t typeIdx = Res_GETTYPE(ident);
3695 if (typeIdx >= 0) {
3696 typeIdx++;
3697 if (mPublicIndex > 0 && mPublicIndex != typeIdx) {
3698 sourcePos.error("Public resource %s/%s has conflicting type codes for its"
3699 " public identifiers (0x%x vs 0x%x).\n",
3700 String8(mName).string(), String8(name).string(),
3701 mPublicIndex, typeIdx);
3702 return UNKNOWN_ERROR;
3703 }
3704 mPublicIndex = typeIdx;
3705 }
3706
3707 if (mFirstPublicSourcePos == NULL) {
3708 mFirstPublicSourcePos = new SourcePos(sourcePos);
3709 }
3710
3711 if (mPublic.indexOfKey(name) < 0) {
3712 mPublic.add(name, Public(sourcePos, String16(), ident));
3713 } else {
3714 Public& p = mPublic.editValueFor(name);
3715 if (p.ident != ident) {
3716 sourcePos.error("Public resource %s/%s has conflicting public identifiers"
3717 " (0x%08x vs 0x%08x).\n"
3718 "%s:%d: Originally defined here.\n",
3719 String8(mName).string(), String8(name).string(), p.ident, ident,
3720 p.sourcePos.file.string(), p.sourcePos.line);
3721 return UNKNOWN_ERROR;
3722 }
3723 }
3724
3725 return NO_ERROR;
3726}
3727
3728void ResourceTable::Type::canAddEntry(const String16& name)
3729{
3730 mCanAddEntries.add(name);
3731}
3732
3733sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
3734 const SourcePos& sourcePos,
3735 const ResTable_config* config,
3736 bool doSetIndex,
3737 bool overlay,
3738 bool autoAddOverlay)
3739{
3740 int pos = -1;
3741 sp<ConfigList> c = mConfigs.valueFor(entry);
3742 if (c == NULL) {
3743 if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) {
3744 sourcePos.error("Resource at %s appears in overlay but not"
3745 " in the base package; use <add-resource> to add.\n",
3746 String8(entry).string());
3747 return NULL;
3748 }
3749 c = new ConfigList(entry, sourcePos);
3750 mConfigs.add(entry, c);
3751 pos = (int)mOrderedConfigs.size();
3752 mOrderedConfigs.add(c);
3753 if (doSetIndex) {
3754 c->setEntryIndex(pos);
3755 }
3756 }
3757
3758 ConfigDescription cdesc;
3759 if (config) cdesc = *config;
3760
3761 sp<Entry> e = c->getEntries().valueFor(cdesc);
3762 if (e == NULL) {
3763 if (config != NULL) {
3764 NOISY(printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c "
3765 "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
3766 "sw%ddp w%ddp h%ddp dir:%d\n",
3767 sourcePos.file.string(), sourcePos.line,
3768 config->mcc, config->mnc,
3769 config->language[0] ? config->language[0] : '-',
3770 config->language[1] ? config->language[1] : '-',
3771 config->country[0] ? config->country[0] : '-',
3772 config->country[1] ? config->country[1] : '-',
3773 config->orientation,
3774 config->touchscreen,
3775 config->density,
3776 config->keyboard,
3777 config->inputFlags,
3778 config->navigation,
3779 config->screenWidth,
3780 config->screenHeight,
3781 config->smallestScreenWidthDp,
3782 config->screenWidthDp,
3783 config->screenHeightDp,
3784 config->layoutDirection));
3785 } else {
3786 NOISY(printf("New entry at %s:%d: NULL config\n",
3787 sourcePos.file.string(), sourcePos.line));
3788 }
3789 e = new Entry(entry, sourcePos);
3790 c->addEntry(cdesc, e);
3791 /*
3792 if (doSetIndex) {
3793 if (pos < 0) {
3794 for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) {
3795 if (mOrderedConfigs[pos] == c) {
3796 break;
3797 }
3798 }
3799 if (pos >= (int)mOrderedConfigs.size()) {
3800 sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry");
3801 return NULL;
3802 }
3803 }
3804 e->setEntryIndex(pos);
3805 }
3806 */
3807 }
3808
3809 mUniqueConfigs.add(cdesc);
3810
3811 return e;
3812}
3813
3814status_t ResourceTable::Type::applyPublicEntryOrder()
3815{
3816 size_t N = mOrderedConfigs.size();
3817 Vector<sp<ConfigList> > origOrder(mOrderedConfigs);
3818 bool hasError = false;
3819
3820 size_t i;
3821 for (i=0; i<N; i++) {
3822 mOrderedConfigs.replaceAt(NULL, i);
3823 }
3824
3825 const size_t NP = mPublic.size();
3826 //printf("Ordering %d configs from %d public defs\n", N, NP);
3827 size_t j;
3828 for (j=0; j<NP; j++) {
3829 const String16& name = mPublic.keyAt(j);
3830 const Public& p = mPublic.valueAt(j);
3831 int32_t idx = Res_GETENTRY(p.ident);
3832 //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n",
3833 // String8(mName).string(), String8(name).string(), p.ident, N);
3834 bool found = false;
3835 for (i=0; i<N; i++) {
3836 sp<ConfigList> e = origOrder.itemAt(i);
3837 //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
3838 if (e->getName() == name) {
3839 if (idx >= (int32_t)mOrderedConfigs.size()) {
3840 p.sourcePos.error("Public entry identifier 0x%x entry index "
3841 "is larger than available symbols (index %d, total symbols %d).\n",
3842 p.ident, idx, mOrderedConfigs.size());
3843 hasError = true;
3844 } else if (mOrderedConfigs.itemAt(idx) == NULL) {
3845 e->setPublic(true);
3846 e->setPublicSourcePos(p.sourcePos);
3847 mOrderedConfigs.replaceAt(e, idx);
3848 origOrder.removeAt(i);
3849 N--;
3850 found = true;
3851 break;
3852 } else {
3853 sp<ConfigList> oe = mOrderedConfigs.itemAt(idx);
3854
3855 p.sourcePos.error("Multiple entry names declared for public entry"
3856 " identifier 0x%x in type %s (%s vs %s).\n"
3857 "%s:%d: Originally defined here.",
3858 idx+1, String8(mName).string(),
3859 String8(oe->getName()).string(),
3860 String8(name).string(),
3861 oe->getPublicSourcePos().file.string(),
3862 oe->getPublicSourcePos().line);
3863 hasError = true;
3864 }
3865 }
3866 }
3867
3868 if (!found) {
3869 p.sourcePos.error("Public symbol %s/%s declared here is not defined.",
3870 String8(mName).string(), String8(name).string());
3871 hasError = true;
3872 }
3873 }
3874
3875 //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size());
3876
3877 if (N != origOrder.size()) {
3878 printf("Internal error: remaining private symbol count mismatch\n");
3879 N = origOrder.size();
3880 }
3881
3882 j = 0;
3883 for (i=0; i<N; i++) {
3884 sp<ConfigList> e = origOrder.itemAt(i);
3885 // There will always be enough room for the remaining entries.
3886 while (mOrderedConfigs.itemAt(j) != NULL) {
3887 j++;
3888 }
3889 mOrderedConfigs.replaceAt(e, j);
3890 j++;
3891 }
3892
3893 return hasError ? UNKNOWN_ERROR : NO_ERROR;
3894}
3895
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003896ResourceTable::Package::Package(const String16& name, size_t packageId)
3897 : mName(name), mPackageId(packageId),
Adam Lesinski282e1812014-01-23 18:17:42 -08003898 mTypeStringsMapping(0xffffffff),
3899 mKeyStringsMapping(0xffffffff)
3900{
3901}
3902
3903sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
3904 const SourcePos& sourcePos,
3905 bool doSetIndex)
3906{
3907 sp<Type> t = mTypes.valueFor(type);
3908 if (t == NULL) {
3909 t = new Type(type, sourcePos);
3910 mTypes.add(type, t);
3911 mOrderedTypes.add(t);
3912 if (doSetIndex) {
3913 // For some reason the type's index is set to one plus the index
3914 // in the mOrderedTypes list, rather than just the index.
3915 t->setIndex(mOrderedTypes.size());
3916 }
3917 }
3918 return t;
3919}
3920
3921status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data)
3922{
Adam Lesinski282e1812014-01-23 18:17:42 -08003923 status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping);
3924 if (err != NO_ERROR) {
3925 fprintf(stderr, "ERROR: Type string data is corrupt!\n");
Adam Lesinski57079512014-07-29 11:51:35 -07003926 return err;
Adam Lesinski282e1812014-01-23 18:17:42 -08003927 }
Adam Lesinski57079512014-07-29 11:51:35 -07003928
3929 // Retain a reference to the new data after we've successfully replaced
3930 // all uses of the old reference (in setStrings() ).
3931 mTypeStringsData = data;
3932 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08003933}
3934
3935status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data)
3936{
Adam Lesinski282e1812014-01-23 18:17:42 -08003937 status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping);
3938 if (err != NO_ERROR) {
3939 fprintf(stderr, "ERROR: Key string data is corrupt!\n");
Adam Lesinski57079512014-07-29 11:51:35 -07003940 return err;
Adam Lesinski282e1812014-01-23 18:17:42 -08003941 }
Adam Lesinski57079512014-07-29 11:51:35 -07003942
3943 // Retain a reference to the new data after we've successfully replaced
3944 // all uses of the old reference (in setStrings() ).
3945 mKeyStringsData = data;
3946 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08003947}
3948
3949status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data,
3950 ResStringPool* strings,
3951 DefaultKeyedVector<String16, uint32_t>* mappings)
3952{
3953 if (data->getData() == NULL) {
3954 return UNKNOWN_ERROR;
3955 }
3956
3957 NOISY(aout << "Setting restable string pool: "
3958 << HexDump(data->getData(), data->getSize()) << endl);
3959
3960 status_t err = strings->setTo(data->getData(), data->getSize());
3961 if (err == NO_ERROR) {
3962 const size_t N = strings->size();
3963 for (size_t i=0; i<N; i++) {
3964 size_t len;
3965 mappings->add(String16(strings->stringAt(i, &len)), i);
3966 }
3967 }
3968 return err;
3969}
3970
3971status_t ResourceTable::Package::applyPublicTypeOrder()
3972{
3973 size_t N = mOrderedTypes.size();
3974 Vector<sp<Type> > origOrder(mOrderedTypes);
3975
3976 size_t i;
3977 for (i=0; i<N; i++) {
3978 mOrderedTypes.replaceAt(NULL, i);
3979 }
3980
3981 for (i=0; i<N; i++) {
3982 sp<Type> t = origOrder.itemAt(i);
3983 int32_t idx = t->getPublicIndex();
3984 if (idx > 0) {
3985 idx--;
3986 while (idx >= (int32_t)mOrderedTypes.size()) {
3987 mOrderedTypes.add();
3988 }
3989 if (mOrderedTypes.itemAt(idx) != NULL) {
3990 sp<Type> ot = mOrderedTypes.itemAt(idx);
3991 t->getFirstPublicSourcePos().error("Multiple type names declared for public type"
3992 " identifier 0x%x (%s vs %s).\n"
3993 "%s:%d: Originally defined here.",
3994 idx, String8(ot->getName()).string(),
3995 String8(t->getName()).string(),
3996 ot->getFirstPublicSourcePos().file.string(),
3997 ot->getFirstPublicSourcePos().line);
3998 return UNKNOWN_ERROR;
3999 }
4000 mOrderedTypes.replaceAt(t, idx);
4001 origOrder.removeAt(i);
4002 i--;
4003 N--;
4004 }
4005 }
4006
4007 size_t j=0;
4008 for (i=0; i<N; i++) {
4009 sp<Type> t = origOrder.itemAt(i);
4010 // There will always be enough room for the remaining types.
4011 while (mOrderedTypes.itemAt(j) != NULL) {
4012 j++;
4013 }
4014 mOrderedTypes.replaceAt(t, j);
4015 }
4016
4017 return NO_ERROR;
4018}
4019
4020sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
4021{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004022 if (package != mAssetsPackage) {
4023 return NULL;
Adam Lesinski282e1812014-01-23 18:17:42 -08004024 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004025 return mPackages.valueFor(package);
Adam Lesinski282e1812014-01-23 18:17:42 -08004026}
4027
4028sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
4029 const String16& type,
4030 const SourcePos& sourcePos,
4031 bool doSetIndex)
4032{
4033 sp<Package> p = getPackage(package);
4034 if (p == NULL) {
4035 return NULL;
4036 }
4037 return p->getType(type, sourcePos, doSetIndex);
4038}
4039
4040sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
4041 const String16& type,
4042 const String16& name,
4043 const SourcePos& sourcePos,
4044 bool overlay,
4045 const ResTable_config* config,
4046 bool doSetIndex)
4047{
4048 sp<Type> t = getType(package, type, sourcePos, doSetIndex);
4049 if (t == NULL) {
4050 return NULL;
4051 }
4052 return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
4053}
4054
Adam Lesinskie572c012014-09-19 15:10:04 -07004055sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package,
4056 const String16& type, const String16& name) const
4057{
4058 const size_t packageCount = mOrderedPackages.size();
4059 for (size_t pi = 0; pi < packageCount; pi++) {
4060 const sp<Package>& p = mOrderedPackages[pi];
4061 if (p == NULL || p->getName() != package) {
4062 continue;
4063 }
4064
4065 const Vector<sp<Type> >& types = p->getOrderedTypes();
4066 const size_t typeCount = types.size();
4067 for (size_t ti = 0; ti < typeCount; ti++) {
4068 const sp<Type>& t = types[ti];
4069 if (t == NULL || t->getName() != type) {
4070 continue;
4071 }
4072
4073 const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs();
4074 const size_t configCount = configs.size();
4075 for (size_t ci = 0; ci < configCount; ci++) {
4076 const sp<ConfigList>& cl = configs[ci];
4077 if (cl == NULL || cl->getName() != name) {
4078 continue;
4079 }
4080
4081 return cl;
4082 }
4083 }
4084 }
4085 return NULL;
4086}
4087
Adam Lesinski282e1812014-01-23 18:17:42 -08004088sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
4089 const ResTable_config* config) const
4090{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004091 size_t pid = Res_GETPACKAGE(resID)+1;
Adam Lesinski282e1812014-01-23 18:17:42 -08004092 const size_t N = mOrderedPackages.size();
Adam Lesinski282e1812014-01-23 18:17:42 -08004093 sp<Package> p;
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004094 for (size_t i = 0; i < N; i++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08004095 sp<Package> check = mOrderedPackages[i];
4096 if (check->getAssignedId() == pid) {
4097 p = check;
4098 break;
4099 }
4100
4101 }
4102 if (p == NULL) {
4103 fprintf(stderr, "warning: Package not found for resource #%08x\n", resID);
4104 return NULL;
4105 }
4106
4107 int tid = Res_GETTYPE(resID);
4108 if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) {
4109 fprintf(stderr, "warning: Type not found for resource #%08x\n", resID);
4110 return NULL;
4111 }
4112 sp<Type> t = p->getOrderedTypes()[tid];
4113
4114 int eid = Res_GETENTRY(resID);
4115 if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) {
4116 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
4117 return NULL;
4118 }
4119
4120 sp<ConfigList> c = t->getOrderedConfigs()[eid];
4121 if (c == NULL) {
4122 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
4123 return NULL;
4124 }
4125
4126 ConfigDescription cdesc;
4127 if (config) cdesc = *config;
4128 sp<Entry> e = c->getEntries().valueFor(cdesc);
4129 if (c == NULL) {
4130 fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID);
4131 return NULL;
4132 }
4133
4134 return e;
4135}
4136
4137const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const
4138{
4139 sp<const Entry> e = getEntry(resID);
4140 if (e == NULL) {
4141 return NULL;
4142 }
4143
4144 const size_t N = e->getBag().size();
4145 for (size_t i=0; i<N; i++) {
4146 const Item& it = e->getBag().valueAt(i);
4147 if (it.bagKeyId == 0) {
4148 fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n",
4149 String8(e->getName()).string(),
4150 String8(e->getBag().keyAt(i)).string());
4151 }
4152 if (it.bagKeyId == attrID) {
4153 return &it;
4154 }
4155 }
4156
4157 return NULL;
4158}
4159
4160bool ResourceTable::getItemValue(
4161 uint32_t resID, uint32_t attrID, Res_value* outValue)
4162{
4163 const Item* item = getItem(resID, attrID);
4164
4165 bool res = false;
4166 if (item != NULL) {
4167 if (item->evaluating) {
4168 sp<const Entry> e = getEntry(resID);
4169 const size_t N = e->getBag().size();
4170 size_t i;
4171 for (i=0; i<N; i++) {
4172 if (&e->getBag().valueAt(i) == item) {
4173 break;
4174 }
4175 }
4176 fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n",
4177 String8(e->getName()).string(),
4178 String8(e->getBag().keyAt(i)).string());
4179 return false;
4180 }
4181 item->evaluating = true;
4182 res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId);
4183 NOISY(
4184 if (res) {
4185 printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n",
4186 resID, attrID, String8(getEntry(resID)->getName()).string(),
4187 outValue->dataType, outValue->data);
4188 } else {
4189 printf("getItemValue of #%08x[#%08x]: failed\n",
4190 resID, attrID);
4191 }
4192 );
4193 item->evaluating = false;
4194 }
4195 return res;
4196}
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004197
4198/**
4199 * Returns true if the given attribute ID comes from
4200 * a platform version from or after L.
4201 */
4202bool ResourceTable::isAttributeFromL(uint32_t attrId) {
4203 const uint32_t baseAttrId = 0x010103f7;
4204 if ((attrId & 0xffff0000) != (baseAttrId & 0xffff0000)) {
4205 return false;
4206 }
4207
4208 uint32_t specFlags;
4209 if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) {
4210 return false;
4211 }
4212
4213 return (specFlags & ResTable_typeSpec::SPEC_PUBLIC) != 0 &&
4214 (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff);
4215}
4216
Adam Lesinskie572c012014-09-19 15:10:04 -07004217static bool isMinSdkVersionLOrAbove(const Bundle* bundle) {
4218 if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
4219 const char firstChar = bundle->getMinSdkVersion()[0];
4220 if (firstChar >= 'L' && firstChar <= 'Z') {
4221 // L is the code-name for the v21 release.
4222 return true;
4223 }
4224
4225 const int minSdk = atoi(bundle->getMinSdkVersion());
4226 if (minSdk >= SDK_L) {
4227 return true;
4228 }
4229 }
4230 return false;
4231}
4232
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004233/**
4234 * Modifies the entries in the resource table to account for compatibility
4235 * issues with older versions of Android.
4236 *
4237 * This primarily handles the issue of private/public attribute clashes
4238 * in framework resources.
4239 *
4240 * AAPT has traditionally assigned resource IDs to public attributes,
4241 * and then followed those public definitions with private attributes.
4242 *
4243 * --- PUBLIC ---
4244 * | 0x01010234 | attr/color
4245 * | 0x01010235 | attr/background
4246 *
4247 * --- PRIVATE ---
4248 * | 0x01010236 | attr/secret
4249 * | 0x01010237 | attr/shhh
4250 *
4251 * Each release, when attributes are added, they take the place of the private
4252 * attributes and the private attributes are shifted down again.
4253 *
4254 * --- PUBLIC ---
4255 * | 0x01010234 | attr/color
4256 * | 0x01010235 | attr/background
4257 * | 0x01010236 | attr/shinyNewAttr
4258 * | 0x01010237 | attr/highlyValuedFeature
4259 *
4260 * --- PRIVATE ---
4261 * | 0x01010238 | attr/secret
4262 * | 0x01010239 | attr/shhh
4263 *
4264 * Platform code may look for private attributes set in a theme. If an app
4265 * compiled against a newer version of the platform uses a new public
4266 * attribute that happens to have the same ID as the private attribute
4267 * the older platform is expecting, then the behavior is undefined.
4268 *
4269 * We get around this by detecting any newly defined attributes (in L),
4270 * copy the resource into a -v21 qualified resource, and delete the
4271 * attribute from the original resource. This ensures that older platforms
4272 * don't see the new attribute, but when running on L+ platforms, the
4273 * attribute will be respected.
4274 */
4275status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004276 if (isMinSdkVersionLOrAbove(bundle)) {
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004277 // If this app will only ever run on L+ devices,
4278 // we don't need to do any compatibility work.
Adam Lesinskie572c012014-09-19 15:10:04 -07004279 return NO_ERROR;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004280 }
4281
4282 const String16 attr16("attr");
4283
4284 const size_t packageCount = mOrderedPackages.size();
4285 for (size_t pi = 0; pi < packageCount; pi++) {
4286 sp<Package> p = mOrderedPackages.itemAt(pi);
4287 if (p == NULL || p->getTypes().size() == 0) {
4288 // Empty, skip!
4289 continue;
4290 }
4291
4292 const size_t typeCount = p->getOrderedTypes().size();
4293 for (size_t ti = 0; ti < typeCount; ti++) {
4294 sp<Type> t = p->getOrderedTypes().itemAt(ti);
4295 if (t == NULL) {
4296 continue;
4297 }
4298
4299 const size_t configCount = t->getOrderedConfigs().size();
4300 for (size_t ci = 0; ci < configCount; ci++) {
4301 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
4302 if (c == NULL) {
4303 continue;
4304 }
4305
4306 Vector<key_value_pair_t<ConfigDescription, sp<Entry> > > entriesToAdd;
4307 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries =
4308 c->getEntries();
4309 const size_t entryCount = entries.size();
4310 for (size_t ei = 0; ei < entryCount; ei++) {
4311 sp<Entry> e = entries.valueAt(ei);
4312 if (e == NULL || e->getType() != Entry::TYPE_BAG) {
4313 continue;
4314 }
4315
4316 const ConfigDescription& config = entries.keyAt(ei);
4317 if (config.sdkVersion >= SDK_L) {
4318 // We don't need to do anything if the resource is
4319 // already qualified for version 21 or higher.
4320 continue;
4321 }
4322
4323 Vector<String16> attributesToRemove;
4324 const KeyedVector<String16, Item>& bag = e->getBag();
4325 const size_t bagCount = bag.size();
4326 for (size_t bi = 0; bi < bagCount; bi++) {
4327 const Item& item = bag.valueAt(bi);
4328 const uint32_t attrId = getResId(bag.keyAt(bi), &attr16);
4329 if (isAttributeFromL(attrId)) {
4330 attributesToRemove.add(bag.keyAt(bi));
4331 }
4332 }
4333
4334 if (attributesToRemove.isEmpty()) {
4335 continue;
4336 }
4337
4338 // Duplicate the entry under the same configuration
4339 // but with sdkVersion == SDK_L.
4340 ConfigDescription newConfig(config);
4341 newConfig.sdkVersion = SDK_L;
4342 entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
4343 newConfig, new Entry(*e)));
4344
4345 // Remove the attribute from the original.
4346 for (size_t i = 0; i < attributesToRemove.size(); i++) {
4347 e->removeFromBag(attributesToRemove[i]);
4348 }
4349 }
4350
4351 const size_t entriesToAddCount = entriesToAdd.size();
4352 for (size_t i = 0; i < entriesToAddCount; i++) {
4353 if (entries.indexOfKey(entriesToAdd[i].key) >= 0) {
4354 // An entry already exists for this config.
4355 // That means that any attributes that were
4356 // defined in L in the original bag will be overriden
4357 // anyways on L devices, so we do nothing.
4358 continue;
4359 }
4360
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004361 if (bundle->getVerbose()) {
4362 entriesToAdd[i].value->getPos()
4363 .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
4364 SDK_L,
4365 String8(p->getName()).string(),
4366 String8(t->getName()).string(),
4367 String8(entriesToAdd[i].value->getName()).string(),
4368 entriesToAdd[i].key.toString().string());
4369 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004370
Adam Lesinski978ab9d2014-09-24 19:02:52 -07004371 sp<Entry> newEntry = t->getEntry(c->getName(),
4372 entriesToAdd[i].value->getPos(),
4373 &entriesToAdd[i].key);
4374
4375 *newEntry = *entriesToAdd[i].value;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004376 }
4377 }
4378 }
4379 }
4380 return NO_ERROR;
4381}
Adam Lesinskie572c012014-09-19 15:10:04 -07004382
4383status_t ResourceTable::modifyForCompat(const Bundle* bundle,
4384 const String16& resourceName,
4385 const sp<AaptFile>& target,
4386 const sp<XMLNode>& root) {
4387 if (isMinSdkVersionLOrAbove(bundle)) {
4388 return NO_ERROR;
4389 }
4390
4391 if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_L) {
4392 // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21
4393 // or higher.
4394 return NO_ERROR;
4395 }
4396
4397 Vector<key_value_pair_t<sp<XMLNode>, size_t> > attrsToRemove;
4398
4399 Vector<sp<XMLNode> > nodesToVisit;
4400 nodesToVisit.push(root);
4401 while (!nodesToVisit.isEmpty()) {
4402 sp<XMLNode> node = nodesToVisit.top();
4403 nodesToVisit.pop();
4404
4405 const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
4406 const size_t attrCount = attrs.size();
4407 for (size_t i = 0; i < attrCount; i++) {
4408 const XMLNode::attribute_entry& attr = attrs[i];
4409 if (isAttributeFromL(attr.nameResId)) {
4410 attrsToRemove.add(key_value_pair_t<sp<XMLNode>, size_t>(node, i));
4411 }
4412 }
4413
4414 // Schedule a visit to the children.
4415 const Vector<sp<XMLNode> >& children = node->getChildren();
4416 const size_t childCount = children.size();
4417 for (size_t i = 0; i < childCount; i++) {
4418 nodesToVisit.push(children[i]);
4419 }
4420 }
4421
4422 if (attrsToRemove.isEmpty()) {
4423 return NO_ERROR;
4424 }
4425
4426 ConfigDescription newConfig(target->getGroupEntry().toParams());
4427 newConfig.sdkVersion = SDK_L;
4428
4429 // Look to see if we already have an overriding v21 configuration.
4430 sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
4431 String16(target->getResourceType()), resourceName);
Adam Lesinskiaff7c242014-10-20 12:15:25 -07004432 //if (cl == NULL) {
4433 // fprintf(stderr, "fuuuuck\n");
4434 //}
Adam Lesinskie572c012014-09-19 15:10:04 -07004435 if (cl->getEntries().indexOfKey(newConfig) < 0) {
4436 // We don't have an overriding entry for v21, so we must duplicate this one.
4437 sp<XMLNode> newRoot = root->clone();
4438 sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
4439 AaptGroupEntry(newConfig), target->getResourceType());
4440 String8 resPath = String8::format("res/%s/%s",
4441 newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
Adam Lesinskiaff7c242014-10-20 12:15:25 -07004442 target->getSourceFile().getPathLeaf().string());
Adam Lesinskie572c012014-09-19 15:10:04 -07004443 resPath.convertToResPath();
4444
4445 // Add a resource table entry.
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004446 if (bundle->getVerbose()) {
4447 SourcePos(target->getSourceFile(), -1).printf(
4448 "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
4449 SDK_L,
4450 mAssets->getPackage().string(),
4451 newFile->getResourceType().string(),
4452 String8(resourceName).string(),
4453 newConfig.toString().string());
4454 }
Adam Lesinskie572c012014-09-19 15:10:04 -07004455
4456 addEntry(SourcePos(),
4457 String16(mAssets->getPackage()),
4458 String16(target->getResourceType()),
4459 resourceName,
4460 String16(resPath),
4461 NULL,
4462 &newConfig);
4463
4464 // Schedule this to be compiled.
4465 CompileResourceWorkItem item;
4466 item.resourceName = resourceName;
4467 item.resPath = resPath;
4468 item.file = newFile;
4469 mWorkQueue.push(item);
4470 }
4471
4472 const size_t removeCount = attrsToRemove.size();
4473 for (size_t i = 0; i < removeCount; i++) {
4474 sp<XMLNode> node = attrsToRemove[i].key;
4475 size_t attrIndex = attrsToRemove[i].value;
4476 const XMLNode::attribute_entry& ae = node->getAttributes()[attrIndex];
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004477 if (bundle->getVerbose()) {
4478 SourcePos(node->getFilename(), node->getStartLineNumber()).printf(
4479 "removing attribute %s%s%s from <%s>",
4480 String8(ae.ns).string(),
4481 (ae.ns.size() == 0 ? "" : ":"),
4482 String8(ae.name).string(),
4483 String8(node->getElementName()).string());
4484 }
Adam Lesinskie572c012014-09-19 15:10:04 -07004485 node->removeAttribute(attrIndex);
4486 }
4487
4488 return NO_ERROR;
4489}
Adam Lesinskide7de472014-11-03 12:03:08 -08004490
4491void ResourceTable::getDensityVaryingResources(KeyedVector<Symbol, Vector<SymbolDefinition> >& resources) {
4492 const ConfigDescription nullConfig;
4493
4494 const size_t packageCount = mOrderedPackages.size();
4495 for (size_t p = 0; p < packageCount; p++) {
4496 const Vector<sp<Type> >& types = mOrderedPackages[p]->getOrderedTypes();
4497 const size_t typeCount = types.size();
4498 for (size_t t = 0; t < typeCount; t++) {
4499 const Vector<sp<ConfigList> >& configs = types[t]->getOrderedConfigs();
4500 const size_t configCount = configs.size();
4501 for (size_t c = 0; c < configCount; c++) {
4502 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configEntries = configs[c]->getEntries();
4503 const size_t configEntryCount = configEntries.size();
4504 for (size_t ce = 0; ce < configEntryCount; ce++) {
4505 const ConfigDescription& config = configEntries.keyAt(ce);
4506 if (AaptConfig::isDensityOnly(config)) {
4507 // This configuration only varies with regards to density.
4508 const Symbol symbol(mOrderedPackages[p]->getName(),
4509 types[t]->getName(),
4510 configs[c]->getName(),
4511 getResId(mOrderedPackages[p], types[t], configs[c]->getEntryIndex()));
4512
4513 const sp<Entry>& entry = configEntries.valueAt(ce);
4514 AaptUtil::appendValue(resources, symbol, SymbolDefinition(symbol, config, entry->getPos()));
4515 }
4516 }
4517 }
4518 }
4519 }
4520}