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