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