blob: 0e470d924f5ad6da3361f8fbb782ff95f34284d5 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4// Build resource files from raw assets.
5//
6
7#include "ResourceTable.h"
8
Adam Lesinskide7de472014-11-03 12:03:08 -08009#include "AaptUtil.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080010#include "XMLNode.h"
11#include "ResourceFilter.h"
12#include "ResourceIdCache.h"
Adam Lesinskidcdfe9f2014-11-06 12:54:36 -080013#include "SdkConstants.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080014
Adam Lesinski9b624c12014-11-19 17:49:26 -080015#include <algorithm>
Adam Lesinski282e1812014-01-23 18:17:42 -080016#include <androidfw/ResourceTypes.h>
17#include <utils/ByteOrder.h>
Adam Lesinski82a2dd82014-09-17 18:34:15 -070018#include <utils/TypeHelpers.h>
Adam Lesinski282e1812014-01-23 18:17:42 -080019#include <stdarg.h>
20
Andreas Gampe2412f842014-09-30 20:55:57 -070021// SSIZE: mingw does not have signed size_t == ssize_t.
22// STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary.
Elliott Hughesb12f2412015-04-03 12:56:45 -070023#if !defined(_WIN32)
Andreas Gampe2412f842014-09-30 20:55:57 -070024# define SSIZE(x) x
25# define STATUST(x) x
26#else
27# define SSIZE(x) (signed size_t)x
28# define STATUST(x) (status_t)x
29#endif
30
31// Set to true for noisy debug output.
32static const bool kIsDebug = false;
33
34#if PRINT_STRING_METRICS
35static const bool kPrintStringMetrics = true;
36#else
37static const bool kPrintStringMetrics = false;
38#endif
Adam Lesinski282e1812014-01-23 18:17:42 -080039
Adam Lesinski9b624c12014-11-19 17:49:26 -080040static const char* kAttrPrivateType = "^attr-private";
41
Adam Lesinskie572c012014-09-19 15:10:04 -070042status_t compileXmlFile(const Bundle* bundle,
43 const sp<AaptAssets>& assets,
44 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080045 const sp<AaptFile>& target,
46 ResourceTable* table,
47 int options)
48{
49 sp<XMLNode> root = XMLNode::parse(target);
50 if (root == NULL) {
51 return UNKNOWN_ERROR;
52 }
Anton Krumina2ef5c02014-03-12 14:46:44 -070053
Adam Lesinskie572c012014-09-19 15:10:04 -070054 return compileXmlFile(bundle, assets, resourceName, root, target, table, options);
Adam Lesinski282e1812014-01-23 18:17:42 -080055}
56
Adam Lesinskie572c012014-09-19 15:10:04 -070057status_t compileXmlFile(const Bundle* bundle,
58 const sp<AaptAssets>& assets,
59 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080060 const sp<AaptFile>& target,
61 const sp<AaptFile>& outTarget,
62 ResourceTable* table,
63 int options)
64{
65 sp<XMLNode> root = XMLNode::parse(target);
66 if (root == NULL) {
67 return UNKNOWN_ERROR;
68 }
69
Adam Lesinskie572c012014-09-19 15:10:04 -070070 return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options);
Adam Lesinski282e1812014-01-23 18:17:42 -080071}
72
Adam Lesinskie572c012014-09-19 15:10:04 -070073status_t compileXmlFile(const Bundle* bundle,
74 const sp<AaptAssets>& assets,
75 const String16& resourceName,
Adam Lesinski282e1812014-01-23 18:17:42 -080076 const sp<XMLNode>& root,
77 const sp<AaptFile>& target,
78 ResourceTable* table,
79 int options)
80{
81 if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
82 root->removeWhitespace(true, NULL);
83 } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
84 root->removeWhitespace(false, NULL);
85 }
86
87 if ((options&XML_COMPILE_UTF8) != 0) {
88 root->setUTF8(true);
89 }
90
Adam Lesinski07dfd2d2015-10-28 15:44:27 -070091 if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
92 return UNKNOWN_ERROR;
93 }
Adam Lesinski5b9847c2015-11-30 21:07:44 +000094
Adam Lesinski07dfd2d2015-10-28 15:44:27 -070095 bool hasErrors = false;
Adam Lesinski282e1812014-01-23 18:17:42 -080096 if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
97 status_t err = root->assignResourceIds(assets, table);
98 if (err != NO_ERROR) {
99 hasErrors = true;
100 }
101 }
102
Adam Lesinski07dfd2d2015-10-28 15:44:27 -0700103 if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
104 status_t err = root->parseValues(assets, table);
105 if (err != NO_ERROR) {
106 hasErrors = true;
107 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800108 }
109
110 if (hasErrors) {
111 return UNKNOWN_ERROR;
112 }
Adam Lesinskie572c012014-09-19 15:10:04 -0700113
114 if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) {
115 return UNKNOWN_ERROR;
116 }
Andreas Gampe87332a72014-10-01 22:03:58 -0700117
Andreas Gampe2412f842014-09-30 20:55:57 -0700118 if (kIsDebug) {
119 printf("Input XML Resource:\n");
120 root->print();
121 }
Adam Lesinski07dfd2d2015-10-28 15:44:27 -0700122 status_t err = root->flatten(target,
Adam Lesinski282e1812014-01-23 18:17:42 -0800123 (options&XML_COMPILE_STRIP_COMMENTS) != 0,
124 (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
125 if (err != NO_ERROR) {
126 return err;
127 }
128
Andreas Gampe2412f842014-09-30 20:55:57 -0700129 if (kIsDebug) {
130 printf("Output XML Resource:\n");
131 ResXMLTree tree;
Adam Lesinski282e1812014-01-23 18:17:42 -0800132 tree.setTo(target->getData(), target->getSize());
Andreas Gampe2412f842014-09-30 20:55:57 -0700133 printXMLBlock(&tree);
134 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800135
136 target->setCompressionMethod(ZipEntry::kCompressDeflated);
137
138 return err;
139}
140
Adam Lesinski282e1812014-01-23 18:17:42 -0800141struct flag_entry
142{
143 const char16_t* name;
144 size_t nameLen;
145 uint32_t value;
146 const char* description;
147};
148
149static const char16_t referenceArray[] =
150 { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' };
151static const char16_t stringArray[] =
152 { 's', 't', 'r', 'i', 'n', 'g' };
153static const char16_t integerArray[] =
154 { 'i', 'n', 't', 'e', 'g', 'e', 'r' };
155static const char16_t booleanArray[] =
156 { 'b', 'o', 'o', 'l', 'e', 'a', 'n' };
157static const char16_t colorArray[] =
158 { 'c', 'o', 'l', 'o', 'r' };
159static const char16_t floatArray[] =
160 { 'f', 'l', 'o', 'a', 't' };
161static const char16_t dimensionArray[] =
162 { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' };
163static const char16_t fractionArray[] =
164 { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' };
165static const char16_t enumArray[] =
166 { 'e', 'n', 'u', 'm' };
167static const char16_t flagsArray[] =
168 { 'f', 'l', 'a', 'g', 's' };
169
170static const flag_entry gFormatFlags[] = {
171 { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
172 "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
173 "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
174 { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
175 "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
176 { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
177 "an integer value, such as \"<code>100</code>\"." },
178 { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
179 "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
180 { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
181 "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
182 "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
183 { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
184 "a floating point value, such as \"<code>1.2</code>\"."},
185 { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
186 "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
187 "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
188 "in (inches), mm (millimeters)." },
189 { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
190 "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
191 "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
192 "some parent container." },
193 { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
194 { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
195 { NULL, 0, 0, NULL }
196};
197
198static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' };
199
200static const flag_entry l10nRequiredFlags[] = {
201 { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL },
202 { NULL, 0, 0, NULL }
203};
204
205static const char16_t nulStr[] = { 0 };
206
207static uint32_t parse_flags(const char16_t* str, size_t len,
208 const flag_entry* flags, bool* outError = NULL)
209{
210 while (len > 0 && isspace(*str)) {
211 str++;
212 len--;
213 }
214 while (len > 0 && isspace(str[len-1])) {
215 len--;
216 }
217
218 const char16_t* const end = str + len;
219 uint32_t value = 0;
220
221 while (str < end) {
222 const char16_t* div = str;
223 while (div < end && *div != '|') {
224 div++;
225 }
226
227 const flag_entry* cur = flags;
228 while (cur->name) {
229 if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
230 value |= cur->value;
231 break;
232 }
233 cur++;
234 }
235
236 if (!cur->name) {
237 if (outError) *outError = true;
238 return 0;
239 }
240
241 str = div < end ? div+1 : div;
242 }
243
244 if (outError) *outError = false;
245 return value;
246}
247
248static String16 mayOrMust(int type, int flags)
249{
250 if ((type&(~flags)) == 0) {
251 return String16("<p>Must");
252 }
253
254 return String16("<p>May");
255}
256
257static void appendTypeInfo(ResourceTable* outTable, const String16& pkg,
258 const String16& typeName, const String16& ident, int type,
259 const flag_entry* flags)
260{
261 bool hadType = false;
262 while (flags->name) {
263 if ((type&flags->value) != 0 && flags->description != NULL) {
264 String16 fullMsg(mayOrMust(type, flags->value));
265 fullMsg.append(String16(" be "));
266 fullMsg.append(String16(flags->description));
267 outTable->appendTypeComment(pkg, typeName, ident, fullMsg);
268 hadType = true;
269 }
270 flags++;
271 }
272 if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) {
273 outTable->appendTypeComment(pkg, typeName, ident,
274 String16("<p>This may also be a reference to a resource (in the form\n"
275 "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
276 "theme attribute (in the form\n"
277 "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
278 "containing a value of this type."));
279 }
280}
281
282struct PendingAttribute
283{
284 const String16 myPackage;
285 const SourcePos sourcePos;
286 const bool appendComment;
287 int32_t type;
288 String16 ident;
289 String16 comment;
290 bool hasErrors;
291 bool added;
292
293 PendingAttribute(String16 _package, const sp<AaptFile>& in,
294 ResXMLTree& block, bool _appendComment)
295 : myPackage(_package)
296 , sourcePos(in->getPrintableSource(), block.getLineNumber())
297 , appendComment(_appendComment)
298 , type(ResTable_map::TYPE_ANY)
299 , hasErrors(false)
300 , added(false)
301 {
302 }
303
304 status_t createIfNeeded(ResourceTable* outTable)
305 {
306 if (added || hasErrors) {
307 return NO_ERROR;
308 }
309 added = true;
310
311 String16 attr16("attr");
312
313 if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
314 sourcePos.error("Attribute \"%s\" has already been defined\n",
315 String8(ident).string());
316 hasErrors = true;
317 return UNKNOWN_ERROR;
318 }
319
320 char numberStr[16];
321 sprintf(numberStr, "%d", type);
322 status_t err = outTable->addBag(sourcePos, myPackage,
323 attr16, ident, String16(""),
324 String16("^type"),
325 String16(numberStr), NULL, NULL);
326 if (err != NO_ERROR) {
327 hasErrors = true;
328 return err;
329 }
330 outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
331 //printf("Attribute %s comment: %s\n", String8(ident).string(),
332 // String8(comment).string());
333 return err;
334 }
335};
336
337static status_t compileAttribute(const sp<AaptFile>& in,
338 ResXMLTree& block,
339 const String16& myPackage,
340 ResourceTable* outTable,
341 String16* outIdent = NULL,
342 bool inStyleable = false)
343{
344 PendingAttribute attr(myPackage, in, block, inStyleable);
345
346 const String16 attr16("attr");
347 const String16 id16("id");
348
349 // Attribute type constants.
350 const String16 enum16("enum");
351 const String16 flag16("flag");
352
353 ResXMLTree::event_code_t code;
354 size_t len;
355 status_t err;
356
357 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
358 if (identIdx >= 0) {
359 attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
360 if (outIdent) {
361 *outIdent = attr.ident;
362 }
363 } else {
364 attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
365 attr.hasErrors = true;
366 }
367
368 attr.comment = String16(
369 block.getComment(&len) ? block.getComment(&len) : nulStr);
370
371 ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
372 if (typeIdx >= 0) {
373 String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len));
374 attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);
375 if (attr.type == 0) {
376 attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
377 String8(typeStr).string());
378 attr.hasErrors = true;
379 }
380 attr.createIfNeeded(outTable);
381 } else if (!inStyleable) {
382 // Attribute definitions outside of styleables always define the
383 // attribute as a generic value.
384 attr.createIfNeeded(outTable);
385 }
386
387 //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
388
389 ssize_t minIdx = block.indexOfAttribute(NULL, "min");
390 if (minIdx >= 0) {
391 String16 val = String16(block.getAttributeStringValue(minIdx, &len));
392 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
393 attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
394 String8(val).string());
395 attr.hasErrors = true;
396 }
397 attr.createIfNeeded(outTable);
398 if (!attr.hasErrors) {
399 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
400 String16(""), String16("^min"), String16(val), NULL, NULL);
401 if (err != NO_ERROR) {
402 attr.hasErrors = true;
403 }
404 }
405 }
406
407 ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
408 if (maxIdx >= 0) {
409 String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
410 if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
411 attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
412 String8(val).string());
413 attr.hasErrors = true;
414 }
415 attr.createIfNeeded(outTable);
416 if (!attr.hasErrors) {
417 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
418 String16(""), String16("^max"), String16(val), NULL, NULL);
419 attr.hasErrors = true;
420 }
421 }
422
423 if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
424 attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
425 attr.hasErrors = true;
426 }
427
428 ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
429 if (l10nIdx >= 0) {
Dan Albertf348c152014-09-08 18:28:00 -0700430 const char16_t* str = block.getAttributeStringValue(l10nIdx, &len);
Adam Lesinski282e1812014-01-23 18:17:42 -0800431 bool error;
432 uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
433 if (error) {
434 attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
435 String8(str).string());
436 attr.hasErrors = true;
437 }
438 attr.createIfNeeded(outTable);
439 if (!attr.hasErrors) {
440 char buf[11];
441 sprintf(buf, "%d", l10n_required);
442 err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
443 String16(""), String16("^l10n"), String16(buf), NULL, NULL);
444 if (err != NO_ERROR) {
445 attr.hasErrors = true;
446 }
447 }
448 }
449
450 String16 enumOrFlagsComment;
451
452 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
453 if (code == ResXMLTree::START_TAG) {
454 uint32_t localType = 0;
455 if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
456 localType = ResTable_map::TYPE_ENUM;
457 } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
458 localType = ResTable_map::TYPE_FLAGS;
459 } else {
460 SourcePos(in->getPrintableSource(), block.getLineNumber())
461 .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
462 String8(block.getElementName(&len)).string());
463 return UNKNOWN_ERROR;
464 }
465
466 attr.createIfNeeded(outTable);
467
468 if (attr.type == ResTable_map::TYPE_ANY) {
469 // No type was explicitly stated, so supplying enum tags
470 // implicitly creates an enum or flag.
471 attr.type = 0;
472 }
473
474 if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
475 // Wasn't originally specified as an enum, so update its type.
476 attr.type |= localType;
477 if (!attr.hasErrors) {
478 char numberStr[16];
479 sprintf(numberStr, "%d", attr.type);
480 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
481 myPackage, attr16, attr.ident, String16(""),
482 String16("^type"), String16(numberStr), NULL, NULL, true);
483 if (err != NO_ERROR) {
484 attr.hasErrors = true;
485 }
486 }
487 } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
488 if (localType == ResTable_map::TYPE_ENUM) {
489 SourcePos(in->getPrintableSource(), block.getLineNumber())
490 .error("<enum> attribute can not be used inside a flags format\n");
491 attr.hasErrors = true;
492 } else {
493 SourcePos(in->getPrintableSource(), block.getLineNumber())
494 .error("<flag> attribute can not be used inside a enum format\n");
495 attr.hasErrors = true;
496 }
497 }
498
499 String16 itemIdent;
500 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
501 if (itemIdentIdx >= 0) {
502 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
503 } else {
504 SourcePos(in->getPrintableSource(), block.getLineNumber())
505 .error("A 'name' attribute is required for <enum> or <flag>\n");
506 attr.hasErrors = true;
507 }
508
509 String16 value;
510 ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
511 if (valueIdx >= 0) {
512 value = String16(block.getAttributeStringValue(valueIdx, &len));
513 } else {
514 SourcePos(in->getPrintableSource(), block.getLineNumber())
515 .error("A 'value' attribute is required for <enum> or <flag>\n");
516 attr.hasErrors = true;
517 }
518 if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
519 SourcePos(in->getPrintableSource(), block.getLineNumber())
520 .error("Tag <enum> or <flag> 'value' attribute must be a number,"
521 " not \"%s\"\n",
522 String8(value).string());
523 attr.hasErrors = true;
524 }
525
Adam Lesinski282e1812014-01-23 18:17:42 -0800526 if (!attr.hasErrors) {
527 if (enumOrFlagsComment.size() == 0) {
528 enumOrFlagsComment.append(mayOrMust(attr.type,
529 ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
530 enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
531 ? String16(" be one of the following constant values.")
532 : String16(" be one or more (separated by '|') of the following constant values."));
533 enumOrFlagsComment.append(String16("</p>\n<table>\n"
534 "<colgroup align=\"left\" />\n"
535 "<colgroup align=\"left\" />\n"
536 "<colgroup align=\"left\" />\n"
537 "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>"));
538 }
539
540 enumOrFlagsComment.append(String16("\n<tr><td><code>"));
541 enumOrFlagsComment.append(itemIdent);
542 enumOrFlagsComment.append(String16("</code></td><td>"));
543 enumOrFlagsComment.append(value);
544 enumOrFlagsComment.append(String16("</td><td>"));
545 if (block.getComment(&len)) {
546 enumOrFlagsComment.append(String16(block.getComment(&len)));
547 }
548 enumOrFlagsComment.append(String16("</td></tr>"));
549
550 err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
551 myPackage,
552 attr16, attr.ident, String16(""),
553 itemIdent, value, NULL, NULL, false, true);
554 if (err != NO_ERROR) {
555 attr.hasErrors = true;
556 }
557 }
558 } else if (code == ResXMLTree::END_TAG) {
559 if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
560 break;
561 }
562 if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
563 if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
564 SourcePos(in->getPrintableSource(), block.getLineNumber())
565 .error("Found tag </%s> where </enum> is expected\n",
566 String8(block.getElementName(&len)).string());
567 return UNKNOWN_ERROR;
568 }
569 } else {
570 if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
571 SourcePos(in->getPrintableSource(), block.getLineNumber())
572 .error("Found tag </%s> where </flag> is expected\n",
573 String8(block.getElementName(&len)).string());
574 return UNKNOWN_ERROR;
575 }
576 }
577 }
578 }
579
580 if (!attr.hasErrors && attr.added) {
581 appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
582 }
583
584 if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
585 enumOrFlagsComment.append(String16("\n</table>"));
586 outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
587 }
588
589
590 return NO_ERROR;
591}
592
593bool localeIsDefined(const ResTable_config& config)
594{
595 return config.locale == 0;
596}
597
598status_t parseAndAddBag(Bundle* bundle,
599 const sp<AaptFile>& in,
600 ResXMLTree* block,
601 const ResTable_config& config,
602 const String16& myPackage,
603 const String16& curType,
604 const String16& ident,
605 const String16& parentIdent,
606 const String16& itemIdent,
607 int32_t curFormat,
608 bool isFormatted,
Andreas Gampe2412f842014-09-30 20:55:57 -0700609 const String16& /* product */,
Anton Krumina2ef5c02014-03-12 14:46:44 -0700610 PseudolocalizationMethod pseudolocalize,
Adam Lesinski282e1812014-01-23 18:17:42 -0800611 const bool overwrite,
612 ResourceTable* outTable)
613{
614 status_t err;
615 const String16 item16("item");
Anton Krumina2ef5c02014-03-12 14:46:44 -0700616
Adam Lesinski282e1812014-01-23 18:17:42 -0800617 String16 str;
618 Vector<StringPool::entry_style_span> spans;
619 err = parseStyledString(bundle, in->getPrintableSource().string(),
620 block, item16, &str, &spans, isFormatted,
621 pseudolocalize);
622 if (err != NO_ERROR) {
623 return err;
624 }
Andreas Gampe2412f842014-09-30 20:55:57 -0700625
626 if (kIsDebug) {
627 printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d "
628 " pid=%s, bag=%s, id=%s: %s\n",
629 config.language[0], config.language[1],
630 config.country[0], config.country[1],
631 config.orientation, config.density,
632 String8(parentIdent).string(),
633 String8(ident).string(),
634 String8(itemIdent).string(),
635 String8(str).string());
636 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800637
638 err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
639 myPackage, curType, ident, parentIdent, itemIdent, str,
640 &spans, &config, overwrite, false, curFormat);
641 return err;
642}
643
644/*
645 * Returns true if needle is one of the elements in the comma-separated list
646 * haystack, false otherwise.
647 */
648bool isInProductList(const String16& needle, const String16& haystack) {
649 const char16_t *needle2 = needle.string();
650 const char16_t *haystack2 = haystack.string();
651 size_t needlesize = needle.size();
652
653 while (*haystack2 != '\0') {
654 if (strncmp16(haystack2, needle2, needlesize) == 0) {
655 if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') {
656 return true;
657 }
658 }
659
660 while (*haystack2 != '\0' && *haystack2 != ',') {
661 haystack2++;
662 }
663 if (*haystack2 == ',') {
664 haystack2++;
665 }
666 }
667
668 return false;
669}
670
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700671/*
672 * A simple container that holds a resource type and name. It is ordered first by type then
673 * by name.
674 */
675struct type_ident_pair_t {
676 String16 type;
677 String16 ident;
678
679 type_ident_pair_t() { };
680 type_ident_pair_t(const String16& t, const String16& i) : type(t), ident(i) { }
681 type_ident_pair_t(const type_ident_pair_t& o) : type(o.type), ident(o.ident) { }
682 inline bool operator < (const type_ident_pair_t& o) const {
683 int cmp = compare_type(type, o.type);
684 if (cmp < 0) {
685 return true;
686 } else if (cmp > 0) {
687 return false;
688 } else {
689 return strictly_order_type(ident, o.ident);
690 }
691 }
692};
693
694
Adam Lesinski282e1812014-01-23 18:17:42 -0800695status_t parseAndAddEntry(Bundle* bundle,
696 const sp<AaptFile>& in,
697 ResXMLTree* block,
698 const ResTable_config& config,
699 const String16& myPackage,
700 const String16& curType,
701 const String16& ident,
702 const String16& curTag,
703 bool curIsStyled,
704 int32_t curFormat,
705 bool isFormatted,
706 const String16& product,
Anton Krumina2ef5c02014-03-12 14:46:44 -0700707 PseudolocalizationMethod pseudolocalize,
Adam Lesinski282e1812014-01-23 18:17:42 -0800708 const bool overwrite,
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700709 KeyedVector<type_ident_pair_t, bool>* skippedResourceNames,
Adam Lesinski282e1812014-01-23 18:17:42 -0800710 ResourceTable* outTable)
711{
712 status_t err;
713
714 String16 str;
715 Vector<StringPool::entry_style_span> spans;
716 err = parseStyledString(bundle, in->getPrintableSource().string(), block,
717 curTag, &str, curIsStyled ? &spans : NULL,
718 isFormatted, pseudolocalize);
719
720 if (err < NO_ERROR) {
721 return err;
722 }
723
724 /*
725 * If a product type was specified on the command line
726 * and also in the string, and the two are not the same,
727 * return without adding the string.
728 */
729
730 const char *bundleProduct = bundle->getProduct();
731 if (bundleProduct == NULL) {
732 bundleProduct = "";
733 }
734
735 if (product.size() != 0) {
736 /*
737 * If the command-line-specified product is empty, only "default"
738 * matches. Other variants are skipped. This is so generation
739 * of the R.java file when the product is not known is predictable.
740 */
741
742 if (bundleProduct[0] == '\0') {
743 if (strcmp16(String16("default").string(), product.string()) != 0) {
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700744 /*
745 * This string has a product other than 'default'. Do not add it,
746 * but record it so that if we do not see the same string with
747 * product 'default' or no product, then report an error.
748 */
749 skippedResourceNames->replaceValueFor(
750 type_ident_pair_t(curType, ident), true);
Adam Lesinski282e1812014-01-23 18:17:42 -0800751 return NO_ERROR;
752 }
753 } else {
754 /*
755 * The command-line product is not empty.
756 * If the product for this string is on the command-line list,
757 * it matches. "default" also matches, but only if nothing
758 * else has matched already.
759 */
760
761 if (isInProductList(product, String16(bundleProduct))) {
762 ;
763 } else if (strcmp16(String16("default").string(), product.string()) == 0 &&
764 !outTable->hasBagOrEntry(myPackage, curType, ident, config)) {
765 ;
766 } else {
767 return NO_ERROR;
768 }
769 }
770 }
771
Andreas Gampe2412f842014-09-30 20:55:57 -0700772 if (kIsDebug) {
773 printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n",
774 config.language[0], config.language[1],
775 config.country[0], config.country[1],
776 config.orientation, config.density,
777 String8(ident).string(), String8(str).string());
778 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800779
780 err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
781 myPackage, curType, ident, str, &spans, &config,
782 false, curFormat, overwrite);
783
784 return err;
785}
786
787status_t compileResourceFile(Bundle* bundle,
788 const sp<AaptAssets>& assets,
789 const sp<AaptFile>& in,
790 const ResTable_config& defParams,
791 const bool overwrite,
792 ResourceTable* outTable)
793{
794 ResXMLTree block;
795 status_t err = parseXMLResource(in, &block, false, true);
796 if (err != NO_ERROR) {
797 return err;
798 }
799
800 // Top-level tag.
801 const String16 resources16("resources");
802
803 // Identifier declaration tags.
804 const String16 declare_styleable16("declare-styleable");
805 const String16 attr16("attr");
806
807 // Data creation organizational tags.
808 const String16 string16("string");
809 const String16 drawable16("drawable");
810 const String16 color16("color");
811 const String16 bool16("bool");
812 const String16 integer16("integer");
813 const String16 dimen16("dimen");
814 const String16 fraction16("fraction");
815 const String16 style16("style");
816 const String16 plurals16("plurals");
817 const String16 array16("array");
818 const String16 string_array16("string-array");
819 const String16 integer_array16("integer-array");
820 const String16 public16("public");
821 const String16 public_padding16("public-padding");
822 const String16 private_symbols16("private-symbols");
823 const String16 java_symbol16("java-symbol");
824 const String16 add_resource16("add-resource");
825 const String16 skip16("skip");
826 const String16 eat_comment16("eat-comment");
827
828 // Data creation tags.
829 const String16 bag16("bag");
830 const String16 item16("item");
831
832 // Attribute type constants.
833 const String16 enum16("enum");
834
835 // plural values
836 const String16 other16("other");
837 const String16 quantityOther16("^other");
838 const String16 zero16("zero");
839 const String16 quantityZero16("^zero");
840 const String16 one16("one");
841 const String16 quantityOne16("^one");
842 const String16 two16("two");
843 const String16 quantityTwo16("^two");
844 const String16 few16("few");
845 const String16 quantityFew16("^few");
846 const String16 many16("many");
847 const String16 quantityMany16("^many");
848
849 // useful attribute names and special values
850 const String16 name16("name");
851 const String16 translatable16("translatable");
852 const String16 formatted16("formatted");
853 const String16 false16("false");
854
855 const String16 myPackage(assets->getPackage());
856
857 bool hasErrors = false;
858
859 bool fileIsTranslatable = true;
860 if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) {
861 fileIsTranslatable = false;
862 }
863
864 DefaultKeyedVector<String16, uint32_t> nextPublicId(0);
865
Adam Lesinski8ff15b42013-10-07 16:54:01 -0700866 // Stores the resource names that were skipped. Typically this happens when
867 // AAPT is invoked without a product specified and a resource has no
868 // 'default' product attribute.
869 KeyedVector<type_ident_pair_t, bool> skippedResourceNames;
870
Adam Lesinski282e1812014-01-23 18:17:42 -0800871 ResXMLTree::event_code_t code;
872 do {
873 code = block.next();
874 } while (code == ResXMLTree::START_NAMESPACE);
875
876 size_t len;
877 if (code != ResXMLTree::START_TAG) {
878 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
879 "No start tag found\n");
880 return UNKNOWN_ERROR;
881 }
882 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
883 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
884 "Invalid start tag %s\n", String8(block.getElementName(&len)).string());
885 return UNKNOWN_ERROR;
886 }
887
888 ResTable_config curParams(defParams);
889
890 ResTable_config pseudoParams(curParams);
Anton Krumina2ef5c02014-03-12 14:46:44 -0700891 pseudoParams.language[0] = 'e';
892 pseudoParams.language[1] = 'n';
893 pseudoParams.country[0] = 'X';
894 pseudoParams.country[1] = 'A';
895
896 ResTable_config pseudoBidiParams(curParams);
897 pseudoBidiParams.language[0] = 'a';
898 pseudoBidiParams.language[1] = 'r';
899 pseudoBidiParams.country[0] = 'X';
900 pseudoBidiParams.country[1] = 'B';
Adam Lesinski282e1812014-01-23 18:17:42 -0800901
Igor Viarheichyk47843df2014-05-01 17:04:39 -0700902 // We should skip resources for pseudolocales if they were
903 // already added automatically. This is a fix for a transition period when
904 // manually pseudolocalized resources may be expected.
905 // TODO: remove this check after next SDK version release.
906 if ((bundle->getPseudolocalize() & PSEUDO_ACCENTED &&
907 curParams.locale == pseudoParams.locale) ||
908 (bundle->getPseudolocalize() & PSEUDO_BIDI &&
909 curParams.locale == pseudoBidiParams.locale)) {
910 SourcePos(in->getPrintableSource(), 0).warning(
911 "Resource file %s is skipped as pseudolocalization"
912 " was done automatically.",
913 in->getPrintableSource().string());
914 return NO_ERROR;
915 }
916
Adam Lesinski282e1812014-01-23 18:17:42 -0800917 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
918 if (code == ResXMLTree::START_TAG) {
919 const String16* curTag = NULL;
920 String16 curType;
Adrian Roos58922482015-06-01 17:59:41 -0700921 String16 curName;
Adam Lesinski282e1812014-01-23 18:17:42 -0800922 int32_t curFormat = ResTable_map::TYPE_ANY;
923 bool curIsBag = false;
924 bool curIsBagReplaceOnOverwrite = false;
925 bool curIsStyled = false;
926 bool curIsPseudolocalizable = false;
927 bool curIsFormatted = fileIsTranslatable;
928 bool localHasErrors = false;
929
930 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
931 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
932 && code != ResXMLTree::BAD_DOCUMENT) {
933 if (code == ResXMLTree::END_TAG) {
934 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
935 break;
936 }
937 }
938 }
939 continue;
940
941 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
942 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
943 && code != ResXMLTree::BAD_DOCUMENT) {
944 if (code == ResXMLTree::END_TAG) {
945 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
946 break;
947 }
948 }
949 }
950 continue;
951
952 } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
953 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
954
955 String16 type;
956 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
957 if (typeIdx < 0) {
958 srcPos.error("A 'type' attribute is required for <public>\n");
959 hasErrors = localHasErrors = true;
960 }
961 type = String16(block.getAttributeStringValue(typeIdx, &len));
962
963 String16 name;
964 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
965 if (nameIdx < 0) {
966 srcPos.error("A 'name' attribute is required for <public>\n");
967 hasErrors = localHasErrors = true;
968 }
969 name = String16(block.getAttributeStringValue(nameIdx, &len));
970
971 uint32_t ident = 0;
972 ssize_t identIdx = block.indexOfAttribute(NULL, "id");
973 if (identIdx >= 0) {
974 const char16_t* identStr = block.getAttributeStringValue(identIdx, &len);
975 Res_value identValue;
976 if (!ResTable::stringToInt(identStr, len, &identValue)) {
977 srcPos.error("Given 'id' attribute is not an integer: %s\n",
978 String8(block.getAttributeStringValue(identIdx, &len)).string());
979 hasErrors = localHasErrors = true;
980 } else {
981 ident = identValue.data;
982 nextPublicId.replaceValueFor(type, ident+1);
983 }
984 } else if (nextPublicId.indexOfKey(type) < 0) {
985 srcPos.error("No 'id' attribute supplied <public>,"
986 " and no previous id defined in this file.\n");
987 hasErrors = localHasErrors = true;
988 } else if (!localHasErrors) {
989 ident = nextPublicId.valueFor(type);
990 nextPublicId.replaceValueFor(type, ident+1);
991 }
992
993 if (!localHasErrors) {
994 err = outTable->addPublic(srcPos, myPackage, type, name, ident);
995 if (err < NO_ERROR) {
996 hasErrors = localHasErrors = true;
997 }
998 }
999 if (!localHasErrors) {
1000 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1001 if (symbols != NULL) {
1002 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1003 }
1004 if (symbols != NULL) {
1005 symbols->makeSymbolPublic(String8(name), srcPos);
1006 String16 comment(
1007 block.getComment(&len) ? block.getComment(&len) : nulStr);
1008 symbols->appendComment(String8(name), comment, srcPos);
1009 } else {
1010 srcPos.error("Unable to create symbols!\n");
1011 hasErrors = localHasErrors = true;
1012 }
1013 }
1014
1015 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1016 if (code == ResXMLTree::END_TAG) {
1017 if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
1018 break;
1019 }
1020 }
1021 }
1022 continue;
1023
1024 } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
1025 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1026
1027 String16 type;
1028 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1029 if (typeIdx < 0) {
1030 srcPos.error("A 'type' attribute is required for <public-padding>\n");
1031 hasErrors = localHasErrors = true;
1032 }
1033 type = String16(block.getAttributeStringValue(typeIdx, &len));
1034
1035 String16 name;
1036 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1037 if (nameIdx < 0) {
1038 srcPos.error("A 'name' attribute is required for <public-padding>\n");
1039 hasErrors = localHasErrors = true;
1040 }
1041 name = String16(block.getAttributeStringValue(nameIdx, &len));
1042
1043 uint32_t start = 0;
1044 ssize_t startIdx = block.indexOfAttribute(NULL, "start");
1045 if (startIdx >= 0) {
1046 const char16_t* startStr = block.getAttributeStringValue(startIdx, &len);
1047 Res_value startValue;
1048 if (!ResTable::stringToInt(startStr, len, &startValue)) {
1049 srcPos.error("Given 'start' attribute is not an integer: %s\n",
1050 String8(block.getAttributeStringValue(startIdx, &len)).string());
1051 hasErrors = localHasErrors = true;
1052 } else {
1053 start = startValue.data;
1054 }
1055 } else if (nextPublicId.indexOfKey(type) < 0) {
1056 srcPos.error("No 'start' attribute supplied <public-padding>,"
1057 " and no previous id defined in this file.\n");
1058 hasErrors = localHasErrors = true;
1059 } else if (!localHasErrors) {
1060 start = nextPublicId.valueFor(type);
1061 }
1062
1063 uint32_t end = 0;
1064 ssize_t endIdx = block.indexOfAttribute(NULL, "end");
1065 if (endIdx >= 0) {
1066 const char16_t* endStr = block.getAttributeStringValue(endIdx, &len);
1067 Res_value endValue;
1068 if (!ResTable::stringToInt(endStr, len, &endValue)) {
1069 srcPos.error("Given 'end' attribute is not an integer: %s\n",
1070 String8(block.getAttributeStringValue(endIdx, &len)).string());
1071 hasErrors = localHasErrors = true;
1072 } else {
1073 end = endValue.data;
1074 }
1075 } else {
1076 srcPos.error("No 'end' attribute supplied <public-padding>\n");
1077 hasErrors = localHasErrors = true;
1078 }
1079
1080 if (end >= start) {
1081 nextPublicId.replaceValueFor(type, end+1);
1082 } else {
1083 srcPos.error("Padding start '%ul' is after end '%ul'\n",
1084 start, end);
1085 hasErrors = localHasErrors = true;
1086 }
1087
1088 String16 comment(
1089 block.getComment(&len) ? block.getComment(&len) : nulStr);
1090 for (uint32_t curIdent=start; curIdent<=end; curIdent++) {
1091 if (localHasErrors) {
1092 break;
1093 }
1094 String16 curName(name);
1095 char buf[64];
1096 sprintf(buf, "%d", (int)(end-curIdent+1));
1097 curName.append(String16(buf));
1098
1099 err = outTable->addEntry(srcPos, myPackage, type, curName,
1100 String16("padding"), NULL, &curParams, false,
1101 ResTable_map::TYPE_STRING, overwrite);
1102 if (err < NO_ERROR) {
1103 hasErrors = localHasErrors = true;
1104 break;
1105 }
1106 err = outTable->addPublic(srcPos, myPackage, type,
1107 curName, curIdent);
1108 if (err < NO_ERROR) {
1109 hasErrors = localHasErrors = true;
1110 break;
1111 }
1112 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1113 if (symbols != NULL) {
1114 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1115 }
1116 if (symbols != NULL) {
1117 symbols->makeSymbolPublic(String8(curName), srcPos);
1118 symbols->appendComment(String8(curName), comment, srcPos);
1119 } else {
1120 srcPos.error("Unable to create symbols!\n");
1121 hasErrors = localHasErrors = true;
1122 }
1123 }
1124
1125 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1126 if (code == ResXMLTree::END_TAG) {
1127 if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
1128 break;
1129 }
1130 }
1131 }
1132 continue;
1133
1134 } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
1135 String16 pkg;
1136 ssize_t pkgIdx = block.indexOfAttribute(NULL, "package");
1137 if (pkgIdx < 0) {
1138 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1139 "A 'package' attribute is required for <private-symbols>\n");
1140 hasErrors = localHasErrors = true;
1141 }
1142 pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
1143 if (!localHasErrors) {
1144 assets->setSymbolsPrivatePackage(String8(pkg));
1145 }
1146
1147 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1148 if (code == ResXMLTree::END_TAG) {
1149 if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
1150 break;
1151 }
1152 }
1153 }
1154 continue;
1155
1156 } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
1157 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1158
1159 String16 type;
1160 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1161 if (typeIdx < 0) {
1162 srcPos.error("A 'type' attribute is required for <public>\n");
1163 hasErrors = localHasErrors = true;
1164 }
1165 type = String16(block.getAttributeStringValue(typeIdx, &len));
1166
1167 String16 name;
1168 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1169 if (nameIdx < 0) {
1170 srcPos.error("A 'name' attribute is required for <public>\n");
1171 hasErrors = localHasErrors = true;
1172 }
1173 name = String16(block.getAttributeStringValue(nameIdx, &len));
1174
1175 sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R"));
1176 if (symbols != NULL) {
1177 symbols = symbols->addNestedSymbol(String8(type), srcPos);
1178 }
1179 if (symbols != NULL) {
1180 symbols->makeSymbolJavaSymbol(String8(name), srcPos);
1181 String16 comment(
1182 block.getComment(&len) ? block.getComment(&len) : nulStr);
1183 symbols->appendComment(String8(name), comment, srcPos);
1184 } else {
1185 srcPos.error("Unable to create symbols!\n");
1186 hasErrors = localHasErrors = true;
1187 }
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), java_symbol16.string()) == 0) {
1192 break;
1193 }
1194 }
1195 }
1196 continue;
1197
1198
1199 } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
1200 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1201
1202 String16 typeName;
1203 ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
1204 if (typeIdx < 0) {
1205 srcPos.error("A 'type' attribute is required for <add-resource>\n");
1206 hasErrors = localHasErrors = true;
1207 }
1208 typeName = String16(block.getAttributeStringValue(typeIdx, &len));
1209
1210 String16 name;
1211 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1212 if (nameIdx < 0) {
1213 srcPos.error("A 'name' attribute is required for <add-resource>\n");
1214 hasErrors = localHasErrors = true;
1215 }
1216 name = String16(block.getAttributeStringValue(nameIdx, &len));
1217
1218 outTable->canAddEntry(srcPos, myPackage, typeName, name);
1219
1220 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1221 if (code == ResXMLTree::END_TAG) {
1222 if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
1223 break;
1224 }
1225 }
1226 }
1227 continue;
1228
1229 } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
1230 SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
1231
1232 String16 ident;
1233 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
1234 if (identIdx < 0) {
1235 srcPos.error("A 'name' attribute is required for <declare-styleable>\n");
1236 hasErrors = localHasErrors = true;
1237 }
1238 ident = String16(block.getAttributeStringValue(identIdx, &len));
1239
1240 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1241 if (!localHasErrors) {
1242 if (symbols != NULL) {
1243 symbols = symbols->addNestedSymbol(String8("styleable"), srcPos);
1244 }
1245 sp<AaptSymbols> styleSymbols = symbols;
1246 if (symbols != NULL) {
1247 symbols = symbols->addNestedSymbol(String8(ident), srcPos);
1248 }
1249 if (symbols == NULL) {
1250 srcPos.error("Unable to create symbols!\n");
1251 return UNKNOWN_ERROR;
1252 }
1253
1254 String16 comment(
1255 block.getComment(&len) ? block.getComment(&len) : nulStr);
1256 styleSymbols->appendComment(String8(ident), comment, srcPos);
1257 } else {
1258 symbols = NULL;
1259 }
1260
1261 while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1262 if (code == ResXMLTree::START_TAG) {
1263 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
1264 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1265 && code != ResXMLTree::BAD_DOCUMENT) {
1266 if (code == ResXMLTree::END_TAG) {
1267 if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
1268 break;
1269 }
1270 }
1271 }
1272 continue;
1273 } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
1274 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1275 && code != ResXMLTree::BAD_DOCUMENT) {
1276 if (code == ResXMLTree::END_TAG) {
1277 if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
1278 break;
1279 }
1280 }
1281 }
1282 continue;
1283 } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) {
1284 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1285 "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n",
1286 String8(block.getElementName(&len)).string());
1287 return UNKNOWN_ERROR;
1288 }
1289
1290 String16 comment(
1291 block.getComment(&len) ? block.getComment(&len) : nulStr);
1292 String16 itemIdent;
1293 err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true);
1294 if (err != NO_ERROR) {
1295 hasErrors = localHasErrors = true;
1296 }
1297
1298 if (symbols != NULL) {
1299 SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber());
1300 symbols->addSymbol(String8(itemIdent), 0, srcPos);
1301 symbols->appendComment(String8(itemIdent), comment, srcPos);
1302 //printf("Attribute %s comment: %s\n", String8(itemIdent).string(),
1303 // String8(comment).string());
1304 }
1305 } else if (code == ResXMLTree::END_TAG) {
1306 if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
1307 break;
1308 }
1309
1310 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1311 "Found tag </%s> where </attr> is expected\n",
1312 String8(block.getElementName(&len)).string());
1313 return UNKNOWN_ERROR;
1314 }
1315 }
1316 continue;
1317
1318 } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
1319 err = compileAttribute(in, block, myPackage, outTable, NULL);
1320 if (err != NO_ERROR) {
1321 hasErrors = true;
1322 }
1323 continue;
1324
1325 } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) {
1326 curTag = &item16;
1327 ssize_t attri = block.indexOfAttribute(NULL, "type");
1328 if (attri >= 0) {
1329 curType = String16(block.getAttributeStringValue(attri, &len));
Adrian Roos58922482015-06-01 17:59:41 -07001330 ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
1331 if (nameIdx >= 0) {
1332 curName = String16(block.getAttributeStringValue(nameIdx, &len));
1333 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001334 ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
1335 if (formatIdx >= 0) {
1336 String16 formatStr = String16(block.getAttributeStringValue(
1337 formatIdx, &len));
1338 curFormat = parse_flags(formatStr.string(), formatStr.size(),
1339 gFormatFlags);
1340 if (curFormat == 0) {
1341 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1342 "Tag <item> 'format' attribute value \"%s\" not valid\n",
1343 String8(formatStr).string());
1344 hasErrors = localHasErrors = true;
1345 }
1346 }
1347 } else {
1348 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1349 "A 'type' attribute is required for <item>\n");
1350 hasErrors = localHasErrors = true;
1351 }
1352 curIsStyled = true;
1353 } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
1354 // Note the existence and locale of every string we process
Narayan Kamath91447d82014-01-21 15:32:36 +00001355 char rawLocale[RESTABLE_MAX_LOCALE_LEN];
1356 curParams.getBcp47Locale(rawLocale);
Adam Lesinski282e1812014-01-23 18:17:42 -08001357 String8 locale(rawLocale);
1358 String16 name;
1359 String16 translatable;
1360 String16 formatted;
1361
1362 size_t n = block.getAttributeCount();
1363 for (size_t i = 0; i < n; i++) {
1364 size_t length;
Dan Albertf348c152014-09-08 18:28:00 -07001365 const char16_t* attr = block.getAttributeName(i, &length);
Adam Lesinski282e1812014-01-23 18:17:42 -08001366 if (strcmp16(attr, name16.string()) == 0) {
1367 name.setTo(block.getAttributeStringValue(i, &length));
1368 } else if (strcmp16(attr, translatable16.string()) == 0) {
1369 translatable.setTo(block.getAttributeStringValue(i, &length));
1370 } else if (strcmp16(attr, formatted16.string()) == 0) {
1371 formatted.setTo(block.getAttributeStringValue(i, &length));
1372 }
1373 }
1374
1375 if (name.size() > 0) {
Adrian Roos58922482015-06-01 17:59:41 -07001376 if (locale.size() == 0) {
1377 outTable->addDefaultLocalization(name);
1378 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001379 if (translatable == false16) {
1380 curIsFormatted = false;
1381 // Untranslatable strings must only exist in the default [empty] locale
1382 if (locale.size() > 0) {
Adam Lesinskia01a9372014-03-20 18:04:57 -07001383 SourcePos(in->getPrintableSource(), block.getLineNumber()).warning(
1384 "string '%s' marked untranslatable but exists in locale '%s'\n",
1385 String8(name).string(),
Adam Lesinski282e1812014-01-23 18:17:42 -08001386 locale.string());
1387 // hasErrors = localHasErrors = true;
1388 } else {
1389 // Intentionally empty block:
1390 //
1391 // Don't add untranslatable strings to the localization table; that
1392 // way if we later see localizations of them, they'll be flagged as
1393 // having no default translation.
1394 }
1395 } else {
Adam Lesinskia01a9372014-03-20 18:04:57 -07001396 outTable->addLocalization(
1397 name,
1398 locale,
1399 SourcePos(in->getPrintableSource(), block.getLineNumber()));
Adam Lesinski282e1812014-01-23 18:17:42 -08001400 }
1401
1402 if (formatted == false16) {
1403 curIsFormatted = false;
1404 }
1405 }
1406
1407 curTag = &string16;
1408 curType = string16;
1409 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
1410 curIsStyled = true;
Igor Viarheichyk84410b02014-04-30 11:56:42 -07001411 curIsPseudolocalizable = fileIsTranslatable && (translatable != false16);
Adam Lesinski282e1812014-01-23 18:17:42 -08001412 } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) {
1413 curTag = &drawable16;
1414 curType = drawable16;
1415 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
1416 } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) {
1417 curTag = &color16;
1418 curType = color16;
1419 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
1420 } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
1421 curTag = &bool16;
1422 curType = bool16;
1423 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
1424 } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
1425 curTag = &integer16;
1426 curType = integer16;
1427 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
1428 } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) {
1429 curTag = &dimen16;
1430 curType = dimen16;
1431 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION;
1432 } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) {
1433 curTag = &fraction16;
1434 curType = fraction16;
1435 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION;
1436 } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) {
1437 curTag = &bag16;
1438 curIsBag = true;
1439 ssize_t attri = block.indexOfAttribute(NULL, "type");
1440 if (attri >= 0) {
1441 curType = String16(block.getAttributeStringValue(attri, &len));
1442 } else {
1443 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1444 "A 'type' attribute is required for <bag>\n");
1445 hasErrors = localHasErrors = true;
1446 }
1447 } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) {
1448 curTag = &style16;
1449 curType = style16;
1450 curIsBag = true;
1451 } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) {
1452 curTag = &plurals16;
1453 curType = plurals16;
1454 curIsBag = true;
Anton Krumina2ef5c02014-03-12 14:46:44 -07001455 curIsPseudolocalizable = fileIsTranslatable;
Adam Lesinski282e1812014-01-23 18:17:42 -08001456 } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
1457 curTag = &array16;
1458 curType = array16;
1459 curIsBag = true;
1460 curIsBagReplaceOnOverwrite = true;
1461 ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
1462 if (formatIdx >= 0) {
1463 String16 formatStr = String16(block.getAttributeStringValue(
1464 formatIdx, &len));
1465 curFormat = parse_flags(formatStr.string(), formatStr.size(),
1466 gFormatFlags);
1467 if (curFormat == 0) {
1468 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1469 "Tag <array> 'format' attribute value \"%s\" not valid\n",
1470 String8(formatStr).string());
1471 hasErrors = localHasErrors = true;
1472 }
1473 }
1474 } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
1475 // Check whether these strings need valid formats.
1476 // (simplified form of what string16 does above)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001477 bool isTranslatable = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001478 size_t n = block.getAttributeCount();
Narayan Kamath9a9fa162013-12-18 13:27:30 +00001479
1480 // Pseudolocalizable by default, unless this string array isn't
1481 // translatable.
Adam Lesinski282e1812014-01-23 18:17:42 -08001482 for (size_t i = 0; i < n; i++) {
1483 size_t length;
Dan Albertf348c152014-09-08 18:28:00 -07001484 const char16_t* attr = block.getAttributeName(i, &length);
Narayan Kamath9a9fa162013-12-18 13:27:30 +00001485 if (strcmp16(attr, formatted16.string()) == 0) {
Dan Albertf348c152014-09-08 18:28:00 -07001486 const char16_t* value = block.getAttributeStringValue(i, &length);
Adam Lesinski282e1812014-01-23 18:17:42 -08001487 if (strcmp16(value, false16.string()) == 0) {
1488 curIsFormatted = false;
Adam Lesinski282e1812014-01-23 18:17:42 -08001489 }
Anton Krumina2ef5c02014-03-12 14:46:44 -07001490 } else if (strcmp16(attr, translatable16.string()) == 0) {
Dan Albertf348c152014-09-08 18:28:00 -07001491 const char16_t* value = block.getAttributeStringValue(i, &length);
Anton Krumina2ef5c02014-03-12 14:46:44 -07001492 if (strcmp16(value, false16.string()) == 0) {
1493 isTranslatable = false;
1494 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001495 }
1496 }
1497
1498 curTag = &string_array16;
1499 curType = array16;
1500 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
1501 curIsBag = true;
1502 curIsBagReplaceOnOverwrite = true;
Anton Krumina2ef5c02014-03-12 14:46:44 -07001503 curIsPseudolocalizable = isTranslatable && fileIsTranslatable;
Adam Lesinski282e1812014-01-23 18:17:42 -08001504 } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
1505 curTag = &integer_array16;
1506 curType = array16;
1507 curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
1508 curIsBag = true;
1509 curIsBagReplaceOnOverwrite = true;
1510 } else {
1511 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1512 "Found tag %s where item is expected\n",
1513 String8(block.getElementName(&len)).string());
1514 return UNKNOWN_ERROR;
1515 }
1516
1517 String16 ident;
1518 ssize_t identIdx = block.indexOfAttribute(NULL, "name");
1519 if (identIdx >= 0) {
1520 ident = String16(block.getAttributeStringValue(identIdx, &len));
1521 } else {
1522 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1523 "A 'name' attribute is required for <%s>\n",
1524 String8(*curTag).string());
1525 hasErrors = localHasErrors = true;
1526 }
1527
1528 String16 product;
1529 identIdx = block.indexOfAttribute(NULL, "product");
1530 if (identIdx >= 0) {
1531 product = String16(block.getAttributeStringValue(identIdx, &len));
1532 }
1533
1534 String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
1535
1536 if (curIsBag) {
1537 // Figure out the parent of this bag...
1538 String16 parentIdent;
1539 ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent");
1540 if (parentIdentIdx >= 0) {
1541 parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len));
1542 } else {
1543 ssize_t sep = ident.findLast('.');
1544 if (sep >= 0) {
1545 parentIdent.setTo(ident, sep);
1546 }
1547 }
1548
1549 if (!localHasErrors) {
1550 err = outTable->startBag(SourcePos(in->getPrintableSource(),
1551 block.getLineNumber()), myPackage, curType, ident,
1552 parentIdent, &curParams,
1553 overwrite, curIsBagReplaceOnOverwrite);
1554 if (err != NO_ERROR) {
1555 hasErrors = localHasErrors = true;
1556 }
1557 }
1558
1559 ssize_t elmIndex = 0;
1560 char elmIndexStr[14];
1561 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1562 && code != ResXMLTree::BAD_DOCUMENT) {
1563
1564 if (code == ResXMLTree::START_TAG) {
1565 if (strcmp16(block.getElementName(&len), item16.string()) != 0) {
1566 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1567 "Tag <%s> can not appear inside <%s>, only <item>\n",
1568 String8(block.getElementName(&len)).string(),
1569 String8(*curTag).string());
1570 return UNKNOWN_ERROR;
1571 }
1572
1573 String16 itemIdent;
1574 if (curType == array16) {
1575 sprintf(elmIndexStr, "^index_%d", (int)elmIndex++);
1576 itemIdent = String16(elmIndexStr);
1577 } else if (curType == plurals16) {
1578 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity");
1579 if (itemIdentIdx >= 0) {
1580 String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len));
1581 if (quantity16 == other16) {
1582 itemIdent = quantityOther16;
1583 }
1584 else if (quantity16 == zero16) {
1585 itemIdent = quantityZero16;
1586 }
1587 else if (quantity16 == one16) {
1588 itemIdent = quantityOne16;
1589 }
1590 else if (quantity16 == two16) {
1591 itemIdent = quantityTwo16;
1592 }
1593 else if (quantity16 == few16) {
1594 itemIdent = quantityFew16;
1595 }
1596 else if (quantity16 == many16) {
1597 itemIdent = quantityMany16;
1598 }
1599 else {
1600 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1601 "Illegal 'quantity' attribute is <item> inside <plurals>\n");
1602 hasErrors = localHasErrors = true;
1603 }
1604 } else {
1605 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1606 "A 'quantity' attribute is required for <item> inside <plurals>\n");
1607 hasErrors = localHasErrors = true;
1608 }
1609 } else {
1610 ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
1611 if (itemIdentIdx >= 0) {
1612 itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
1613 } else {
1614 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1615 "A 'name' attribute is required for <item>\n");
1616 hasErrors = localHasErrors = true;
1617 }
1618 }
1619
1620 ResXMLParser::ResXMLPosition parserPosition;
1621 block.getPosition(&parserPosition);
1622
1623 err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
1624 ident, parentIdent, itemIdent, curFormat, curIsFormatted,
Anton Krumina2ef5c02014-03-12 14:46:44 -07001625 product, NO_PSEUDOLOCALIZATION, overwrite, outTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001626 if (err == NO_ERROR) {
1627 if (curIsPseudolocalizable && localeIsDefined(curParams)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001628 && bundle->getPseudolocalize() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001629 // pseudolocalize here
Anton Krumina2ef5c02014-03-12 14:46:44 -07001630 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
1631 PSEUDO_ACCENTED) {
1632 block.setPosition(parserPosition);
1633 err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
1634 curType, ident, parentIdent, itemIdent, curFormat,
1635 curIsFormatted, product, PSEUDO_ACCENTED,
1636 overwrite, outTable);
1637 }
1638 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
1639 PSEUDO_BIDI) {
1640 block.setPosition(parserPosition);
1641 err = parseAndAddBag(bundle, in, &block, pseudoBidiParams, myPackage,
1642 curType, ident, parentIdent, itemIdent, curFormat,
1643 curIsFormatted, product, PSEUDO_BIDI,
1644 overwrite, outTable);
1645 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001646 }
Anton Krumina2ef5c02014-03-12 14:46:44 -07001647 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001648 if (err != NO_ERROR) {
1649 hasErrors = localHasErrors = true;
1650 }
1651 } else if (code == ResXMLTree::END_TAG) {
1652 if (strcmp16(block.getElementName(&len), curTag->string()) != 0) {
1653 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1654 "Found tag </%s> where </%s> is expected\n",
1655 String8(block.getElementName(&len)).string(),
1656 String8(*curTag).string());
1657 return UNKNOWN_ERROR;
1658 }
1659 break;
1660 }
1661 }
1662 } else {
1663 ResXMLParser::ResXMLPosition parserPosition;
1664 block.getPosition(&parserPosition);
1665
1666 err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
1667 *curTag, curIsStyled, curFormat, curIsFormatted,
Anton Krumina2ef5c02014-03-12 14:46:44 -07001668 product, NO_PSEUDOLOCALIZATION, overwrite, &skippedResourceNames, outTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001669
1670 if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
1671 hasErrors = localHasErrors = true;
1672 }
1673 else if (err == NO_ERROR) {
Adrian Roos58922482015-06-01 17:59:41 -07001674 if (curType == string16 && !curParams.language[0] && !curParams.country[0]) {
1675 outTable->addDefaultLocalization(curName);
1676 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001677 if (curIsPseudolocalizable && localeIsDefined(curParams)
Anton Krumina2ef5c02014-03-12 14:46:44 -07001678 && bundle->getPseudolocalize() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001679 // pseudolocalize here
Anton Krumina2ef5c02014-03-12 14:46:44 -07001680 if ((PSEUDO_ACCENTED & bundle->getPseudolocalize()) ==
1681 PSEUDO_ACCENTED) {
1682 block.setPosition(parserPosition);
1683 err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
1684 ident, *curTag, curIsStyled, curFormat,
1685 curIsFormatted, product,
1686 PSEUDO_ACCENTED, overwrite, &skippedResourceNames, outTable);
1687 }
1688 if ((PSEUDO_BIDI & bundle->getPseudolocalize()) ==
1689 PSEUDO_BIDI) {
1690 block.setPosition(parserPosition);
1691 err = parseAndAddEntry(bundle, in, &block, pseudoBidiParams,
1692 myPackage, curType, ident, *curTag, curIsStyled, curFormat,
1693 curIsFormatted, product,
1694 PSEUDO_BIDI, overwrite, &skippedResourceNames, outTable);
1695 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001696 if (err != NO_ERROR) {
1697 hasErrors = localHasErrors = true;
1698 }
1699 }
1700 }
1701 }
1702
1703#if 0
1704 if (comment.size() > 0) {
1705 printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(),
1706 String8(curType).string(), String8(ident).string(),
1707 String8(comment).string());
1708 }
1709#endif
1710 if (!localHasErrors) {
1711 outTable->appendComment(myPackage, curType, ident, comment, false);
1712 }
1713 }
1714 else if (code == ResXMLTree::END_TAG) {
1715 if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
1716 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1717 "Unexpected end tag %s\n", String8(block.getElementName(&len)).string());
1718 return UNKNOWN_ERROR;
1719 }
1720 }
1721 else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) {
1722 }
1723 else if (code == ResXMLTree::TEXT) {
1724 if (isWhitespace(block.getText(&len))) {
1725 continue;
1726 }
1727 SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
1728 "Found text \"%s\" where item tag is expected\n",
1729 String8(block.getText(&len)).string());
1730 return UNKNOWN_ERROR;
1731 }
1732 }
1733
Adam Lesinski8ff15b42013-10-07 16:54:01 -07001734 // For every resource defined, there must be exist one variant with a product attribute
1735 // set to 'default' (or no product attribute at all).
1736 // We check to see that for every resource that was ignored because of a mismatched
1737 // product attribute, some product variant of that resource was processed.
1738 for (size_t i = 0; i < skippedResourceNames.size(); i++) {
1739 if (skippedResourceNames[i]) {
1740 const type_ident_pair_t& p = skippedResourceNames.keyAt(i);
1741 if (!outTable->hasBagOrEntry(myPackage, p.type, p.ident)) {
1742 const char* bundleProduct =
1743 (bundle->getProduct() == NULL) ? "" : bundle->getProduct();
1744 fprintf(stderr, "In resource file %s: %s\n",
1745 in->getPrintableSource().string(),
1746 curParams.toString().string());
1747
1748 fprintf(stderr, "\t%s '%s' does not match product %s.\n"
1749 "\tYou may have forgotten to include a 'default' product variant"
1750 " of the resource.\n",
1751 String8(p.type).string(), String8(p.ident).string(),
1752 bundleProduct[0] == 0 ? "default" : bundleProduct);
1753 return UNKNOWN_ERROR;
1754 }
1755 }
1756 }
1757
Andreas Gampe2412f842014-09-30 20:55:57 -07001758 return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001759}
1760
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001761ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
1762 : mAssetsPackage(assetsPackage)
1763 , mPackageType(type)
1764 , mTypeIdOffset(0)
1765 , mNumLocal(0)
1766 , mBundle(bundle)
Adam Lesinski282e1812014-01-23 18:17:42 -08001767{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001768 ssize_t packageId = -1;
1769 switch (mPackageType) {
1770 case App:
1771 case AppFeature:
1772 packageId = 0x7f;
1773 break;
1774
1775 case System:
1776 packageId = 0x01;
1777 break;
1778
1779 case SharedLibrary:
1780 packageId = 0x00;
1781 break;
1782
1783 default:
1784 assert(0);
1785 break;
1786 }
1787 sp<Package> package = new Package(mAssetsPackage, packageId);
1788 mPackages.add(assetsPackage, package);
1789 mOrderedPackages.add(package);
1790
1791 // Every resource table always has one first entry, the bag attributes.
1792 const SourcePos unknown(String8("????"), 0);
1793 getType(mAssetsPackage, String16("attr"), unknown);
1794}
1795
1796static uint32_t findLargestTypeIdForPackage(const ResTable& table, const String16& packageName) {
1797 const size_t basePackageCount = table.getBasePackageCount();
1798 for (size_t i = 0; i < basePackageCount; i++) {
1799 if (packageName == table.getBasePackageName(i)) {
1800 return table.getLastTypeIdForPackage(i);
1801 }
1802 }
1803 return 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08001804}
1805
1806status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
1807{
1808 status_t err = assets->buildIncludedResources(bundle);
1809 if (err != NO_ERROR) {
1810 return err;
1811 }
1812
Adam Lesinski282e1812014-01-23 18:17:42 -08001813 mAssets = assets;
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001814 mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
Adam Lesinski282e1812014-01-23 18:17:42 -08001815
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001816 const String8& featureAfter = bundle->getFeatureAfterPackage();
1817 if (!featureAfter.isEmpty()) {
1818 AssetManager featureAssetManager;
1819 if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
1820 fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
1821 featureAfter.string());
1822 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001823 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001824
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001825 const ResTable& featureTable = featureAssetManager.getResources(false);
Dan Albert030f5362015-03-04 13:54:20 -08001826 mTypeIdOffset = std::max(mTypeIdOffset,
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001827 findLargestTypeIdForPackage(featureTable, mAssetsPackage));
1828 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001829
1830 return NO_ERROR;
1831}
1832
1833status_t ResourceTable::addPublic(const SourcePos& sourcePos,
1834 const String16& package,
1835 const String16& type,
1836 const String16& name,
1837 const uint32_t ident)
1838{
1839 uint32_t rid = mAssets->getIncludedResources()
1840 .identifierForName(name.string(), name.size(),
1841 type.string(), type.size(),
1842 package.string(), package.size());
1843 if (rid != 0) {
1844 sourcePos.error("Error declaring public resource %s/%s for included package %s\n",
1845 String8(type).string(), String8(name).string(),
1846 String8(package).string());
1847 return UNKNOWN_ERROR;
1848 }
1849
1850 sp<Type> t = getType(package, type, sourcePos);
1851 if (t == NULL) {
1852 return UNKNOWN_ERROR;
1853 }
1854 return t->addPublic(sourcePos, name, ident);
1855}
1856
1857status_t ResourceTable::addEntry(const SourcePos& sourcePos,
1858 const String16& package,
1859 const String16& type,
1860 const String16& name,
1861 const String16& value,
1862 const Vector<StringPool::entry_style_span>* style,
1863 const ResTable_config* params,
1864 const bool doSetIndex,
1865 const int32_t format,
1866 const bool overwrite)
1867{
Adam Lesinski282e1812014-01-23 18:17:42 -08001868 uint32_t rid = mAssets->getIncludedResources()
1869 .identifierForName(name.string(), name.size(),
1870 type.string(), type.size(),
1871 package.string(), package.size());
1872 if (rid != 0) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001873 sourcePos.error("Resource entry %s/%s is already defined in package %s.",
1874 String8(type).string(), String8(name).string(), String8(package).string());
1875 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001876 }
1877
Adam Lesinski282e1812014-01-23 18:17:42 -08001878 sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
1879 params, doSetIndex);
1880 if (e == NULL) {
1881 return UNKNOWN_ERROR;
1882 }
1883 status_t err = e->setItem(sourcePos, value, style, format, overwrite);
1884 if (err == NO_ERROR) {
1885 mNumLocal++;
1886 }
1887 return err;
1888}
1889
1890status_t ResourceTable::startBag(const SourcePos& sourcePos,
1891 const String16& package,
1892 const String16& type,
1893 const String16& name,
1894 const String16& bagParent,
1895 const ResTable_config* params,
1896 bool overlay,
Andreas Gampe2412f842014-09-30 20:55:57 -07001897 bool replace, bool /* isId */)
Adam Lesinski282e1812014-01-23 18:17:42 -08001898{
1899 status_t result = NO_ERROR;
1900
1901 // Check for adding entries in other packages... for now we do
1902 // nothing. We need to do the right thing here to support skinning.
1903 uint32_t rid = mAssets->getIncludedResources()
1904 .identifierForName(name.string(), name.size(),
1905 type.string(), type.size(),
1906 package.string(), package.size());
1907 if (rid != 0) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001908 sourcePos.error("Resource entry %s/%s is already defined in package %s.",
1909 String8(type).string(), String8(name).string(), String8(package).string());
1910 return UNKNOWN_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001911 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001912
Adam Lesinski282e1812014-01-23 18:17:42 -08001913 if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) {
1914 bool canAdd = false;
1915 sp<Package> p = mPackages.valueFor(package);
1916 if (p != NULL) {
1917 sp<Type> t = p->getTypes().valueFor(type);
1918 if (t != NULL) {
1919 if (t->getCanAddEntries().indexOf(name) >= 0) {
1920 canAdd = true;
1921 }
1922 }
1923 }
1924 if (!canAdd) {
1925 sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n",
1926 String8(name).string());
1927 return UNKNOWN_ERROR;
1928 }
1929 }
1930 sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params);
1931 if (e == NULL) {
1932 return UNKNOWN_ERROR;
1933 }
1934
1935 // If a parent is explicitly specified, set it.
1936 if (bagParent.size() > 0) {
1937 e->setParent(bagParent);
1938 }
1939
1940 if ((result = e->makeItABag(sourcePos)) != NO_ERROR) {
1941 return result;
1942 }
1943
1944 if (overlay && replace) {
1945 return e->emptyBag(sourcePos);
1946 }
1947 return result;
1948}
1949
1950status_t ResourceTable::addBag(const SourcePos& sourcePos,
1951 const String16& package,
1952 const String16& type,
1953 const String16& name,
1954 const String16& bagParent,
1955 const String16& bagKey,
1956 const String16& value,
1957 const Vector<StringPool::entry_style_span>* style,
1958 const ResTable_config* params,
1959 bool replace, bool isId, const int32_t format)
1960{
1961 // Check for adding entries in other packages... for now we do
1962 // nothing. We need to do the right thing here to support skinning.
1963 uint32_t rid = mAssets->getIncludedResources()
1964 .identifierForName(name.string(), name.size(),
1965 type.string(), type.size(),
1966 package.string(), package.size());
1967 if (rid != 0) {
1968 return NO_ERROR;
1969 }
1970
1971#if 0
1972 if (name == String16("left")) {
1973 printf("Adding bag left: file=%s, line=%d, type=%s\n",
1974 sourcePos.file.striing(), sourcePos.line, String8(type).string());
1975 }
1976#endif
1977 sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params);
1978 if (e == NULL) {
1979 return UNKNOWN_ERROR;
1980 }
1981
1982 // If a parent is explicitly specified, set it.
1983 if (bagParent.size() > 0) {
1984 e->setParent(bagParent);
1985 }
1986
1987 const bool first = e->getBag().indexOfKey(bagKey) < 0;
1988 status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format);
1989 if (err == NO_ERROR && first) {
1990 mNumLocal++;
1991 }
1992 return err;
1993}
1994
1995bool ResourceTable::hasBagOrEntry(const String16& package,
1996 const String16& type,
1997 const String16& name) const
1998{
1999 // First look for this in the included resources...
2000 uint32_t rid = mAssets->getIncludedResources()
2001 .identifierForName(name.string(), name.size(),
2002 type.string(), type.size(),
2003 package.string(), package.size());
2004 if (rid != 0) {
2005 return true;
2006 }
2007
2008 sp<Package> p = mPackages.valueFor(package);
2009 if (p != NULL) {
2010 sp<Type> t = p->getTypes().valueFor(type);
2011 if (t != NULL) {
2012 sp<ConfigList> c = t->getConfigs().valueFor(name);
2013 if (c != NULL) return true;
2014 }
2015 }
2016
2017 return false;
2018}
2019
2020bool ResourceTable::hasBagOrEntry(const String16& package,
2021 const String16& type,
2022 const String16& name,
2023 const ResTable_config& config) const
2024{
2025 // First look for this in the included resources...
2026 uint32_t rid = mAssets->getIncludedResources()
2027 .identifierForName(name.string(), name.size(),
2028 type.string(), type.size(),
2029 package.string(), package.size());
2030 if (rid != 0) {
2031 return true;
2032 }
2033
2034 sp<Package> p = mPackages.valueFor(package);
2035 if (p != NULL) {
2036 sp<Type> t = p->getTypes().valueFor(type);
2037 if (t != NULL) {
2038 sp<ConfigList> c = t->getConfigs().valueFor(name);
2039 if (c != NULL) {
2040 sp<Entry> e = c->getEntries().valueFor(config);
2041 if (e != NULL) {
2042 return true;
2043 }
2044 }
2045 }
2046 }
2047
2048 return false;
2049}
2050
2051bool ResourceTable::hasBagOrEntry(const String16& ref,
2052 const String16* defType,
2053 const String16* defPackage)
2054{
2055 String16 package, type, name;
2056 if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name,
2057 defType, defPackage ? defPackage:&mAssetsPackage, NULL)) {
2058 return false;
2059 }
2060 return hasBagOrEntry(package, type, name);
2061}
2062
2063bool ResourceTable::appendComment(const String16& package,
2064 const String16& type,
2065 const String16& name,
2066 const String16& comment,
2067 bool onlyIfEmpty)
2068{
2069 if (comment.size() <= 0) {
2070 return true;
2071 }
2072
2073 sp<Package> p = mPackages.valueFor(package);
2074 if (p != NULL) {
2075 sp<Type> t = p->getTypes().valueFor(type);
2076 if (t != NULL) {
2077 sp<ConfigList> c = t->getConfigs().valueFor(name);
2078 if (c != NULL) {
2079 c->appendComment(comment, onlyIfEmpty);
2080 return true;
2081 }
2082 }
2083 }
2084 return false;
2085}
2086
2087bool ResourceTable::appendTypeComment(const String16& package,
2088 const String16& type,
2089 const String16& name,
2090 const String16& comment)
2091{
2092 if (comment.size() <= 0) {
2093 return true;
2094 }
2095
2096 sp<Package> p = mPackages.valueFor(package);
2097 if (p != NULL) {
2098 sp<Type> t = p->getTypes().valueFor(type);
2099 if (t != NULL) {
2100 sp<ConfigList> c = t->getConfigs().valueFor(name);
2101 if (c != NULL) {
2102 c->appendTypeComment(comment);
2103 return true;
2104 }
2105 }
2106 }
2107 return false;
2108}
2109
2110void ResourceTable::canAddEntry(const SourcePos& pos,
2111 const String16& package, const String16& type, const String16& name)
2112{
2113 sp<Type> t = getType(package, type, pos);
2114 if (t != NULL) {
2115 t->canAddEntry(name);
2116 }
2117}
2118
2119size_t ResourceTable::size() const {
2120 return mPackages.size();
2121}
2122
2123size_t ResourceTable::numLocalResources() const {
2124 return mNumLocal;
2125}
2126
2127bool ResourceTable::hasResources() const {
2128 return mNumLocal > 0;
2129}
2130
Adam Lesinski27f69f42014-08-21 13:19:12 -07002131sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
2132 const bool isBase)
Adam Lesinski282e1812014-01-23 18:17:42 -08002133{
2134 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
Adam Lesinski27f69f42014-08-21 13:19:12 -07002135 status_t err = flatten(bundle, filter, data, isBase);
Adam Lesinski282e1812014-01-23 18:17:42 -08002136 return err == NO_ERROR ? data : NULL;
2137}
2138
2139inline uint32_t ResourceTable::getResId(const sp<Package>& p,
2140 const sp<Type>& t,
2141 uint32_t nameId)
2142{
2143 return makeResId(p->getAssignedId(), t->getIndex(), nameId);
2144}
2145
2146uint32_t ResourceTable::getResId(const String16& package,
2147 const String16& type,
2148 const String16& name,
2149 bool onlyPublic) const
2150{
2151 uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic);
2152 if (id != 0) return id; // cache hit
2153
Adam Lesinski282e1812014-01-23 18:17:42 -08002154 // First look for this in the included resources...
2155 uint32_t specFlags = 0;
2156 uint32_t rid = mAssets->getIncludedResources()
2157 .identifierForName(name.string(), name.size(),
2158 type.string(), type.size(),
2159 package.string(), package.size(),
2160 &specFlags);
2161 if (rid != 0) {
2162 if (onlyPublic) {
2163 if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
2164 return 0;
2165 }
2166 }
2167
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002168 return ResourceIdCache::store(package, type, name, onlyPublic, rid);
Adam Lesinski282e1812014-01-23 18:17:42 -08002169 }
2170
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002171 sp<Package> p = mPackages.valueFor(package);
2172 if (p == NULL) return 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08002173 sp<Type> t = p->getTypes().valueFor(type);
2174 if (t == NULL) return 0;
Adam Lesinski9b624c12014-11-19 17:49:26 -08002175 sp<ConfigList> c = t->getConfigs().valueFor(name);
2176 if (c == NULL) {
2177 if (type != String16("attr")) {
2178 return 0;
2179 }
2180 t = p->getTypes().valueFor(String16(kAttrPrivateType));
2181 if (t == NULL) return 0;
2182 c = t->getConfigs().valueFor(name);
2183 if (c == NULL) return 0;
2184 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002185 int32_t ei = c->getEntryIndex();
2186 if (ei < 0) return 0;
2187
2188 return ResourceIdCache::store(package, type, name, onlyPublic,
2189 getResId(p, t, ei));
2190}
2191
2192uint32_t ResourceTable::getResId(const String16& ref,
2193 const String16* defType,
2194 const String16* defPackage,
2195 const char** outErrorMsg,
2196 bool onlyPublic) const
2197{
2198 String16 package, type, name;
2199 bool refOnlyPublic = true;
2200 if (!ResTable::expandResourceRef(
2201 ref.string(), ref.size(), &package, &type, &name,
2202 defType, defPackage ? defPackage:&mAssetsPackage,
2203 outErrorMsg, &refOnlyPublic)) {
Andreas Gampe2412f842014-09-30 20:55:57 -07002204 if (kIsDebug) {
2205 printf("Expanding resource: ref=%s\n", String8(ref).string());
2206 printf("Expanding resource: defType=%s\n",
2207 defType ? String8(*defType).string() : "NULL");
2208 printf("Expanding resource: defPackage=%s\n",
2209 defPackage ? String8(*defPackage).string() : "NULL");
2210 printf("Expanding resource: ref=%s\n", String8(ref).string());
2211 printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n",
2212 String8(package).string(), String8(type).string(),
2213 String8(name).string());
2214 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002215 return 0;
2216 }
2217 uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic);
Andreas Gampe2412f842014-09-30 20:55:57 -07002218 if (kIsDebug) {
2219 printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
2220 String8(package).string(), String8(type).string(),
2221 String8(name).string(), res);
2222 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002223 if (res == 0) {
2224 if (outErrorMsg)
2225 *outErrorMsg = "No resource found that matches the given name";
2226 }
2227 return res;
2228}
2229
2230bool ResourceTable::isValidResourceName(const String16& s)
2231{
2232 const char16_t* p = s.string();
2233 bool first = true;
2234 while (*p) {
2235 if ((*p >= 'a' && *p <= 'z')
2236 || (*p >= 'A' && *p <= 'Z')
2237 || *p == '_'
2238 || (!first && *p >= '0' && *p <= '9')) {
2239 first = false;
2240 p++;
2241 continue;
2242 }
2243 return false;
2244 }
2245 return true;
2246}
2247
2248bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
2249 const String16& str,
2250 bool preserveSpaces, bool coerceType,
2251 uint32_t attrID,
2252 const Vector<StringPool::entry_style_span>* style,
2253 String16* outStr, void* accessorCookie,
2254 uint32_t attrType, const String8* configTypeName,
2255 const ConfigDescription* config)
2256{
2257 String16 finalStr;
2258
2259 bool res = true;
2260 if (style == NULL || style->size() == 0) {
2261 // Text is not styled so it can be any type... let's figure it out.
2262 res = mAssets->getIncludedResources()
2263 .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces,
2264 coerceType, attrID, NULL, &mAssetsPackage, this,
2265 accessorCookie, attrType);
2266 } else {
2267 // Styled text can only be a string, and while collecting the style
2268 // information we have already processed that string!
2269 outValue->size = sizeof(Res_value);
2270 outValue->res0 = 0;
2271 outValue->dataType = outValue->TYPE_STRING;
2272 outValue->data = 0;
2273 finalStr = str;
2274 }
2275
2276 if (!res) {
2277 return false;
2278 }
2279
2280 if (outValue->dataType == outValue->TYPE_STRING) {
2281 // Should do better merging styles.
2282 if (pool) {
2283 String8 configStr;
2284 if (config != NULL) {
2285 configStr = config->toString();
2286 } else {
2287 configStr = "(null)";
2288 }
Andreas Gampe2412f842014-09-30 20:55:57 -07002289 if (kIsDebug) {
2290 printf("Adding to pool string style #%zu config %s: %s\n",
2291 style != NULL ? style->size() : 0U,
2292 configStr.string(), String8(finalStr).string());
2293 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002294 if (style != NULL && style->size() > 0) {
2295 outValue->data = pool->add(finalStr, *style, configTypeName, config);
2296 } else {
2297 outValue->data = pool->add(finalStr, true, configTypeName, config);
2298 }
2299 } else {
2300 // Caller will fill this in later.
2301 outValue->data = 0;
2302 }
2303
2304 if (outStr) {
2305 *outStr = finalStr;
2306 }
2307
2308 }
2309
2310 return true;
2311}
2312
2313uint32_t ResourceTable::getCustomResource(
2314 const String16& package, const String16& type, const String16& name) const
2315{
2316 //printf("getCustomResource: %s %s %s\n", String8(package).string(),
2317 // String8(type).string(), String8(name).string());
2318 sp<Package> p = mPackages.valueFor(package);
2319 if (p == NULL) return 0;
2320 sp<Type> t = p->getTypes().valueFor(type);
2321 if (t == NULL) return 0;
2322 sp<ConfigList> c = t->getConfigs().valueFor(name);
Adam Lesinski9b624c12014-11-19 17:49:26 -08002323 if (c == NULL) {
2324 if (type != String16("attr")) {
2325 return 0;
2326 }
2327 t = p->getTypes().valueFor(String16(kAttrPrivateType));
2328 if (t == NULL) return 0;
2329 c = t->getConfigs().valueFor(name);
2330 if (c == NULL) return 0;
2331 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002332 int32_t ei = c->getEntryIndex();
2333 if (ei < 0) return 0;
2334 return getResId(p, t, ei);
2335}
2336
2337uint32_t ResourceTable::getCustomResourceWithCreation(
2338 const String16& package, const String16& type, const String16& name,
2339 const bool createIfNotFound)
2340{
2341 uint32_t resId = getCustomResource(package, type, name);
2342 if (resId != 0 || !createIfNotFound) {
2343 return resId;
2344 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002345
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002346 if (mAssetsPackage != package) {
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002347 mCurrentXmlPos.error("creating resource for external package %s: %s/%s.",
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002348 String8(package).string(), String8(type).string(), String8(name).string());
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002349 if (package == String16("android")) {
2350 mCurrentXmlPos.printf("did you mean to use @+id instead of @+android:id?");
2351 }
2352 return 0;
Adam Lesinskif90f2f8d2014-06-06 14:27:00 -07002353 }
2354
2355 String16 value("false");
Adam Lesinski282e1812014-01-23 18:17:42 -08002356 status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
2357 if (status == NO_ERROR) {
2358 resId = getResId(package, type, name);
2359 return resId;
2360 }
2361 return 0;
2362}
2363
2364uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const
2365{
2366 return origPackage;
2367}
2368
2369bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType)
2370{
2371 //printf("getAttributeType #%08x\n", attrID);
2372 Res_value value;
2373 if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) {
2374 //printf("getAttributeType #%08x (%s): #%08x\n", attrID,
2375 // String8(getEntry(attrID)->getName()).string(), value.data);
2376 *outType = value.data;
2377 return true;
2378 }
2379 return false;
2380}
2381
2382bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin)
2383{
2384 //printf("getAttributeMin #%08x\n", attrID);
2385 Res_value value;
2386 if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) {
2387 *outMin = value.data;
2388 return true;
2389 }
2390 return false;
2391}
2392
2393bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax)
2394{
2395 //printf("getAttributeMax #%08x\n", attrID);
2396 Res_value value;
2397 if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) {
2398 *outMax = value.data;
2399 return true;
2400 }
2401 return false;
2402}
2403
2404uint32_t ResourceTable::getAttributeL10N(uint32_t attrID)
2405{
2406 //printf("getAttributeL10N #%08x\n", attrID);
2407 Res_value value;
2408 if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) {
2409 return value.data;
2410 }
2411 return ResTable_map::L10N_NOT_REQUIRED;
2412}
2413
2414bool ResourceTable::getLocalizationSetting()
2415{
2416 return mBundle->getRequireLocalization();
2417}
2418
2419void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...)
2420{
2421 if (accessorCookie != NULL && fmt != NULL) {
2422 AccessorCookie* ac = (AccessorCookie*)accessorCookie;
2423 int retval=0;
2424 char buf[1024];
2425 va_list ap;
2426 va_start(ap, fmt);
2427 retval = vsnprintf(buf, sizeof(buf), fmt, ap);
2428 va_end(ap);
2429 ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n",
2430 buf, ac->attr.string(), ac->value.string());
2431 }
2432}
2433
2434bool ResourceTable::getAttributeKeys(
2435 uint32_t attrID, Vector<String16>* outKeys)
2436{
2437 sp<const Entry> e = getEntry(attrID);
2438 if (e != NULL) {
2439 const size_t N = e->getBag().size();
2440 for (size_t i=0; i<N; i++) {
2441 const String16& key = e->getBag().keyAt(i);
2442 if (key.size() > 0 && key.string()[0] != '^') {
2443 outKeys->add(key);
2444 }
2445 }
2446 return true;
2447 }
2448 return false;
2449}
2450
2451bool ResourceTable::getAttributeEnum(
2452 uint32_t attrID, const char16_t* name, size_t nameLen,
2453 Res_value* outValue)
2454{
2455 //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string());
2456 String16 nameStr(name, nameLen);
2457 sp<const Entry> e = getEntry(attrID);
2458 if (e != NULL) {
2459 const size_t N = e->getBag().size();
2460 for (size_t i=0; i<N; i++) {
2461 //printf("Comparing %s to %s\n", String8(name, nameLen).string(),
2462 // String8(e->getBag().keyAt(i)).string());
2463 if (e->getBag().keyAt(i) == nameStr) {
2464 return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue);
2465 }
2466 }
2467 }
2468 return false;
2469}
2470
2471bool ResourceTable::getAttributeFlags(
2472 uint32_t attrID, const char16_t* name, size_t nameLen,
2473 Res_value* outValue)
2474{
2475 outValue->dataType = Res_value::TYPE_INT_HEX;
2476 outValue->data = 0;
2477
2478 //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string());
2479 String16 nameStr(name, nameLen);
2480 sp<const Entry> e = getEntry(attrID);
2481 if (e != NULL) {
2482 const size_t N = e->getBag().size();
2483
2484 const char16_t* end = name + nameLen;
2485 const char16_t* pos = name;
2486 while (pos < end) {
2487 const char16_t* start = pos;
2488 while (pos < end && *pos != '|') {
2489 pos++;
2490 }
2491
2492 String16 nameStr(start, pos-start);
2493 size_t i;
2494 for (i=0; i<N; i++) {
2495 //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(),
2496 // String8(e->getBag().keyAt(i)).string());
2497 if (e->getBag().keyAt(i) == nameStr) {
2498 Res_value val;
2499 bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val);
2500 if (!got) {
2501 return false;
2502 }
2503 //printf("Got value: 0x%08x\n", val.data);
2504 outValue->data |= val.data;
2505 break;
2506 }
2507 }
2508
2509 if (i >= N) {
2510 // Didn't find this flag identifier.
2511 return false;
2512 }
2513 pos++;
2514 }
2515
2516 return true;
2517 }
2518 return false;
2519}
2520
2521status_t ResourceTable::assignResourceIds()
2522{
2523 const size_t N = mOrderedPackages.size();
2524 size_t pi;
2525 status_t firstError = NO_ERROR;
2526
2527 // First generate all bag attributes and assign indices.
2528 for (pi=0; pi<N; pi++) {
2529 sp<Package> p = mOrderedPackages.itemAt(pi);
2530 if (p == NULL || p->getTypes().size() == 0) {
2531 // Empty, skip!
2532 continue;
2533 }
2534
Adam Lesinski9b624c12014-11-19 17:49:26 -08002535 if (mPackageType == System) {
2536 p->movePrivateAttrs();
2537 }
2538
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002539 // This has no sense for packages being built as AppFeature (aka with a non-zero offset).
Adam Lesinski282e1812014-01-23 18:17:42 -08002540 status_t err = p->applyPublicTypeOrder();
2541 if (err != NO_ERROR && firstError == NO_ERROR) {
2542 firstError = err;
2543 }
2544
2545 // Generate attributes...
2546 const size_t N = p->getOrderedTypes().size();
2547 size_t ti;
2548 for (ti=0; ti<N; ti++) {
2549 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2550 if (t == NULL) {
2551 continue;
2552 }
2553 const size_t N = t->getOrderedConfigs().size();
2554 for (size_t ci=0; ci<N; ci++) {
2555 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2556 if (c == NULL) {
2557 continue;
2558 }
2559 const size_t N = c->getEntries().size();
2560 for (size_t ei=0; ei<N; ei++) {
2561 sp<Entry> e = c->getEntries().valueAt(ei);
2562 if (e == NULL) {
2563 continue;
2564 }
2565 status_t err = e->generateAttributes(this, p->getName());
2566 if (err != NO_ERROR && firstError == NO_ERROR) {
2567 firstError = err;
2568 }
2569 }
2570 }
2571 }
2572
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002573 uint32_t typeIdOffset = 0;
2574 if (mPackageType == AppFeature && p->getName() == mAssetsPackage) {
2575 typeIdOffset = mTypeIdOffset;
2576 }
2577
Adam Lesinski282e1812014-01-23 18:17:42 -08002578 const SourcePos unknown(String8("????"), 0);
2579 sp<Type> attr = p->getType(String16("attr"), unknown);
2580
2581 // Assign indices...
Adam Lesinski43a0df02014-08-18 17:14:57 -07002582 const size_t typeCount = p->getOrderedTypes().size();
2583 for (size_t ti = 0; ti < typeCount; ti++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002584 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2585 if (t == NULL) {
2586 continue;
2587 }
Adam Lesinski43a0df02014-08-18 17:14:57 -07002588
Adam Lesinski282e1812014-01-23 18:17:42 -08002589 err = t->applyPublicEntryOrder();
2590 if (err != NO_ERROR && firstError == NO_ERROR) {
2591 firstError = err;
2592 }
2593
2594 const size_t N = t->getOrderedConfigs().size();
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002595 t->setIndex(ti + 1 + typeIdOffset);
Adam Lesinski282e1812014-01-23 18:17:42 -08002596
2597 LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
2598 "First type is not attr!");
2599
2600 for (size_t ei=0; ei<N; ei++) {
2601 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
2602 if (c == NULL) {
2603 continue;
2604 }
2605 c->setEntryIndex(ei);
2606 }
2607 }
2608
Adam Lesinski9b624c12014-11-19 17:49:26 -08002609
Adam Lesinski282e1812014-01-23 18:17:42 -08002610 // Assign resource IDs to keys in bags...
Adam Lesinski43a0df02014-08-18 17:14:57 -07002611 for (size_t ti = 0; ti < typeCount; ti++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002612 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2613 if (t == NULL) {
2614 continue;
2615 }
Adam Lesinski9b624c12014-11-19 17:49:26 -08002616
Adam Lesinski282e1812014-01-23 18:17:42 -08002617 const size_t N = t->getOrderedConfigs().size();
2618 for (size_t ci=0; ci<N; ci++) {
2619 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
Adam Lesinski9b624c12014-11-19 17:49:26 -08002620 if (c == NULL) {
2621 continue;
2622 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002623 //printf("Ordered config #%d: %p\n", ci, c.get());
2624 const size_t N = c->getEntries().size();
2625 for (size_t ei=0; ei<N; ei++) {
2626 sp<Entry> e = c->getEntries().valueAt(ei);
2627 if (e == NULL) {
2628 continue;
2629 }
2630 status_t err = e->assignResourceIds(this, p->getName());
2631 if (err != NO_ERROR && firstError == NO_ERROR) {
2632 firstError = err;
2633 }
2634 }
2635 }
2636 }
2637 }
2638 return firstError;
2639}
2640
Adrian Roos58922482015-06-01 17:59:41 -07002641status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols,
2642 bool skipSymbolsWithoutDefaultLocalization) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002643 const size_t N = mOrderedPackages.size();
Adrian Roos58922482015-06-01 17:59:41 -07002644 const String8 defaultLocale;
2645 const String16 stringType("string");
Adam Lesinski282e1812014-01-23 18:17:42 -08002646 size_t pi;
2647
2648 for (pi=0; pi<N; pi++) {
2649 sp<Package> p = mOrderedPackages.itemAt(pi);
2650 if (p->getTypes().size() == 0) {
2651 // Empty, skip!
2652 continue;
2653 }
2654
2655 const size_t N = p->getOrderedTypes().size();
2656 size_t ti;
2657
2658 for (ti=0; ti<N; ti++) {
2659 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2660 if (t == NULL) {
2661 continue;
2662 }
Adam Lesinski9b624c12014-11-19 17:49:26 -08002663
Adam Lesinski282e1812014-01-23 18:17:42 -08002664 const size_t N = t->getOrderedConfigs().size();
Adam Lesinski9b624c12014-11-19 17:49:26 -08002665 sp<AaptSymbols> typeSymbols;
2666 if (t->getName() == String16(kAttrPrivateType)) {
2667 typeSymbols = outSymbols->addNestedSymbol(String8("attr"), t->getPos());
2668 } else {
2669 typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
2670 }
2671
Adam Lesinski3fb8c9b2014-09-09 16:05:10 -07002672 if (typeSymbols == NULL) {
2673 return UNKNOWN_ERROR;
2674 }
2675
Adam Lesinski282e1812014-01-23 18:17:42 -08002676 for (size_t ci=0; ci<N; ci++) {
2677 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2678 if (c == NULL) {
2679 continue;
2680 }
2681 uint32_t rid = getResId(p, t, ci);
2682 if (rid == 0) {
2683 return UNKNOWN_ERROR;
2684 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002685 if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) {
Adrian Roos58922482015-06-01 17:59:41 -07002686
2687 if (skipSymbolsWithoutDefaultLocalization &&
2688 t->getName() == stringType) {
2689
2690 // Don't generate symbols for strings without a default localization.
2691 if (mHasDefaultLocalization.find(c->getName())
2692 == mHasDefaultLocalization.end()) {
2693 // printf("Skip symbol [%08x] %s\n", rid,
2694 // String8(c->getName()).string());
2695 continue;
2696 }
2697 }
2698
Adam Lesinski282e1812014-01-23 18:17:42 -08002699 typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
2700
2701 String16 comment(c->getComment());
2702 typeSymbols->appendComment(String8(c->getName()), comment, c->getPos());
Adam Lesinski8ff15b42013-10-07 16:54:01 -07002703 //printf("Type symbol [%08x] %s comment: %s\n", rid,
2704 // String8(c->getName()).string(), String8(comment).string());
Adam Lesinski282e1812014-01-23 18:17:42 -08002705 comment = c->getTypeComment();
2706 typeSymbols->appendTypeComment(String8(c->getName()), comment);
Adam Lesinski282e1812014-01-23 18:17:42 -08002707 }
2708 }
2709 }
2710 }
2711 return NO_ERROR;
2712}
2713
2714
2715void
Adam Lesinskia01a9372014-03-20 18:04:57 -07002716ResourceTable::addLocalization(const String16& name, const String8& locale, const SourcePos& src)
Adam Lesinski282e1812014-01-23 18:17:42 -08002717{
Adam Lesinskia01a9372014-03-20 18:04:57 -07002718 mLocalizations[name][locale] = src;
Adam Lesinski282e1812014-01-23 18:17:42 -08002719}
2720
Adrian Roos58922482015-06-01 17:59:41 -07002721void
2722ResourceTable::addDefaultLocalization(const String16& name)
2723{
2724 mHasDefaultLocalization.insert(name);
2725}
2726
Adam Lesinski282e1812014-01-23 18:17:42 -08002727
2728/*!
2729 * Flag various sorts of localization problems. '+' indicates checks already implemented;
2730 * '-' indicates checks that will be implemented in the future.
2731 *
2732 * + A localized string for which no default-locale version exists => warning
2733 * + A string for which no version in an explicitly-requested locale exists => warning
2734 * + A localized translation of an translateable="false" string => warning
2735 * - A localized string not provided in every locale used by the table
2736 */
2737status_t
2738ResourceTable::validateLocalizations(void)
2739{
2740 status_t err = NO_ERROR;
2741 const String8 defaultLocale;
2742
2743 // For all strings...
Dan Albert030f5362015-03-04 13:54:20 -08002744 for (const auto& nameIter : mLocalizations) {
2745 const std::map<String8, SourcePos>& configSrcMap = nameIter.second;
Adam Lesinski282e1812014-01-23 18:17:42 -08002746
2747 // Look for strings with no default localization
Adam Lesinskia01a9372014-03-20 18:04:57 -07002748 if (configSrcMap.count(defaultLocale) == 0) {
2749 SourcePos().warning("string '%s' has no default translation.",
Dan Albert030f5362015-03-04 13:54:20 -08002750 String8(nameIter.first).string());
Adam Lesinskia01a9372014-03-20 18:04:57 -07002751 if (mBundle->getVerbose()) {
Dan Albert030f5362015-03-04 13:54:20 -08002752 for (const auto& locale : configSrcMap) {
2753 locale.second.printf("locale %s found", locale.first.string());
Adam Lesinskia01a9372014-03-20 18:04:57 -07002754 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002755 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002756 // !!! TODO: throw an error here in some circumstances
2757 }
2758
2759 // Check that all requested localizations are present for this string
Adam Lesinskifab50872014-04-16 14:40:42 -07002760 if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) {
2761 const char* allConfigs = mBundle->getConfigurations().string();
Adam Lesinski282e1812014-01-23 18:17:42 -08002762 const char* start = allConfigs;
2763 const char* comma;
Dan Albert030f5362015-03-04 13:54:20 -08002764
2765 std::set<String8> missingConfigs;
Adam Lesinskia01a9372014-03-20 18:04:57 -07002766 AaptLocaleValue locale;
Adam Lesinski282e1812014-01-23 18:17:42 -08002767 do {
2768 String8 config;
2769 comma = strchr(start, ',');
2770 if (comma != NULL) {
2771 config.setTo(start, comma - start);
2772 start = comma + 1;
2773 } else {
2774 config.setTo(start);
2775 }
2776
Adam Lesinskia01a9372014-03-20 18:04:57 -07002777 if (!locale.initFromFilterString(config)) {
2778 continue;
2779 }
2780
Anton Krumina2ef5c02014-03-12 14:46:44 -07002781 // don't bother with the pseudolocale "en_XA" or "ar_XB"
2782 if (config != "en_XA" && config != "ar_XB") {
Adam Lesinskia01a9372014-03-20 18:04:57 -07002783 if (configSrcMap.find(config) == configSrcMap.end()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002784 // okay, no specific localization found. it's possible that we are
2785 // requiring a specific regional localization [e.g. de_DE] but there is an
2786 // available string in the generic language localization [e.g. de];
2787 // consider that string to have fulfilled the localization requirement.
2788 String8 region(config.string(), 2);
Adam Lesinskia01a9372014-03-20 18:04:57 -07002789 if (configSrcMap.find(region) == configSrcMap.end() &&
2790 configSrcMap.count(defaultLocale) == 0) {
2791 missingConfigs.insert(config);
Adam Lesinski282e1812014-01-23 18:17:42 -08002792 }
2793 }
2794 }
Adam Lesinskia01a9372014-03-20 18:04:57 -07002795 } while (comma != NULL);
2796
2797 if (!missingConfigs.empty()) {
2798 String8 configStr;
Dan Albert030f5362015-03-04 13:54:20 -08002799 for (const auto& iter : missingConfigs) {
2800 configStr.appendFormat(" %s", iter.string());
Adam Lesinskia01a9372014-03-20 18:04:57 -07002801 }
2802 SourcePos().warning("string '%s' is missing %u required localizations:%s",
Dan Albert030f5362015-03-04 13:54:20 -08002803 String8(nameIter.first).string(),
Adam Lesinskia01a9372014-03-20 18:04:57 -07002804 (unsigned int)missingConfigs.size(),
2805 configStr.string());
2806 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002807 }
2808 }
2809
2810 return err;
2811}
2812
Adam Lesinski27f69f42014-08-21 13:19:12 -07002813status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
2814 const sp<AaptFile>& dest,
2815 const bool isBase)
Adam Lesinski282e1812014-01-23 18:17:42 -08002816{
Adam Lesinski282e1812014-01-23 18:17:42 -08002817 const ConfigDescription nullConfig;
2818
2819 const size_t N = mOrderedPackages.size();
2820 size_t pi;
2821
2822 const static String16 mipmap16("mipmap");
2823
2824 bool useUTF8 = !bundle->getUTF16StringsOption();
2825
Adam Lesinskide898ff2014-01-29 18:20:45 -08002826 // The libraries this table references.
2827 Vector<sp<Package> > libraryPackages;
Adam Lesinski6022deb2014-08-20 14:59:19 -07002828 const ResTable& table = mAssets->getIncludedResources();
2829 const size_t basePackageCount = table.getBasePackageCount();
2830 for (size_t i = 0; i < basePackageCount; i++) {
2831 size_t packageId = table.getBasePackageId(i);
2832 String16 packageName(table.getBasePackageName(i));
2833 if (packageId > 0x01 && packageId != 0x7f &&
2834 packageName != String16("android")) {
2835 libraryPackages.add(sp<Package>(new Package(packageName, packageId)));
2836 }
2837 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08002838
Adam Lesinski282e1812014-01-23 18:17:42 -08002839 // Iterate through all data, collecting all values (strings,
2840 // references, etc).
2841 StringPool valueStrings(useUTF8);
2842 Vector<sp<Entry> > allEntries;
2843 for (pi=0; pi<N; pi++) {
2844 sp<Package> p = mOrderedPackages.itemAt(pi);
2845 if (p->getTypes().size() == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002846 continue;
2847 }
2848
2849 StringPool typeStrings(useUTF8);
2850 StringPool keyStrings(useUTF8);
2851
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002852 ssize_t stringsAdded = 0;
Adam Lesinski282e1812014-01-23 18:17:42 -08002853 const size_t N = p->getOrderedTypes().size();
2854 for (size_t ti=0; ti<N; ti++) {
2855 sp<Type> t = p->getOrderedTypes().itemAt(ti);
2856 if (t == NULL) {
2857 typeStrings.add(String16("<empty>"), false);
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002858 stringsAdded++;
Adam Lesinski282e1812014-01-23 18:17:42 -08002859 continue;
2860 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002861
2862 while (stringsAdded < t->getIndex() - 1) {
2863 typeStrings.add(String16("<empty>"), false);
2864 stringsAdded++;
2865 }
2866
Adam Lesinski282e1812014-01-23 18:17:42 -08002867 const String16 typeName(t->getName());
2868 typeStrings.add(typeName, false);
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002869 stringsAdded++;
Adam Lesinski282e1812014-01-23 18:17:42 -08002870
2871 // This is a hack to tweak the sorting order of the final strings,
2872 // to put stuff that is generally not language-specific first.
2873 String8 configTypeName(typeName);
2874 if (configTypeName == "drawable" || configTypeName == "layout"
2875 || configTypeName == "color" || configTypeName == "anim"
2876 || configTypeName == "interpolator" || configTypeName == "animator"
2877 || configTypeName == "xml" || configTypeName == "menu"
2878 || configTypeName == "mipmap" || configTypeName == "raw") {
2879 configTypeName = "1complex";
2880 } else {
2881 configTypeName = "2value";
2882 }
2883
Adam Lesinski27f69f42014-08-21 13:19:12 -07002884 // mipmaps don't get filtered, so they will
2885 // allways end up in the base. Make sure they
2886 // don't end up in a split.
2887 if (typeName == mipmap16 && !isBase) {
2888 continue;
2889 }
2890
Adam Lesinski282e1812014-01-23 18:17:42 -08002891 const bool filterable = (typeName != mipmap16);
2892
2893 const size_t N = t->getOrderedConfigs().size();
2894 for (size_t ci=0; ci<N; ci++) {
2895 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
2896 if (c == NULL) {
2897 continue;
2898 }
2899 const size_t N = c->getEntries().size();
2900 for (size_t ei=0; ei<N; ei++) {
2901 ConfigDescription config = c->getEntries().keyAt(ei);
Adam Lesinskifab50872014-04-16 14:40:42 -07002902 if (filterable && !filter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002903 continue;
2904 }
2905 sp<Entry> e = c->getEntries().valueAt(ei);
2906 if (e == NULL) {
2907 continue;
2908 }
2909 e->setNameIndex(keyStrings.add(e->getName(), true));
2910
2911 // If this entry has no values for other configs,
2912 // and is the default config, then it is special. Otherwise
2913 // we want to add it with the config info.
2914 ConfigDescription* valueConfig = NULL;
2915 if (N != 1 || config == nullConfig) {
2916 valueConfig = &config;
2917 }
2918
2919 status_t err = e->prepareFlatten(&valueStrings, this,
2920 &configTypeName, &config);
2921 if (err != NO_ERROR) {
2922 return err;
2923 }
2924 allEntries.add(e);
2925 }
2926 }
2927 }
2928
2929 p->setTypeStrings(typeStrings.createStringBlock());
2930 p->setKeyStrings(keyStrings.createStringBlock());
2931 }
2932
2933 if (bundle->getOutputAPKFile() != NULL) {
2934 // Now we want to sort the value strings for better locality. This will
2935 // cause the positions of the strings to change, so we need to go back
2936 // through out resource entries and update them accordingly. Only need
2937 // to do this if actually writing the output file.
2938 valueStrings.sortByConfig();
2939 for (pi=0; pi<allEntries.size(); pi++) {
2940 allEntries[pi]->remapStringValue(&valueStrings);
2941 }
2942 }
2943
2944 ssize_t strAmt = 0;
Adam Lesinskide898ff2014-01-29 18:20:45 -08002945
Adam Lesinski282e1812014-01-23 18:17:42 -08002946 // Now build the array of package chunks.
2947 Vector<sp<AaptFile> > flatPackages;
2948 for (pi=0; pi<N; pi++) {
2949 sp<Package> p = mOrderedPackages.itemAt(pi);
2950 if (p->getTypes().size() == 0) {
2951 // Empty, skip!
2952 continue;
2953 }
2954
2955 const size_t N = p->getTypeStrings().size();
2956
2957 const size_t baseSize = sizeof(ResTable_package);
2958
2959 // Start the package data.
2960 sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
2961 ResTable_package* header = (ResTable_package*)data->editData(baseSize);
2962 if (header == NULL) {
2963 fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
2964 return NO_MEMORY;
2965 }
2966 memset(header, 0, sizeof(*header));
2967 header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
2968 header->header.headerSize = htods(sizeof(*header));
Adam Lesinski833f3cc2014-06-18 15:06:01 -07002969 header->id = htodl(static_cast<uint32_t>(p->getAssignedId()));
Adam Lesinski282e1812014-01-23 18:17:42 -08002970 strcpy16_htod(header->name, p->getName().string());
2971
2972 // Write the string blocks.
2973 const size_t typeStringsStart = data->getSize();
2974 sp<AaptFile> strFile = p->getTypeStringsData();
2975 ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
Andreas Gampe2412f842014-09-30 20:55:57 -07002976 if (kPrintStringMetrics) {
2977 fprintf(stderr, "**** type strings: %zd\n", SSIZE(amt));
2978 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002979 strAmt += amt;
2980 if (amt < 0) {
2981 return amt;
2982 }
2983 const size_t keyStringsStart = data->getSize();
2984 strFile = p->getKeyStringsData();
2985 amt = data->writeData(strFile->getData(), strFile->getSize());
Andreas Gampe2412f842014-09-30 20:55:57 -07002986 if (kPrintStringMetrics) {
2987 fprintf(stderr, "**** key strings: %zd\n", SSIZE(amt));
2988 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002989 strAmt += amt;
2990 if (amt < 0) {
2991 return amt;
2992 }
2993
Adam Lesinski27f69f42014-08-21 13:19:12 -07002994 if (isBase) {
2995 status_t err = flattenLibraryTable(data, libraryPackages);
2996 if (err != NO_ERROR) {
2997 fprintf(stderr, "ERROR: failed to write library table\n");
2998 return err;
2999 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003000 }
3001
Adam Lesinski282e1812014-01-23 18:17:42 -08003002 // Build the type chunks inside of this package.
3003 for (size_t ti=0; ti<N; ti++) {
3004 // Retrieve them in the same order as the type string block.
3005 size_t len;
3006 String16 typeName(p->getTypeStrings().stringAt(ti, &len));
3007 sp<Type> t = p->getTypes().valueFor(typeName);
3008 LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
3009 "Type name %s not found",
3010 String8(typeName).string());
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003011 if (t == NULL) {
3012 continue;
3013 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003014 const bool filterable = (typeName != mipmap16);
Adam Lesinski27f69f42014-08-21 13:19:12 -07003015 const bool skipEntireType = (typeName == mipmap16 && !isBase);
Adam Lesinski282e1812014-01-23 18:17:42 -08003016
3017 const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
3018
3019 // Until a non-NO_ENTRY value has been written for a resource,
3020 // that resource is invalid; validResources[i] represents
3021 // the item at t->getOrderedConfigs().itemAt(i).
3022 Vector<bool> validResources;
3023 validResources.insertAt(false, 0, N);
3024
3025 // First write the typeSpec chunk, containing information about
3026 // each resource entry in this type.
3027 {
3028 const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
3029 const size_t typeSpecStart = data->getSize();
3030 ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
3031 (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
3032 if (tsHeader == NULL) {
3033 fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n");
3034 return NO_MEMORY;
3035 }
3036 memset(tsHeader, 0, sizeof(*tsHeader));
3037 tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
3038 tsHeader->header.headerSize = htods(sizeof(*tsHeader));
3039 tsHeader->header.size = htodl(typeSpecSize);
3040 tsHeader->id = ti+1;
3041 tsHeader->entryCount = htodl(N);
3042
3043 uint32_t* typeSpecFlags = (uint32_t*)
3044 (((uint8_t*)data->editData())
3045 + typeSpecStart + sizeof(ResTable_typeSpec));
3046 memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
3047
3048 for (size_t ei=0; ei<N; ei++) {
3049 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
Adam Lesinski9b624c12014-11-19 17:49:26 -08003050 if (cl == NULL) {
3051 continue;
3052 }
3053
Adam Lesinski282e1812014-01-23 18:17:42 -08003054 if (cl->getPublic()) {
3055 typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
3056 }
Adam Lesinski27f69f42014-08-21 13:19:12 -07003057
3058 if (skipEntireType) {
3059 continue;
3060 }
3061
Adam Lesinski282e1812014-01-23 18:17:42 -08003062 const size_t CN = cl->getEntries().size();
3063 for (size_t ci=0; ci<CN; ci++) {
Adam Lesinskifab50872014-04-16 14:40:42 -07003064 if (filterable && !filter->match(cl->getEntries().keyAt(ci))) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003065 continue;
3066 }
3067 for (size_t cj=ci+1; cj<CN; cj++) {
Adam Lesinskifab50872014-04-16 14:40:42 -07003068 if (filterable && !filter->match(cl->getEntries().keyAt(cj))) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003069 continue;
3070 }
3071 typeSpecFlags[ei] |= htodl(
3072 cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
3073 }
3074 }
3075 }
3076 }
3077
Adam Lesinski27f69f42014-08-21 13:19:12 -07003078 if (skipEntireType) {
3079 continue;
3080 }
3081
Adam Lesinski282e1812014-01-23 18:17:42 -08003082 // We need to write one type chunk for each configuration for
3083 // which we have entries in this type.
Adam Lesinskie97908d2014-12-05 11:06:21 -08003084 SortedVector<ConfigDescription> uniqueConfigs;
3085 if (t != NULL) {
3086 uniqueConfigs = t->getUniqueConfigs();
3087 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003088
3089 const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
3090
Adam Lesinskie97908d2014-12-05 11:06:21 -08003091 const size_t NC = uniqueConfigs.size();
Adam Lesinski282e1812014-01-23 18:17:42 -08003092 for (size_t ci=0; ci<NC; ci++) {
Adam Lesinski9b624c12014-11-19 17:49:26 -08003093 const ConfigDescription& config = uniqueConfigs[ci];
Adam Lesinski282e1812014-01-23 18:17:42 -08003094
Andreas Gampe2412f842014-09-30 20:55:57 -07003095 if (kIsDebug) {
3096 printf("Writing config %zu config: imsi:%d/%d lang:%c%c cnt:%c%c "
3097 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
3098 "sw%ddp w%ddp h%ddp layout:%d\n",
3099 ti + 1,
3100 config.mcc, config.mnc,
3101 config.language[0] ? config.language[0] : '-',
3102 config.language[1] ? config.language[1] : '-',
3103 config.country[0] ? config.country[0] : '-',
3104 config.country[1] ? config.country[1] : '-',
3105 config.orientation,
3106 config.uiMode,
3107 config.touchscreen,
3108 config.density,
3109 config.keyboard,
3110 config.inputFlags,
3111 config.navigation,
3112 config.screenWidth,
3113 config.screenHeight,
3114 config.smallestScreenWidthDp,
3115 config.screenWidthDp,
3116 config.screenHeightDp,
3117 config.screenLayout);
3118 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003119
Adam Lesinskifab50872014-04-16 14:40:42 -07003120 if (filterable && !filter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003121 continue;
3122 }
3123
3124 const size_t typeStart = data->getSize();
3125
3126 ResTable_type* tHeader = (ResTable_type*)
3127 (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
3128 if (tHeader == NULL) {
3129 fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
3130 return NO_MEMORY;
3131 }
3132
3133 memset(tHeader, 0, sizeof(*tHeader));
3134 tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
3135 tHeader->header.headerSize = htods(sizeof(*tHeader));
3136 tHeader->id = ti+1;
3137 tHeader->entryCount = htodl(N);
3138 tHeader->entriesStart = htodl(typeSize);
3139 tHeader->config = config;
Andreas Gampe2412f842014-09-30 20:55:57 -07003140 if (kIsDebug) {
3141 printf("Writing type %zu config: imsi:%d/%d lang:%c%c cnt:%c%c "
3142 "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
3143 "sw%ddp w%ddp h%ddp layout:%d\n",
3144 ti + 1,
3145 tHeader->config.mcc, tHeader->config.mnc,
3146 tHeader->config.language[0] ? tHeader->config.language[0] : '-',
3147 tHeader->config.language[1] ? tHeader->config.language[1] : '-',
3148 tHeader->config.country[0] ? tHeader->config.country[0] : '-',
3149 tHeader->config.country[1] ? tHeader->config.country[1] : '-',
3150 tHeader->config.orientation,
3151 tHeader->config.uiMode,
3152 tHeader->config.touchscreen,
3153 tHeader->config.density,
3154 tHeader->config.keyboard,
3155 tHeader->config.inputFlags,
3156 tHeader->config.navigation,
3157 tHeader->config.screenWidth,
3158 tHeader->config.screenHeight,
3159 tHeader->config.smallestScreenWidthDp,
3160 tHeader->config.screenWidthDp,
3161 tHeader->config.screenHeightDp,
3162 tHeader->config.screenLayout);
3163 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003164 tHeader->config.swapHtoD();
3165
3166 // Build the entries inside of this type.
3167 for (size_t ei=0; ei<N; ei++) {
3168 sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
Adam Lesinski9b624c12014-11-19 17:49:26 -08003169 sp<Entry> e = NULL;
3170 if (cl != NULL) {
3171 e = cl->getEntries().valueFor(config);
3172 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003173
3174 // Set the offset for this entry in its type.
3175 uint32_t* index = (uint32_t*)
3176 (((uint8_t*)data->editData())
3177 + typeStart + sizeof(ResTable_type));
3178 if (e != NULL) {
3179 index[ei] = htodl(data->getSize()-typeStart-typeSize);
3180
3181 // Create the entry.
3182 ssize_t amt = e->flatten(bundle, data, cl->getPublic());
3183 if (amt < 0) {
3184 return amt;
3185 }
3186 validResources.editItemAt(ei) = true;
3187 } else {
3188 index[ei] = htodl(ResTable_type::NO_ENTRY);
3189 }
3190 }
3191
3192 // Fill in the rest of the type information.
3193 tHeader = (ResTable_type*)
3194 (((uint8_t*)data->editData()) + typeStart);
3195 tHeader->header.size = htodl(data->getSize()-typeStart);
3196 }
3197
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003198 // If we're building splits, then each invocation of the flattening
3199 // step will have 'missing' entries. Don't warn/error for this case.
3200 if (bundle->getSplitConfigurations().isEmpty()) {
3201 bool missing_entry = false;
3202 const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
3203 "error" : "warning";
3204 for (size_t i = 0; i < N; ++i) {
3205 if (!validResources[i]) {
3206 sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
Adam Lesinski9b624c12014-11-19 17:49:26 -08003207 if (c != NULL) {
Colin Cross01f18562015-04-08 17:29:00 -07003208 fprintf(stderr, "%s: no entries written for %s/%s (0x%08zx)\n", log_prefix,
Adam Lesinski9b624c12014-11-19 17:49:26 -08003209 String8(typeName).string(), String8(c->getName()).string(),
3210 Res_MAKEID(p->getAssignedId() - 1, ti, i));
3211 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003212 missing_entry = true;
3213 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003214 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07003215 if (bundle->getErrorOnMissingConfigEntry() && missing_entry) {
3216 fprintf(stderr, "Error: Missing entries, quit!\n");
3217 return NOT_ENOUGH_DATA;
3218 }
Ying Wangcd28bd32013-11-14 17:12:10 -08003219 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003220 }
3221
3222 // Fill in the rest of the package information.
3223 header = (ResTable_package*)data->editData();
3224 header->header.size = htodl(data->getSize());
3225 header->typeStrings = htodl(typeStringsStart);
3226 header->lastPublicType = htodl(p->getTypeStrings().size());
3227 header->keyStrings = htodl(keyStringsStart);
3228 header->lastPublicKey = htodl(p->getKeyStrings().size());
3229
3230 flatPackages.add(data);
3231 }
3232
3233 // And now write out the final chunks.
3234 const size_t dataStart = dest->getSize();
3235
3236 {
3237 // blah
3238 ResTable_header header;
3239 memset(&header, 0, sizeof(header));
3240 header.header.type = htods(RES_TABLE_TYPE);
3241 header.header.headerSize = htods(sizeof(header));
3242 header.packageCount = htodl(flatPackages.size());
3243 status_t err = dest->writeData(&header, sizeof(header));
3244 if (err != NO_ERROR) {
3245 fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
3246 return err;
3247 }
3248 }
3249
3250 ssize_t strStart = dest->getSize();
Adam Lesinskifab50872014-04-16 14:40:42 -07003251 status_t err = valueStrings.writeStringBlock(dest);
Adam Lesinski282e1812014-01-23 18:17:42 -08003252 if (err != NO_ERROR) {
3253 return err;
3254 }
3255
3256 ssize_t amt = (dest->getSize()-strStart);
3257 strAmt += amt;
Andreas Gampe2412f842014-09-30 20:55:57 -07003258 if (kPrintStringMetrics) {
3259 fprintf(stderr, "**** value strings: %zd\n", SSIZE(amt));
3260 fprintf(stderr, "**** total strings: %zd\n", SSIZE(strAmt));
3261 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003262
Adam Lesinski282e1812014-01-23 18:17:42 -08003263 for (pi=0; pi<flatPackages.size(); pi++) {
3264 err = dest->writeData(flatPackages[pi]->getData(),
3265 flatPackages[pi]->getSize());
3266 if (err != NO_ERROR) {
3267 fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n");
3268 return err;
3269 }
3270 }
3271
3272 ResTable_header* header = (ResTable_header*)
3273 (((uint8_t*)dest->getData()) + dataStart);
3274 header->header.size = htodl(dest->getSize() - dataStart);
3275
Andreas Gampe2412f842014-09-30 20:55:57 -07003276 if (kPrintStringMetrics) {
3277 fprintf(stderr, "**** total resource table size: %zu / %zu%% strings\n",
3278 dest->getSize(), (size_t)(strAmt*100)/dest->getSize());
3279 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003280
3281 return NO_ERROR;
3282}
3283
Adam Lesinskide898ff2014-01-29 18:20:45 -08003284status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) {
3285 // Write out the library table if necessary
3286 if (libs.size() > 0) {
Andreas Gampe87332a72014-10-01 22:03:58 -07003287 if (kIsDebug) {
3288 fprintf(stderr, "Writing library reference table\n");
3289 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003290
3291 const size_t libStart = dest->getSize();
3292 const size_t count = libs.size();
Adam Lesinski6022deb2014-08-20 14:59:19 -07003293 ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange(
3294 libStart, sizeof(ResTable_lib_header));
Adam Lesinskide898ff2014-01-29 18:20:45 -08003295
3296 memset(libHeader, 0, sizeof(*libHeader));
3297 libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE);
3298 libHeader->header.headerSize = htods(sizeof(*libHeader));
3299 libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count));
3300 libHeader->count = htodl(count);
3301
3302 // Write the library entries
3303 for (size_t i = 0; i < count; i++) {
3304 const size_t entryStart = dest->getSize();
3305 sp<Package> libPackage = libs[i];
Andreas Gampe87332a72014-10-01 22:03:58 -07003306 if (kIsDebug) {
3307 fprintf(stderr, " Entry %s -> 0x%02x\n",
Adam Lesinskide898ff2014-01-29 18:20:45 -08003308 String8(libPackage->getName()).string(),
Andreas Gampe87332a72014-10-01 22:03:58 -07003309 (uint8_t)libPackage->getAssignedId());
3310 }
Adam Lesinskide898ff2014-01-29 18:20:45 -08003311
Adam Lesinski6022deb2014-08-20 14:59:19 -07003312 ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange(
3313 entryStart, sizeof(ResTable_lib_entry));
Adam Lesinskide898ff2014-01-29 18:20:45 -08003314 memset(entry, 0, sizeof(*entry));
3315 entry->packageId = htodl(libPackage->getAssignedId());
3316 strcpy16_htod(entry->packageName, libPackage->getName().string());
3317 }
3318 }
3319 return NO_ERROR;
3320}
3321
Adam Lesinski282e1812014-01-23 18:17:42 -08003322void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp)
3323{
3324 fprintf(fp,
3325 "<!-- This file contains <public> resource definitions for all\n"
3326 " resources that were generated from the source data. -->\n"
3327 "\n"
3328 "<resources>\n");
3329
3330 writePublicDefinitions(package, fp, true);
3331 writePublicDefinitions(package, fp, false);
3332
3333 fprintf(fp,
3334 "\n"
3335 "</resources>\n");
3336}
3337
3338void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub)
3339{
3340 bool didHeader = false;
3341
3342 sp<Package> pkg = mPackages.valueFor(package);
3343 if (pkg != NULL) {
3344 const size_t NT = pkg->getOrderedTypes().size();
3345 for (size_t i=0; i<NT; i++) {
3346 sp<Type> t = pkg->getOrderedTypes().itemAt(i);
3347 if (t == NULL) {
3348 continue;
3349 }
3350
3351 bool didType = false;
3352
3353 const size_t NC = t->getOrderedConfigs().size();
3354 for (size_t j=0; j<NC; j++) {
3355 sp<ConfigList> c = t->getOrderedConfigs().itemAt(j);
3356 if (c == NULL) {
3357 continue;
3358 }
3359
3360 if (c->getPublic() != pub) {
3361 continue;
3362 }
3363
3364 if (!didType) {
3365 fprintf(fp, "\n");
3366 didType = true;
3367 }
3368 if (!didHeader) {
3369 if (pub) {
3370 fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n");
3371 fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n");
3372 } else {
3373 fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n");
3374 fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n");
3375 }
3376 didHeader = true;
3377 }
3378 if (!pub) {
3379 const size_t NE = c->getEntries().size();
3380 for (size_t k=0; k<NE; k++) {
3381 const SourcePos& pos = c->getEntries().valueAt(k)->getPos();
3382 if (pos.file != "") {
3383 fprintf(fp," <!-- Declared at %s:%d -->\n",
3384 pos.file.string(), pos.line);
3385 }
3386 }
3387 }
3388 fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n",
3389 String8(t->getName()).string(),
3390 String8(c->getName()).string(),
3391 getResId(pkg, t, c->getEntryIndex()));
3392 }
3393 }
3394 }
3395}
3396
3397ResourceTable::Item::Item(const SourcePos& _sourcePos,
3398 bool _isId,
3399 const String16& _value,
3400 const Vector<StringPool::entry_style_span>* _style,
3401 int32_t _format)
3402 : sourcePos(_sourcePos)
3403 , isId(_isId)
3404 , value(_value)
3405 , format(_format)
3406 , bagKeyId(0)
3407 , evaluating(false)
3408{
3409 if (_style) {
3410 style = *_style;
3411 }
3412}
3413
Adam Lesinski82a2dd82014-09-17 18:34:15 -07003414ResourceTable::Entry::Entry(const Entry& entry)
3415 : RefBase()
3416 , mName(entry.mName)
3417 , mParent(entry.mParent)
3418 , mType(entry.mType)
3419 , mItem(entry.mItem)
3420 , mItemFormat(entry.mItemFormat)
3421 , mBag(entry.mBag)
3422 , mNameIndex(entry.mNameIndex)
3423 , mParentId(entry.mParentId)
3424 , mPos(entry.mPos) {}
3425
Adam Lesinski978ab9d2014-09-24 19:02:52 -07003426ResourceTable::Entry& ResourceTable::Entry::operator=(const Entry& entry) {
3427 mName = entry.mName;
3428 mParent = entry.mParent;
3429 mType = entry.mType;
3430 mItem = entry.mItem;
3431 mItemFormat = entry.mItemFormat;
3432 mBag = entry.mBag;
3433 mNameIndex = entry.mNameIndex;
3434 mParentId = entry.mParentId;
3435 mPos = entry.mPos;
3436 return *this;
3437}
3438
Adam Lesinski282e1812014-01-23 18:17:42 -08003439status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
3440{
3441 if (mType == TYPE_BAG) {
3442 return NO_ERROR;
3443 }
3444 if (mType == TYPE_UNKNOWN) {
3445 mType = TYPE_BAG;
3446 return NO_ERROR;
3447 }
3448 sourcePos.error("Resource entry %s is already defined as a single item.\n"
3449 "%s:%d: Originally defined here.\n",
3450 String8(mName).string(),
3451 mItem.sourcePos.file.string(), mItem.sourcePos.line);
3452 return UNKNOWN_ERROR;
3453}
3454
3455status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
3456 const String16& value,
3457 const Vector<StringPool::entry_style_span>* style,
3458 int32_t format,
3459 const bool overwrite)
3460{
3461 Item item(sourcePos, false, value, style);
3462
3463 if (mType == TYPE_BAG) {
Adam Lesinski43a0df02014-08-18 17:14:57 -07003464 if (mBag.size() == 0) {
3465 sourcePos.error("Resource entry %s is already defined as a bag.",
3466 String8(mName).string());
3467 } else {
3468 const Item& item(mBag.valueAt(0));
3469 sourcePos.error("Resource entry %s is already defined as a bag.\n"
3470 "%s:%d: Originally defined here.\n",
3471 String8(mName).string(),
3472 item.sourcePos.file.string(), item.sourcePos.line);
3473 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003474 return UNKNOWN_ERROR;
3475 }
3476 if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
3477 sourcePos.error("Resource entry %s is already defined.\n"
3478 "%s:%d: Originally defined here.\n",
3479 String8(mName).string(),
3480 mItem.sourcePos.file.string(), mItem.sourcePos.line);
3481 return UNKNOWN_ERROR;
3482 }
3483
3484 mType = TYPE_ITEM;
3485 mItem = item;
3486 mItemFormat = format;
3487 return NO_ERROR;
3488}
3489
3490status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
3491 const String16& key, const String16& value,
3492 const Vector<StringPool::entry_style_span>* style,
3493 bool replace, bool isId, int32_t format)
3494{
3495 status_t err = makeItABag(sourcePos);
3496 if (err != NO_ERROR) {
3497 return err;
3498 }
3499
3500 Item item(sourcePos, isId, value, style, format);
3501
3502 // XXX NOTE: there is an error if you try to have a bag with two keys,
3503 // one an attr and one an id, with the same name. Not something we
3504 // currently ever have to worry about.
3505 ssize_t origKey = mBag.indexOfKey(key);
3506 if (origKey >= 0) {
3507 if (!replace) {
3508 const Item& item(mBag.valueAt(origKey));
3509 sourcePos.error("Resource entry %s already has bag item %s.\n"
3510 "%s:%d: Originally defined here.\n",
3511 String8(mName).string(), String8(key).string(),
3512 item.sourcePos.file.string(), item.sourcePos.line);
3513 return UNKNOWN_ERROR;
3514 }
3515 //printf("Replacing %s with %s\n",
3516 // String8(mBag.valueFor(key).value).string(), String8(value).string());
3517 mBag.replaceValueFor(key, item);
3518 }
3519
3520 mBag.add(key, item);
3521 return NO_ERROR;
3522}
3523
Adam Lesinski82a2dd82014-09-17 18:34:15 -07003524status_t ResourceTable::Entry::removeFromBag(const String16& key) {
3525 if (mType != Entry::TYPE_BAG) {
3526 return NO_ERROR;
3527 }
3528
3529 if (mBag.removeItem(key) >= 0) {
3530 return NO_ERROR;
3531 }
3532 return UNKNOWN_ERROR;
3533}
3534
Adam Lesinski282e1812014-01-23 18:17:42 -08003535status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos)
3536{
3537 status_t err = makeItABag(sourcePos);
3538 if (err != NO_ERROR) {
3539 return err;
3540 }
3541
3542 mBag.clear();
3543 return NO_ERROR;
3544}
3545
3546status_t ResourceTable::Entry::generateAttributes(ResourceTable* table,
3547 const String16& package)
3548{
3549 const String16 attr16("attr");
3550 const String16 id16("id");
3551 const size_t N = mBag.size();
3552 for (size_t i=0; i<N; i++) {
3553 const String16& key = mBag.keyAt(i);
3554 const Item& it = mBag.valueAt(i);
3555 if (it.isId) {
3556 if (!table->hasBagOrEntry(key, &id16, &package)) {
3557 String16 value("false");
Andreas Gampe87332a72014-10-01 22:03:58 -07003558 if (kIsDebug) {
3559 fprintf(stderr, "Generating %s:id/%s\n",
3560 String8(package).string(),
3561 String8(key).string());
3562 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003563 status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package,
3564 id16, key, value);
3565 if (err != NO_ERROR) {
3566 return err;
3567 }
3568 }
3569 } else if (!table->hasBagOrEntry(key, &attr16, &package)) {
3570
3571#if 1
3572// fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n",
3573// String8(key).string());
3574// const Item& item(mBag.valueAt(i));
3575// fprintf(stderr, "Referenced from file %s line %d\n",
3576// item.sourcePos.file.string(), item.sourcePos.line);
3577// return UNKNOWN_ERROR;
3578#else
3579 char numberStr[16];
3580 sprintf(numberStr, "%d", ResTable_map::TYPE_ANY);
3581 status_t err = table->addBag(SourcePos("<generated>", 0), package,
3582 attr16, key, String16(""),
3583 String16("^type"),
3584 String16(numberStr), NULL, NULL);
3585 if (err != NO_ERROR) {
3586 return err;
3587 }
3588#endif
3589 }
3590 }
3591 return NO_ERROR;
3592}
3593
3594status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
Andreas Gampe2412f842014-09-30 20:55:57 -07003595 const String16& /* package */)
Adam Lesinski282e1812014-01-23 18:17:42 -08003596{
3597 bool hasErrors = false;
3598
3599 if (mType == TYPE_BAG) {
3600 const char* errorMsg;
3601 const String16 style16("style");
3602 const String16 attr16("attr");
3603 const String16 id16("id");
3604 mParentId = 0;
3605 if (mParent.size() > 0) {
3606 mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
3607 if (mParentId == 0) {
3608 mPos.error("Error retrieving parent for item: %s '%s'.\n",
3609 errorMsg, String8(mParent).string());
3610 hasErrors = true;
3611 }
3612 }
3613 const size_t N = mBag.size();
3614 for (size_t i=0; i<N; i++) {
3615 const String16& key = mBag.keyAt(i);
3616 Item& it = mBag.editValueAt(i);
3617 it.bagKeyId = table->getResId(key,
3618 it.isId ? &id16 : &attr16, NULL, &errorMsg);
3619 //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId);
3620 if (it.bagKeyId == 0) {
3621 it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
3622 String8(it.isId ? id16 : attr16).string(),
3623 String8(key).string());
3624 hasErrors = true;
3625 }
3626 }
3627 }
Andreas Gampe2412f842014-09-30 20:55:57 -07003628 return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08003629}
3630
3631status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table,
3632 const String8* configTypeName, const ConfigDescription* config)
3633{
3634 if (mType == TYPE_ITEM) {
3635 Item& it = mItem;
3636 AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
3637 if (!table->stringToValue(&it.parsedValue, strings,
3638 it.value, false, true, 0,
3639 &it.style, NULL, &ac, mItemFormat,
3640 configTypeName, config)) {
3641 return UNKNOWN_ERROR;
3642 }
3643 } else if (mType == TYPE_BAG) {
3644 const size_t N = mBag.size();
3645 for (size_t i=0; i<N; i++) {
3646 const String16& key = mBag.keyAt(i);
3647 Item& it = mBag.editValueAt(i);
3648 AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
3649 if (!table->stringToValue(&it.parsedValue, strings,
3650 it.value, false, true, it.bagKeyId,
3651 &it.style, NULL, &ac, it.format,
3652 configTypeName, config)) {
3653 return UNKNOWN_ERROR;
3654 }
3655 }
3656 } else {
3657 mPos.error("Error: entry %s is not a single item or a bag.\n",
3658 String8(mName).string());
3659 return UNKNOWN_ERROR;
3660 }
3661 return NO_ERROR;
3662}
3663
3664status_t ResourceTable::Entry::remapStringValue(StringPool* strings)
3665{
3666 if (mType == TYPE_ITEM) {
3667 Item& it = mItem;
3668 if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
3669 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
3670 }
3671 } else if (mType == TYPE_BAG) {
3672 const size_t N = mBag.size();
3673 for (size_t i=0; i<N; i++) {
3674 Item& it = mBag.editValueAt(i);
3675 if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
3676 it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
3677 }
3678 }
3679 } else {
3680 mPos.error("Error: entry %s is not a single item or a bag.\n",
3681 String8(mName).string());
3682 return UNKNOWN_ERROR;
3683 }
3684 return NO_ERROR;
3685}
3686
Andreas Gampe2412f842014-09-30 20:55:57 -07003687ssize_t ResourceTable::Entry::flatten(Bundle* /* bundle */, const sp<AaptFile>& data, bool isPublic)
Adam Lesinski282e1812014-01-23 18:17:42 -08003688{
3689 size_t amt = 0;
3690 ResTable_entry header;
3691 memset(&header, 0, sizeof(header));
3692 header.size = htods(sizeof(header));
Andreas Gampe2412f842014-09-30 20:55:57 -07003693 const type ty = mType;
3694 if (ty == TYPE_BAG) {
3695 header.flags |= htods(header.FLAG_COMPLEX);
Adam Lesinski282e1812014-01-23 18:17:42 -08003696 }
Andreas Gampe2412f842014-09-30 20:55:57 -07003697 if (isPublic) {
3698 header.flags |= htods(header.FLAG_PUBLIC);
3699 }
3700 header.key.index = htodl(mNameIndex);
Adam Lesinski282e1812014-01-23 18:17:42 -08003701 if (ty != TYPE_BAG) {
3702 status_t err = data->writeData(&header, sizeof(header));
3703 if (err != NO_ERROR) {
3704 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
3705 return err;
3706 }
3707
3708 const Item& it = mItem;
3709 Res_value par;
3710 memset(&par, 0, sizeof(par));
3711 par.size = htods(it.parsedValue.size);
3712 par.dataType = it.parsedValue.dataType;
3713 par.res0 = it.parsedValue.res0;
3714 par.data = htodl(it.parsedValue.data);
3715 #if 0
3716 printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n",
3717 String8(mName).string(), it.parsedValue.dataType,
3718 it.parsedValue.data, par.res0);
3719 #endif
3720 err = data->writeData(&par, it.parsedValue.size);
3721 if (err != NO_ERROR) {
3722 fprintf(stderr, "ERROR: out of memory creating Res_value\n");
3723 return err;
3724 }
3725 amt += it.parsedValue.size;
3726 } else {
3727 size_t N = mBag.size();
3728 size_t i;
3729 // Create correct ordering of items.
3730 KeyedVector<uint32_t, const Item*> items;
3731 for (i=0; i<N; i++) {
3732 const Item& it = mBag.valueAt(i);
3733 items.add(it.bagKeyId, &it);
3734 }
3735 N = items.size();
3736
3737 ResTable_map_entry mapHeader;
3738 memcpy(&mapHeader, &header, sizeof(header));
3739 mapHeader.size = htods(sizeof(mapHeader));
3740 mapHeader.parent.ident = htodl(mParentId);
3741 mapHeader.count = htodl(N);
3742 status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
3743 if (err != NO_ERROR) {
3744 fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
3745 return err;
3746 }
3747
3748 for (i=0; i<N; i++) {
3749 const Item& it = *items.valueAt(i);
3750 ResTable_map map;
3751 map.name.ident = htodl(it.bagKeyId);
3752 map.value.size = htods(it.parsedValue.size);
3753 map.value.dataType = it.parsedValue.dataType;
3754 map.value.res0 = it.parsedValue.res0;
3755 map.value.data = htodl(it.parsedValue.data);
3756 err = data->writeData(&map, sizeof(map));
3757 if (err != NO_ERROR) {
3758 fprintf(stderr, "ERROR: out of memory creating Res_value\n");
3759 return err;
3760 }
3761 amt += sizeof(map);
3762 }
3763 }
3764 return amt;
3765}
3766
3767void ResourceTable::ConfigList::appendComment(const String16& comment,
3768 bool onlyIfEmpty)
3769{
3770 if (comment.size() <= 0) {
3771 return;
3772 }
3773 if (onlyIfEmpty && mComment.size() > 0) {
3774 return;
3775 }
3776 if (mComment.size() > 0) {
3777 mComment.append(String16("\n"));
3778 }
3779 mComment.append(comment);
3780}
3781
3782void ResourceTable::ConfigList::appendTypeComment(const String16& comment)
3783{
3784 if (comment.size() <= 0) {
3785 return;
3786 }
3787 if (mTypeComment.size() > 0) {
3788 mTypeComment.append(String16("\n"));
3789 }
3790 mTypeComment.append(comment);
3791}
3792
3793status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos,
3794 const String16& name,
3795 const uint32_t ident)
3796{
3797 #if 0
3798 int32_t entryIdx = Res_GETENTRY(ident);
3799 if (entryIdx < 0) {
3800 sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n",
3801 String8(mName).string(), String8(name).string(), ident);
3802 return UNKNOWN_ERROR;
3803 }
3804 #endif
3805
3806 int32_t typeIdx = Res_GETTYPE(ident);
3807 if (typeIdx >= 0) {
3808 typeIdx++;
3809 if (mPublicIndex > 0 && mPublicIndex != typeIdx) {
3810 sourcePos.error("Public resource %s/%s has conflicting type codes for its"
3811 " public identifiers (0x%x vs 0x%x).\n",
3812 String8(mName).string(), String8(name).string(),
3813 mPublicIndex, typeIdx);
3814 return UNKNOWN_ERROR;
3815 }
3816 mPublicIndex = typeIdx;
3817 }
3818
3819 if (mFirstPublicSourcePos == NULL) {
3820 mFirstPublicSourcePos = new SourcePos(sourcePos);
3821 }
3822
3823 if (mPublic.indexOfKey(name) < 0) {
3824 mPublic.add(name, Public(sourcePos, String16(), ident));
3825 } else {
3826 Public& p = mPublic.editValueFor(name);
3827 if (p.ident != ident) {
3828 sourcePos.error("Public resource %s/%s has conflicting public identifiers"
3829 " (0x%08x vs 0x%08x).\n"
3830 "%s:%d: Originally defined here.\n",
3831 String8(mName).string(), String8(name).string(), p.ident, ident,
3832 p.sourcePos.file.string(), p.sourcePos.line);
3833 return UNKNOWN_ERROR;
3834 }
3835 }
3836
3837 return NO_ERROR;
3838}
3839
3840void ResourceTable::Type::canAddEntry(const String16& name)
3841{
3842 mCanAddEntries.add(name);
3843}
3844
3845sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
3846 const SourcePos& sourcePos,
3847 const ResTable_config* config,
3848 bool doSetIndex,
3849 bool overlay,
3850 bool autoAddOverlay)
3851{
3852 int pos = -1;
3853 sp<ConfigList> c = mConfigs.valueFor(entry);
3854 if (c == NULL) {
3855 if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) {
3856 sourcePos.error("Resource at %s appears in overlay but not"
3857 " in the base package; use <add-resource> to add.\n",
3858 String8(entry).string());
3859 return NULL;
3860 }
3861 c = new ConfigList(entry, sourcePos);
3862 mConfigs.add(entry, c);
3863 pos = (int)mOrderedConfigs.size();
3864 mOrderedConfigs.add(c);
3865 if (doSetIndex) {
3866 c->setEntryIndex(pos);
3867 }
3868 }
3869
3870 ConfigDescription cdesc;
3871 if (config) cdesc = *config;
3872
3873 sp<Entry> e = c->getEntries().valueFor(cdesc);
3874 if (e == NULL) {
Andreas Gampe2412f842014-09-30 20:55:57 -07003875 if (kIsDebug) {
3876 if (config != NULL) {
3877 printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c "
Adam Lesinski282e1812014-01-23 18:17:42 -08003878 "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
Andreas Gampe2412f842014-09-30 20:55:57 -07003879 "sw%ddp w%ddp h%ddp layout:%d\n",
Adam Lesinski282e1812014-01-23 18:17:42 -08003880 sourcePos.file.string(), sourcePos.line,
3881 config->mcc, config->mnc,
3882 config->language[0] ? config->language[0] : '-',
3883 config->language[1] ? config->language[1] : '-',
3884 config->country[0] ? config->country[0] : '-',
3885 config->country[1] ? config->country[1] : '-',
3886 config->orientation,
3887 config->touchscreen,
3888 config->density,
3889 config->keyboard,
3890 config->inputFlags,
3891 config->navigation,
3892 config->screenWidth,
3893 config->screenHeight,
3894 config->smallestScreenWidthDp,
3895 config->screenWidthDp,
3896 config->screenHeightDp,
Andreas Gampe2412f842014-09-30 20:55:57 -07003897 config->screenLayout);
3898 } else {
3899 printf("New entry at %s:%d: NULL config\n",
3900 sourcePos.file.string(), sourcePos.line);
3901 }
Adam Lesinski282e1812014-01-23 18:17:42 -08003902 }
3903 e = new Entry(entry, sourcePos);
3904 c->addEntry(cdesc, e);
3905 /*
3906 if (doSetIndex) {
3907 if (pos < 0) {
3908 for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) {
3909 if (mOrderedConfigs[pos] == c) {
3910 break;
3911 }
3912 }
3913 if (pos >= (int)mOrderedConfigs.size()) {
3914 sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry");
3915 return NULL;
3916 }
3917 }
3918 e->setEntryIndex(pos);
3919 }
3920 */
3921 }
3922
Adam Lesinski282e1812014-01-23 18:17:42 -08003923 return e;
3924}
3925
Adam Lesinski9b624c12014-11-19 17:49:26 -08003926sp<ResourceTable::ConfigList> ResourceTable::Type::removeEntry(const String16& entry) {
3927 ssize_t idx = mConfigs.indexOfKey(entry);
3928 if (idx < 0) {
3929 return NULL;
3930 }
3931
3932 sp<ConfigList> removed = mConfigs.valueAt(idx);
3933 mConfigs.removeItemsAt(idx);
3934
3935 Vector<sp<ConfigList> >::iterator iter = std::find(
3936 mOrderedConfigs.begin(), mOrderedConfigs.end(), removed);
3937 if (iter != mOrderedConfigs.end()) {
3938 mOrderedConfigs.erase(iter);
3939 }
3940
3941 mPublic.removeItem(entry);
3942 return removed;
3943}
3944
3945SortedVector<ConfigDescription> ResourceTable::Type::getUniqueConfigs() const {
3946 SortedVector<ConfigDescription> unique;
3947 const size_t entryCount = mOrderedConfigs.size();
3948 for (size_t i = 0; i < entryCount; i++) {
3949 if (mOrderedConfigs[i] == NULL) {
3950 continue;
3951 }
3952 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configs =
3953 mOrderedConfigs[i]->getEntries();
3954 const size_t configCount = configs.size();
3955 for (size_t j = 0; j < configCount; j++) {
3956 unique.add(configs.keyAt(j));
3957 }
3958 }
3959 return unique;
3960}
3961
Adam Lesinski282e1812014-01-23 18:17:42 -08003962status_t ResourceTable::Type::applyPublicEntryOrder()
3963{
3964 size_t N = mOrderedConfigs.size();
3965 Vector<sp<ConfigList> > origOrder(mOrderedConfigs);
3966 bool hasError = false;
3967
3968 size_t i;
3969 for (i=0; i<N; i++) {
3970 mOrderedConfigs.replaceAt(NULL, i);
3971 }
3972
3973 const size_t NP = mPublic.size();
3974 //printf("Ordering %d configs from %d public defs\n", N, NP);
3975 size_t j;
3976 for (j=0; j<NP; j++) {
3977 const String16& name = mPublic.keyAt(j);
3978 const Public& p = mPublic.valueAt(j);
3979 int32_t idx = Res_GETENTRY(p.ident);
3980 //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n",
3981 // String8(mName).string(), String8(name).string(), p.ident, N);
3982 bool found = false;
3983 for (i=0; i<N; i++) {
3984 sp<ConfigList> e = origOrder.itemAt(i);
3985 //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
3986 if (e->getName() == name) {
3987 if (idx >= (int32_t)mOrderedConfigs.size()) {
Adam Lesinski9b624c12014-11-19 17:49:26 -08003988 mOrderedConfigs.resize(idx + 1);
3989 }
3990
3991 if (mOrderedConfigs.itemAt(idx) == NULL) {
Adam Lesinski282e1812014-01-23 18:17:42 -08003992 e->setPublic(true);
3993 e->setPublicSourcePos(p.sourcePos);
3994 mOrderedConfigs.replaceAt(e, idx);
3995 origOrder.removeAt(i);
3996 N--;
3997 found = true;
3998 break;
3999 } else {
4000 sp<ConfigList> oe = mOrderedConfigs.itemAt(idx);
4001
4002 p.sourcePos.error("Multiple entry names declared for public entry"
4003 " identifier 0x%x in type %s (%s vs %s).\n"
4004 "%s:%d: Originally defined here.",
4005 idx+1, String8(mName).string(),
4006 String8(oe->getName()).string(),
4007 String8(name).string(),
4008 oe->getPublicSourcePos().file.string(),
4009 oe->getPublicSourcePos().line);
4010 hasError = true;
4011 }
4012 }
4013 }
4014
4015 if (!found) {
4016 p.sourcePos.error("Public symbol %s/%s declared here is not defined.",
4017 String8(mName).string(), String8(name).string());
4018 hasError = true;
4019 }
4020 }
4021
4022 //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size());
4023
4024 if (N != origOrder.size()) {
4025 printf("Internal error: remaining private symbol count mismatch\n");
4026 N = origOrder.size();
4027 }
4028
4029 j = 0;
4030 for (i=0; i<N; i++) {
4031 sp<ConfigList> e = origOrder.itemAt(i);
4032 // There will always be enough room for the remaining entries.
4033 while (mOrderedConfigs.itemAt(j) != NULL) {
4034 j++;
4035 }
4036 mOrderedConfigs.replaceAt(e, j);
4037 j++;
4038 }
4039
Andreas Gampe2412f842014-09-30 20:55:57 -07004040 return hasError ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08004041}
4042
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004043ResourceTable::Package::Package(const String16& name, size_t packageId)
4044 : mName(name), mPackageId(packageId),
Adam Lesinski282e1812014-01-23 18:17:42 -08004045 mTypeStringsMapping(0xffffffff),
4046 mKeyStringsMapping(0xffffffff)
4047{
4048}
4049
4050sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
4051 const SourcePos& sourcePos,
4052 bool doSetIndex)
4053{
4054 sp<Type> t = mTypes.valueFor(type);
4055 if (t == NULL) {
4056 t = new Type(type, sourcePos);
4057 mTypes.add(type, t);
4058 mOrderedTypes.add(t);
4059 if (doSetIndex) {
4060 // For some reason the type's index is set to one plus the index
4061 // in the mOrderedTypes list, rather than just the index.
4062 t->setIndex(mOrderedTypes.size());
4063 }
4064 }
4065 return t;
4066}
4067
4068status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data)
4069{
Adam Lesinski282e1812014-01-23 18:17:42 -08004070 status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping);
4071 if (err != NO_ERROR) {
4072 fprintf(stderr, "ERROR: Type string data is corrupt!\n");
Adam Lesinski57079512014-07-29 11:51:35 -07004073 return err;
Adam Lesinski282e1812014-01-23 18:17:42 -08004074 }
Adam Lesinski57079512014-07-29 11:51:35 -07004075
4076 // Retain a reference to the new data after we've successfully replaced
4077 // all uses of the old reference (in setStrings() ).
4078 mTypeStringsData = data;
4079 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08004080}
4081
4082status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data)
4083{
Adam Lesinski282e1812014-01-23 18:17:42 -08004084 status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping);
4085 if (err != NO_ERROR) {
4086 fprintf(stderr, "ERROR: Key string data is corrupt!\n");
Adam Lesinski57079512014-07-29 11:51:35 -07004087 return err;
Adam Lesinski282e1812014-01-23 18:17:42 -08004088 }
Adam Lesinski57079512014-07-29 11:51:35 -07004089
4090 // Retain a reference to the new data after we've successfully replaced
4091 // all uses of the old reference (in setStrings() ).
4092 mKeyStringsData = data;
4093 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08004094}
4095
4096status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data,
4097 ResStringPool* strings,
4098 DefaultKeyedVector<String16, uint32_t>* mappings)
4099{
4100 if (data->getData() == NULL) {
4101 return UNKNOWN_ERROR;
4102 }
4103
Adam Lesinski282e1812014-01-23 18:17:42 -08004104 status_t err = strings->setTo(data->getData(), data->getSize());
4105 if (err == NO_ERROR) {
4106 const size_t N = strings->size();
4107 for (size_t i=0; i<N; i++) {
4108 size_t len;
4109 mappings->add(String16(strings->stringAt(i, &len)), i);
4110 }
4111 }
4112 return err;
4113}
4114
4115status_t ResourceTable::Package::applyPublicTypeOrder()
4116{
4117 size_t N = mOrderedTypes.size();
4118 Vector<sp<Type> > origOrder(mOrderedTypes);
4119
4120 size_t i;
4121 for (i=0; i<N; i++) {
4122 mOrderedTypes.replaceAt(NULL, i);
4123 }
4124
4125 for (i=0; i<N; i++) {
4126 sp<Type> t = origOrder.itemAt(i);
4127 int32_t idx = t->getPublicIndex();
4128 if (idx > 0) {
4129 idx--;
4130 while (idx >= (int32_t)mOrderedTypes.size()) {
4131 mOrderedTypes.add();
4132 }
4133 if (mOrderedTypes.itemAt(idx) != NULL) {
4134 sp<Type> ot = mOrderedTypes.itemAt(idx);
4135 t->getFirstPublicSourcePos().error("Multiple type names declared for public type"
4136 " identifier 0x%x (%s vs %s).\n"
4137 "%s:%d: Originally defined here.",
4138 idx, String8(ot->getName()).string(),
4139 String8(t->getName()).string(),
4140 ot->getFirstPublicSourcePos().file.string(),
4141 ot->getFirstPublicSourcePos().line);
4142 return UNKNOWN_ERROR;
4143 }
4144 mOrderedTypes.replaceAt(t, idx);
4145 origOrder.removeAt(i);
4146 i--;
4147 N--;
4148 }
4149 }
4150
4151 size_t j=0;
4152 for (i=0; i<N; i++) {
4153 sp<Type> t = origOrder.itemAt(i);
4154 // There will always be enough room for the remaining types.
4155 while (mOrderedTypes.itemAt(j) != NULL) {
4156 j++;
4157 }
4158 mOrderedTypes.replaceAt(t, j);
4159 }
4160
4161 return NO_ERROR;
4162}
4163
Adam Lesinski9b624c12014-11-19 17:49:26 -08004164void ResourceTable::Package::movePrivateAttrs() {
4165 sp<Type> attr = mTypes.valueFor(String16("attr"));
4166 if (attr == NULL) {
4167 // Nothing to do.
4168 return;
4169 }
4170
4171 Vector<sp<ConfigList> > privateAttrs;
4172
4173 bool hasPublic = false;
4174 const Vector<sp<ConfigList> >& configs = attr->getOrderedConfigs();
4175 const size_t configCount = configs.size();
4176 for (size_t i = 0; i < configCount; i++) {
4177 if (configs[i] == NULL) {
4178 continue;
4179 }
4180
4181 if (attr->isPublic(configs[i]->getName())) {
4182 hasPublic = true;
4183 } else {
4184 privateAttrs.add(configs[i]);
4185 }
4186 }
4187
4188 // Only if we have public attributes do we create a separate type for
4189 // private attributes.
4190 if (!hasPublic) {
4191 return;
4192 }
4193
4194 // Create a new type for private attributes.
4195 sp<Type> privateAttrType = getType(String16(kAttrPrivateType), SourcePos());
4196
4197 const size_t privateAttrCount = privateAttrs.size();
4198 for (size_t i = 0; i < privateAttrCount; i++) {
4199 const sp<ConfigList>& cl = privateAttrs[i];
4200
4201 // Remove the private attributes from their current type.
4202 attr->removeEntry(cl->getName());
4203
4204 // Add it to the new type.
4205 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = cl->getEntries();
4206 const size_t entryCount = entries.size();
4207 for (size_t j = 0; j < entryCount; j++) {
4208 const sp<Entry>& oldEntry = entries[j];
4209 sp<Entry> entry = privateAttrType->getEntry(
4210 cl->getName(), oldEntry->getPos(), &entries.keyAt(j));
4211 *entry = *oldEntry;
4212 }
4213
4214 // Move the symbols to the new type.
4215
4216 }
4217}
4218
Adam Lesinski282e1812014-01-23 18:17:42 -08004219sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
4220{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004221 if (package != mAssetsPackage) {
4222 return NULL;
Adam Lesinski282e1812014-01-23 18:17:42 -08004223 }
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004224 return mPackages.valueFor(package);
Adam Lesinski282e1812014-01-23 18:17:42 -08004225}
4226
4227sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
4228 const String16& type,
4229 const SourcePos& sourcePos,
4230 bool doSetIndex)
4231{
4232 sp<Package> p = getPackage(package);
4233 if (p == NULL) {
4234 return NULL;
4235 }
4236 return p->getType(type, sourcePos, doSetIndex);
4237}
4238
4239sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
4240 const String16& type,
4241 const String16& name,
4242 const SourcePos& sourcePos,
4243 bool overlay,
4244 const ResTable_config* config,
4245 bool doSetIndex)
4246{
4247 sp<Type> t = getType(package, type, sourcePos, doSetIndex);
4248 if (t == NULL) {
4249 return NULL;
4250 }
4251 return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
4252}
4253
Adam Lesinskie572c012014-09-19 15:10:04 -07004254sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package,
4255 const String16& type, const String16& name) const
4256{
4257 const size_t packageCount = mOrderedPackages.size();
4258 for (size_t pi = 0; pi < packageCount; pi++) {
4259 const sp<Package>& p = mOrderedPackages[pi];
4260 if (p == NULL || p->getName() != package) {
4261 continue;
4262 }
4263
4264 const Vector<sp<Type> >& types = p->getOrderedTypes();
4265 const size_t typeCount = types.size();
4266 for (size_t ti = 0; ti < typeCount; ti++) {
4267 const sp<Type>& t = types[ti];
4268 if (t == NULL || t->getName() != type) {
4269 continue;
4270 }
4271
4272 const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs();
4273 const size_t configCount = configs.size();
4274 for (size_t ci = 0; ci < configCount; ci++) {
4275 const sp<ConfigList>& cl = configs[ci];
4276 if (cl == NULL || cl->getName() != name) {
4277 continue;
4278 }
4279
4280 return cl;
4281 }
4282 }
4283 }
4284 return NULL;
4285}
4286
Adam Lesinski282e1812014-01-23 18:17:42 -08004287sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
4288 const ResTable_config* config) const
4289{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004290 size_t pid = Res_GETPACKAGE(resID)+1;
Adam Lesinski282e1812014-01-23 18:17:42 -08004291 const size_t N = mOrderedPackages.size();
Adam Lesinski282e1812014-01-23 18:17:42 -08004292 sp<Package> p;
Adam Lesinski833f3cc2014-06-18 15:06:01 -07004293 for (size_t i = 0; i < N; i++) {
Adam Lesinski282e1812014-01-23 18:17:42 -08004294 sp<Package> check = mOrderedPackages[i];
4295 if (check->getAssignedId() == pid) {
4296 p = check;
4297 break;
4298 }
4299
4300 }
4301 if (p == NULL) {
4302 fprintf(stderr, "warning: Package not found for resource #%08x\n", resID);
4303 return NULL;
4304 }
4305
4306 int tid = Res_GETTYPE(resID);
4307 if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) {
4308 fprintf(stderr, "warning: Type not found for resource #%08x\n", resID);
4309 return NULL;
4310 }
4311 sp<Type> t = p->getOrderedTypes()[tid];
4312
4313 int eid = Res_GETENTRY(resID);
4314 if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) {
4315 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
4316 return NULL;
4317 }
4318
4319 sp<ConfigList> c = t->getOrderedConfigs()[eid];
4320 if (c == NULL) {
4321 fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
4322 return NULL;
4323 }
4324
4325 ConfigDescription cdesc;
4326 if (config) cdesc = *config;
4327 sp<Entry> e = c->getEntries().valueFor(cdesc);
4328 if (c == NULL) {
4329 fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID);
4330 return NULL;
4331 }
4332
4333 return e;
4334}
4335
4336const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const
4337{
4338 sp<const Entry> e = getEntry(resID);
4339 if (e == NULL) {
4340 return NULL;
4341 }
4342
4343 const size_t N = e->getBag().size();
4344 for (size_t i=0; i<N; i++) {
4345 const Item& it = e->getBag().valueAt(i);
4346 if (it.bagKeyId == 0) {
4347 fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n",
4348 String8(e->getName()).string(),
4349 String8(e->getBag().keyAt(i)).string());
4350 }
4351 if (it.bagKeyId == attrID) {
4352 return &it;
4353 }
4354 }
4355
4356 return NULL;
4357}
4358
4359bool ResourceTable::getItemValue(
4360 uint32_t resID, uint32_t attrID, Res_value* outValue)
4361{
4362 const Item* item = getItem(resID, attrID);
4363
4364 bool res = false;
4365 if (item != NULL) {
4366 if (item->evaluating) {
4367 sp<const Entry> e = getEntry(resID);
4368 const size_t N = e->getBag().size();
4369 size_t i;
4370 for (i=0; i<N; i++) {
4371 if (&e->getBag().valueAt(i) == item) {
4372 break;
4373 }
4374 }
4375 fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n",
4376 String8(e->getName()).string(),
4377 String8(e->getBag().keyAt(i)).string());
4378 return false;
4379 }
4380 item->evaluating = true;
4381 res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId);
Andreas Gampe2412f842014-09-30 20:55:57 -07004382 if (kIsDebug) {
Adam Lesinski282e1812014-01-23 18:17:42 -08004383 if (res) {
4384 printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n",
4385 resID, attrID, String8(getEntry(resID)->getName()).string(),
4386 outValue->dataType, outValue->data);
4387 } else {
4388 printf("getItemValue of #%08x[#%08x]: failed\n",
4389 resID, attrID);
4390 }
Andreas Gampe2412f842014-09-30 20:55:57 -07004391 }
Adam Lesinski282e1812014-01-23 18:17:42 -08004392 item->evaluating = false;
4393 }
4394 return res;
4395}
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004396
4397/**
Adam Lesinski28994d82015-01-13 13:42:41 -08004398 * Returns the SDK version at which the attribute was
4399 * made public, or -1 if the resource ID is not an attribute
4400 * or is not public.
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004401 */
Adam Lesinski28994d82015-01-13 13:42:41 -08004402int ResourceTable::getPublicAttributeSdkLevel(uint32_t attrId) const {
4403 if (Res_GETPACKAGE(attrId) + 1 != 0x01 || Res_GETTYPE(attrId) + 1 != 0x01) {
4404 return -1;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004405 }
4406
4407 uint32_t specFlags;
4408 if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) {
Adam Lesinski28994d82015-01-13 13:42:41 -08004409 return -1;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004410 }
4411
Adam Lesinski28994d82015-01-13 13:42:41 -08004412 if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
4413 return -1;
4414 }
4415
4416 const size_t entryId = Res_GETENTRY(attrId);
4417 if (entryId <= 0x021c) {
4418 return 1;
4419 } else if (entryId <= 0x021d) {
4420 return 2;
4421 } else if (entryId <= 0x0269) {
4422 return SDK_CUPCAKE;
4423 } else if (entryId <= 0x028d) {
4424 return SDK_DONUT;
4425 } else if (entryId <= 0x02ad) {
4426 return SDK_ECLAIR;
4427 } else if (entryId <= 0x02b3) {
4428 return SDK_ECLAIR_0_1;
4429 } else if (entryId <= 0x02b5) {
4430 return SDK_ECLAIR_MR1;
4431 } else if (entryId <= 0x02bd) {
4432 return SDK_FROYO;
4433 } else if (entryId <= 0x02cb) {
4434 return SDK_GINGERBREAD;
4435 } else if (entryId <= 0x0361) {
4436 return SDK_HONEYCOMB;
4437 } else if (entryId <= 0x0366) {
4438 return SDK_HONEYCOMB_MR1;
4439 } else if (entryId <= 0x03a6) {
4440 return SDK_HONEYCOMB_MR2;
4441 } else if (entryId <= 0x03ae) {
4442 return SDK_JELLY_BEAN;
4443 } else if (entryId <= 0x03cc) {
4444 return SDK_JELLY_BEAN_MR1;
4445 } else if (entryId <= 0x03da) {
4446 return SDK_JELLY_BEAN_MR2;
4447 } else if (entryId <= 0x03f1) {
4448 return SDK_KITKAT;
4449 } else if (entryId <= 0x03f6) {
4450 return SDK_KITKAT_WATCH;
4451 } else if (entryId <= 0x04ce) {
4452 return SDK_LOLLIPOP;
4453 } else {
4454 // Anything else is marked as defined in
4455 // SDK_LOLLIPOP_MR1 since after this
4456 // version no attribute compat work
4457 // needs to be done.
4458 return SDK_LOLLIPOP_MR1;
4459 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004460}
4461
Adam Lesinski28994d82015-01-13 13:42:41 -08004462/**
4463 * First check the Manifest, then check the command line flag.
4464 */
4465static int getMinSdkVersion(const Bundle* bundle) {
4466 if (bundle->getManifestMinSdkVersion() != NULL && strlen(bundle->getManifestMinSdkVersion()) > 0) {
4467 return atoi(bundle->getManifestMinSdkVersion());
4468 } else if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
4469 return atoi(bundle->getMinSdkVersion());
Adam Lesinskie572c012014-09-19 15:10:04 -07004470 }
Adam Lesinski28994d82015-01-13 13:42:41 -08004471 return 0;
Adam Lesinskie572c012014-09-19 15:10:04 -07004472}
4473
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004474bool ResourceTable::shouldGenerateVersionedResource(
4475 const sp<ResourceTable::ConfigList>& configList,
4476 const ConfigDescription& sourceConfig,
4477 const int sdkVersionToGenerate) {
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004478 assert(sdkVersionToGenerate > sourceConfig.sdkVersion);
4479 const DefaultKeyedVector<ConfigDescription, sp<ResourceTable::Entry>>& entries
4480 = configList->getEntries();
4481 ssize_t idx = entries.indexOfKey(sourceConfig);
4482
4483 // The source config came from this list, so it should be here.
4484 assert(idx >= 0);
4485
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004486 // The next configuration either only varies in sdkVersion, or it is completely different
4487 // and therefore incompatible. If it is incompatible, we must generate the versioned resource.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004488
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004489 // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other
4490 // qualifiers, so we need to iterate through the entire list to be sure there
4491 // are no higher sdk level versions of this resource.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004492 ConfigDescription tempConfig(sourceConfig);
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004493 for (size_t i = static_cast<size_t>(idx) + 1; i < entries.size(); i++) {
4494 const ConfigDescription& nextConfig = entries.keyAt(i);
4495 tempConfig.sdkVersion = nextConfig.sdkVersion;
4496 if (tempConfig == nextConfig) {
4497 // The two configs are the same, check the sdk version.
4498 return sdkVersionToGenerate < nextConfig.sdkVersion;
4499 }
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004500 }
Adam Lesinskibeb9e332015-08-14 13:16:18 -07004501
4502 // No match was found, so we should generate the versioned resource.
4503 return true;
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004504}
4505
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004506/**
4507 * Modifies the entries in the resource table to account for compatibility
4508 * issues with older versions of Android.
4509 *
4510 * This primarily handles the issue of private/public attribute clashes
4511 * in framework resources.
4512 *
4513 * AAPT has traditionally assigned resource IDs to public attributes,
4514 * and then followed those public definitions with private attributes.
4515 *
4516 * --- PUBLIC ---
4517 * | 0x01010234 | attr/color
4518 * | 0x01010235 | attr/background
4519 *
4520 * --- PRIVATE ---
4521 * | 0x01010236 | attr/secret
4522 * | 0x01010237 | attr/shhh
4523 *
4524 * Each release, when attributes are added, they take the place of the private
4525 * attributes and the private attributes are shifted down again.
4526 *
4527 * --- PUBLIC ---
4528 * | 0x01010234 | attr/color
4529 * | 0x01010235 | attr/background
4530 * | 0x01010236 | attr/shinyNewAttr
4531 * | 0x01010237 | attr/highlyValuedFeature
4532 *
4533 * --- PRIVATE ---
4534 * | 0x01010238 | attr/secret
4535 * | 0x01010239 | attr/shhh
4536 *
4537 * Platform code may look for private attributes set in a theme. If an app
4538 * compiled against a newer version of the platform uses a new public
4539 * attribute that happens to have the same ID as the private attribute
4540 * the older platform is expecting, then the behavior is undefined.
4541 *
4542 * We get around this by detecting any newly defined attributes (in L),
4543 * copy the resource into a -v21 qualified resource, and delete the
4544 * attribute from the original resource. This ensures that older platforms
4545 * don't see the new attribute, but when running on L+ platforms, the
4546 * attribute will be respected.
4547 */
4548status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
Adam Lesinski28994d82015-01-13 13:42:41 -08004549 const int minSdk = getMinSdkVersion(bundle);
4550 if (minSdk >= SDK_LOLLIPOP_MR1) {
4551 // Lollipop MR1 and up handles public attributes differently, no
4552 // need to do any compat modifications.
Adam Lesinskie572c012014-09-19 15:10:04 -07004553 return NO_ERROR;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004554 }
4555
4556 const String16 attr16("attr");
4557
4558 const size_t packageCount = mOrderedPackages.size();
4559 for (size_t pi = 0; pi < packageCount; pi++) {
4560 sp<Package> p = mOrderedPackages.itemAt(pi);
4561 if (p == NULL || p->getTypes().size() == 0) {
4562 // Empty, skip!
4563 continue;
4564 }
4565
4566 const size_t typeCount = p->getOrderedTypes().size();
4567 for (size_t ti = 0; ti < typeCount; ti++) {
4568 sp<Type> t = p->getOrderedTypes().itemAt(ti);
4569 if (t == NULL) {
4570 continue;
4571 }
4572
4573 const size_t configCount = t->getOrderedConfigs().size();
4574 for (size_t ci = 0; ci < configCount; ci++) {
4575 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
4576 if (c == NULL) {
4577 continue;
4578 }
4579
4580 Vector<key_value_pair_t<ConfigDescription, sp<Entry> > > entriesToAdd;
4581 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries =
4582 c->getEntries();
4583 const size_t entryCount = entries.size();
4584 for (size_t ei = 0; ei < entryCount; ei++) {
4585 sp<Entry> e = entries.valueAt(ei);
4586 if (e == NULL || e->getType() != Entry::TYPE_BAG) {
4587 continue;
4588 }
4589
4590 const ConfigDescription& config = entries.keyAt(ei);
Adam Lesinski28994d82015-01-13 13:42:41 -08004591 if (config.sdkVersion >= SDK_LOLLIPOP_MR1) {
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004592 continue;
4593 }
4594
Adam Lesinski28994d82015-01-13 13:42:41 -08004595 KeyedVector<int, Vector<String16> > attributesToRemove;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004596 const KeyedVector<String16, Item>& bag = e->getBag();
4597 const size_t bagCount = bag.size();
4598 for (size_t bi = 0; bi < bagCount; bi++) {
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004599 const uint32_t attrId = getResId(bag.keyAt(bi), &attr16);
Adam Lesinski28994d82015-01-13 13:42:41 -08004600 const int sdkLevel = getPublicAttributeSdkLevel(attrId);
4601 if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
4602 AaptUtil::appendValue(attributesToRemove, sdkLevel, bag.keyAt(bi));
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004603 }
4604 }
4605
4606 if (attributesToRemove.isEmpty()) {
4607 continue;
4608 }
4609
Adam Lesinski28994d82015-01-13 13:42:41 -08004610 const size_t sdkCount = attributesToRemove.size();
4611 for (size_t i = 0; i < sdkCount; i++) {
4612 const int sdkLevel = attributesToRemove.keyAt(i);
4613
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004614 if (!shouldGenerateVersionedResource(c, config, sdkLevel)) {
4615 // There is a style that will override this generated one.
4616 continue;
4617 }
4618
Adam Lesinski28994d82015-01-13 13:42:41 -08004619 // Duplicate the entry under the same configuration
4620 // but with sdkVersion == sdkLevel.
4621 ConfigDescription newConfig(config);
4622 newConfig.sdkVersion = sdkLevel;
4623
4624 sp<Entry> newEntry = new Entry(*e);
4625
4626 // Remove all items that have a higher SDK level than
4627 // the one we are synthesizing.
4628 for (size_t j = 0; j < sdkCount; j++) {
4629 if (j == i) {
4630 continue;
4631 }
4632
4633 if (attributesToRemove.keyAt(j) > sdkLevel) {
4634 const size_t attrCount = attributesToRemove[j].size();
4635 for (size_t k = 0; k < attrCount; k++) {
4636 newEntry->removeFromBag(attributesToRemove[j][k]);
4637 }
4638 }
4639 }
4640
4641 entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
4642 newConfig, newEntry));
4643 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004644
4645 // Remove the attribute from the original.
4646 for (size_t i = 0; i < attributesToRemove.size(); i++) {
Adam Lesinski28994d82015-01-13 13:42:41 -08004647 for (size_t j = 0; j < attributesToRemove[i].size(); j++) {
4648 e->removeFromBag(attributesToRemove[i][j]);
4649 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004650 }
4651 }
4652
4653 const size_t entriesToAddCount = entriesToAdd.size();
4654 for (size_t i = 0; i < entriesToAddCount; i++) {
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004655 assert(entries.indexOfKey(entriesToAdd[i].key) < 0);
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004656
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004657 if (bundle->getVerbose()) {
4658 entriesToAdd[i].value->getPos()
4659 .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
Adam Lesinski28994d82015-01-13 13:42:41 -08004660 entriesToAdd[i].key.sdkVersion,
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004661 String8(p->getName()).string(),
4662 String8(t->getName()).string(),
4663 String8(entriesToAdd[i].value->getName()).string(),
4664 entriesToAdd[i].key.toString().string());
4665 }
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004666
Adam Lesinski978ab9d2014-09-24 19:02:52 -07004667 sp<Entry> newEntry = t->getEntry(c->getName(),
4668 entriesToAdd[i].value->getPos(),
4669 &entriesToAdd[i].key);
4670
4671 *newEntry = *entriesToAdd[i].value;
Adam Lesinski82a2dd82014-09-17 18:34:15 -07004672 }
4673 }
4674 }
4675 }
4676 return NO_ERROR;
4677}
Adam Lesinskie572c012014-09-19 15:10:04 -07004678
4679status_t ResourceTable::modifyForCompat(const Bundle* bundle,
4680 const String16& resourceName,
4681 const sp<AaptFile>& target,
4682 const sp<XMLNode>& root) {
Adam Lesinski6e460562015-04-21 14:20:15 -07004683 const String16 vector16("vector");
4684 const String16 animatedVector16("animated-vector");
4685
Adam Lesinski28994d82015-01-13 13:42:41 -08004686 const int minSdk = getMinSdkVersion(bundle);
4687 if (minSdk >= SDK_LOLLIPOP_MR1) {
4688 // Lollipop MR1 and up handles public attributes differently, no
4689 // need to do any compat modifications.
Adam Lesinskie572c012014-09-19 15:10:04 -07004690 return NO_ERROR;
4691 }
4692
Adam Lesinski28994d82015-01-13 13:42:41 -08004693 const ConfigDescription config(target->getGroupEntry().toParams());
4694 if (target->getResourceType() == "" || config.sdkVersion >= SDK_LOLLIPOP_MR1) {
Adam Lesinski6e460562015-04-21 14:20:15 -07004695 // Skip resources that have no type (AndroidManifest.xml) or are already version qualified
4696 // with v21 or higher.
Adam Lesinskie572c012014-09-19 15:10:04 -07004697 return NO_ERROR;
4698 }
4699
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004700 sp<XMLNode> newRoot = NULL;
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004701 int sdkVersionToGenerate = SDK_LOLLIPOP_MR1;
Adam Lesinskie572c012014-09-19 15:10:04 -07004702
4703 Vector<sp<XMLNode> > nodesToVisit;
4704 nodesToVisit.push(root);
4705 while (!nodesToVisit.isEmpty()) {
4706 sp<XMLNode> node = nodesToVisit.top();
4707 nodesToVisit.pop();
4708
Adam Lesinski6e460562015-04-21 14:20:15 -07004709 if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 ||
4710 node->getElementName() == animatedVector16)) {
4711 // We were told not to version vector tags, so skip the children here.
4712 continue;
4713 }
4714
Adam Lesinskie572c012014-09-19 15:10:04 -07004715 const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004716 for (size_t i = 0; i < attrs.size(); i++) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004717 const XMLNode::attribute_entry& attr = attrs[i];
Adam Lesinski28994d82015-01-13 13:42:41 -08004718 const int sdkLevel = getPublicAttributeSdkLevel(attr.nameResId);
4719 if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004720 if (newRoot == NULL) {
4721 newRoot = root->clone();
4722 }
4723
Adam Lesinski28994d82015-01-13 13:42:41 -08004724 // Find the smallest sdk version that we need to synthesize for
4725 // and do that one. Subsequent versions will be processed on
4726 // the next pass.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004727 sdkVersionToGenerate = std::min(sdkLevel, sdkVersionToGenerate);
Adam Lesinski28994d82015-01-13 13:42:41 -08004728
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004729 if (bundle->getVerbose()) {
4730 SourcePos(node->getFilename(), node->getStartLineNumber()).printf(
4731 "removing attribute %s%s%s from <%s>",
4732 String8(attr.ns).string(),
4733 (attr.ns.size() == 0 ? "" : ":"),
4734 String8(attr.name).string(),
4735 String8(node->getElementName()).string());
4736 }
4737 node->removeAttribute(i);
4738 i--;
Adam Lesinskie572c012014-09-19 15:10:04 -07004739 }
4740 }
4741
4742 // Schedule a visit to the children.
4743 const Vector<sp<XMLNode> >& children = node->getChildren();
4744 const size_t childCount = children.size();
4745 for (size_t i = 0; i < childCount; i++) {
4746 nodesToVisit.push(children[i]);
4747 }
4748 }
4749
Adam Lesinskiea4e5ec2014-12-10 15:46:51 -08004750 if (newRoot == NULL) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004751 return NO_ERROR;
4752 }
4753
Adam Lesinskie572c012014-09-19 15:10:04 -07004754 // Look to see if we already have an overriding v21 configuration.
4755 sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
4756 String16(target->getResourceType()), resourceName);
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004757 if (shouldGenerateVersionedResource(cl, config, sdkVersionToGenerate)) {
Adam Lesinskie572c012014-09-19 15:10:04 -07004758 // We don't have an overriding entry for v21, so we must duplicate this one.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004759 ConfigDescription newConfig(config);
4760 newConfig.sdkVersion = sdkVersionToGenerate;
Adam Lesinskie572c012014-09-19 15:10:04 -07004761 sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
4762 AaptGroupEntry(newConfig), target->getResourceType());
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004763 String8 resPath = String8::format("res/%s/%s.xml",
Adam Lesinskie572c012014-09-19 15:10:04 -07004764 newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004765 String8(resourceName).string());
Adam Lesinskie572c012014-09-19 15:10:04 -07004766 resPath.convertToResPath();
4767
4768 // Add a resource table entry.
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004769 if (bundle->getVerbose()) {
4770 SourcePos(target->getSourceFile(), -1).printf(
4771 "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
Adam Lesinski28994d82015-01-13 13:42:41 -08004772 newConfig.sdkVersion,
Adam Lesinskif15de2e2014-10-03 14:57:28 -07004773 mAssets->getPackage().string(),
4774 newFile->getResourceType().string(),
4775 String8(resourceName).string(),
4776 newConfig.toString().string());
4777 }
Adam Lesinskie572c012014-09-19 15:10:04 -07004778
4779 addEntry(SourcePos(),
4780 String16(mAssets->getPackage()),
4781 String16(target->getResourceType()),
4782 resourceName,
4783 String16(resPath),
4784 NULL,
4785 &newConfig);
4786
4787 // Schedule this to be compiled.
4788 CompileResourceWorkItem item;
4789 item.resourceName = resourceName;
4790 item.resPath = resPath;
4791 item.file = newFile;
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004792 item.xmlRoot = newRoot;
4793 item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
4794 // to do it again.
Adam Lesinskie572c012014-09-19 15:10:04 -07004795 mWorkQueue.push(item);
4796 }
Adam Lesinskie572c012014-09-19 15:10:04 -07004797 return NO_ERROR;
4798}
Adam Lesinskide7de472014-11-03 12:03:08 -08004799
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004800void ResourceTable::getDensityVaryingResources(
4801 KeyedVector<Symbol, Vector<SymbolDefinition> >& resources) {
Adam Lesinskide7de472014-11-03 12:03:08 -08004802 const ConfigDescription nullConfig;
4803
4804 const size_t packageCount = mOrderedPackages.size();
4805 for (size_t p = 0; p < packageCount; p++) {
4806 const Vector<sp<Type> >& types = mOrderedPackages[p]->getOrderedTypes();
4807 const size_t typeCount = types.size();
4808 for (size_t t = 0; t < typeCount; t++) {
4809 const Vector<sp<ConfigList> >& configs = types[t]->getOrderedConfigs();
4810 const size_t configCount = configs.size();
4811 for (size_t c = 0; c < configCount; c++) {
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004812 const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configEntries
4813 = configs[c]->getEntries();
Adam Lesinskide7de472014-11-03 12:03:08 -08004814 const size_t configEntryCount = configEntries.size();
4815 for (size_t ce = 0; ce < configEntryCount; ce++) {
4816 const ConfigDescription& config = configEntries.keyAt(ce);
4817 if (AaptConfig::isDensityOnly(config)) {
4818 // This configuration only varies with regards to density.
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004819 const Symbol symbol(
4820 mOrderedPackages[p]->getName(),
Adam Lesinskide7de472014-11-03 12:03:08 -08004821 types[t]->getName(),
4822 configs[c]->getName(),
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004823 getResId(mOrderedPackages[p], types[t],
4824 configs[c]->getEntryIndex()));
Adam Lesinskide7de472014-11-03 12:03:08 -08004825
4826 const sp<Entry>& entry = configEntries.valueAt(ce);
Adam Lesinskif45d2fa2015-07-28 12:10:36 -07004827 AaptUtil::appendValue(resources, symbol,
4828 SymbolDefinition(symbol, config, entry->getPos()));
Adam Lesinskide7de472014-11-03 12:03:08 -08004829 }
4830 }
4831 }
4832 }
4833 }
4834}
Adam Lesinski07dfd2d2015-10-28 15:44:27 -07004835
4836static String16 buildNamespace(const String16& package) {
4837 return String16("http://schemas.android.com/apk/res/") + package;
4838}
4839
4840static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
4841 const Vector<sp<XMLNode> >& children = parent->getChildren();
4842 sp<XMLNode> onlyChild;
4843 for (size_t i = 0; i < children.size(); i++) {
4844 if (children[i]->getType() != XMLNode::TYPE_CDATA) {
4845 if (onlyChild != NULL) {
4846 return NULL;
4847 }
4848 onlyChild = children[i];
4849 }
4850 }
4851 return onlyChild;
4852}
4853
4854/**
4855 * Detects use of the `bundle' format and extracts nested resources into their own top level
4856 * resources. The bundle format looks like this:
4857 *
4858 * <!-- res/drawable/bundle.xml -->
4859 * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
4860 * <aapt:attr name="android:drawable">
4861 * <vector android:width="60dp"
4862 * android:height="60dp">
4863 * <path android:name="v"
4864 * android:fillColor="#000000"
4865 * android:pathData="M300,70 l 0,-70 70,..." />
4866 * </vector>
4867 * </aapt:attr>
4868 * </animated-vector>
4869 *
4870 * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
4871 * into a new high-level resource, assigning it a name and ID. Then value of the `name`
4872 * attribute must be a resource attribute. That resource attribute is inserted into the parent
4873 * with the reference to the extracted resource as the value.
4874 *
4875 * <!-- res/drawable/bundle.xml -->
4876 * <animated-vector android:drawable="@drawable/bundle_1.xml">
4877 * </animated-vector>
4878 *
4879 * <!-- res/drawable/bundle_1.xml -->
4880 * <vector android:width="60dp"
4881 * android:height="60dp">
4882 * <path android:name="v"
4883 * android:fillColor="#000000"
4884 * android:pathData="M300,70 l 0,-70 70,..." />
4885 * </vector>
4886 */
4887status_t ResourceTable::processBundleFormat(const Bundle* bundle,
4888 const String16& resourceName,
4889 const sp<AaptFile>& target,
4890 const sp<XMLNode>& root) {
4891 Vector<sp<XMLNode> > namespaces;
4892 if (root->getType() == XMLNode::TYPE_NAMESPACE) {
4893 namespaces.push(root);
4894 }
4895 return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
4896}
4897
4898status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
4899 const String16& resourceName,
4900 const sp<AaptFile>& target,
4901 const sp<XMLNode>& parent,
4902 Vector<sp<XMLNode> >* namespaces) {
4903 const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
4904 const String16 kName16("name");
4905 const String16 kAttr16("attr");
4906 const String16 kAssetPackage16(mAssets->getPackage());
4907
4908 Vector<sp<XMLNode> >& children = parent->getChildren();
4909 for (size_t i = 0; i < children.size(); i++) {
4910 const sp<XMLNode>& child = children[i];
4911
4912 if (child->getType() == XMLNode::TYPE_CDATA) {
4913 continue;
4914 } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
4915 namespaces->push(child);
4916 }
4917
4918 if (child->getElementNamespace() != kAaptNamespaceUri16 ||
4919 child->getElementName() != kAttr16) {
4920 status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
4921 namespaces);
4922 if (result != NO_ERROR) {
4923 return result;
4924 }
4925
4926 if (child->getType() == XMLNode::TYPE_NAMESPACE) {
4927 namespaces->pop();
4928 }
4929 continue;
4930 }
4931
4932 // This is the <aapt:attr> tag. Look for the 'name' attribute.
4933 SourcePos source(child->getFilename(), child->getStartLineNumber());
4934
4935 sp<XMLNode> nestedRoot = findOnlyChildElement(child);
4936 if (nestedRoot == NULL) {
4937 source.error("<%s:%s> must have exactly one child element",
4938 String8(child->getElementNamespace()).string(),
4939 String8(child->getElementName()).string());
4940 return UNKNOWN_ERROR;
4941 }
4942
4943 // Find the special attribute 'parent-attr'. This attribute's value contains
4944 // the resource attribute for which this element should be assigned in the parent.
4945 const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
4946 if (attr == NULL) {
4947 source.error("inline resource definition must specify an attribute via 'name'");
4948 return UNKNOWN_ERROR;
4949 }
4950
4951 // Parse the attribute name.
4952 const char* errorMsg = NULL;
4953 String16 attrPackage, attrType, attrName;
4954 bool result = ResTable::expandResourceRef(attr->string.string(),
4955 attr->string.size(),
4956 &attrPackage, &attrType, &attrName,
4957 &kAttr16, &kAssetPackage16,
4958 &errorMsg, NULL);
4959 if (!result) {
4960 source.error("invalid attribute name for 'name': %s", errorMsg);
4961 return UNKNOWN_ERROR;
4962 }
4963
4964 if (attrType != kAttr16) {
4965 // The value of the 'name' attribute must be an attribute reference.
4966 source.error("value of 'name' must be an attribute reference.");
4967 return UNKNOWN_ERROR;
4968 }
4969
4970 // Generate a name for this nested resource and try to add it to the table.
4971 // We do this in a loop because the name may be taken, in which case we will
4972 // increment a suffix until we succeed.
4973 String8 nestedResourceName;
4974 String8 nestedResourcePath;
4975 int suffix = 1;
4976 while (true) {
4977 // This child element will be extracted into its own resource file.
4978 // Generate a name and path for it from its parent.
4979 nestedResourceName = String8::format("%s_%d",
4980 String8(resourceName).string(), suffix++);
4981 nestedResourcePath = String8::format("res/%s/%s.xml",
4982 target->getGroupEntry().toDirName(target->getResourceType())
4983 .string(),
4984 nestedResourceName.string());
4985
4986 // Lookup or create the entry for this name.
4987 sp<Entry> entry = getEntry(kAssetPackage16,
4988 String16(target->getResourceType()),
4989 String16(nestedResourceName),
4990 source,
4991 false,
4992 &target->getGroupEntry().toParams(),
4993 true);
4994 if (entry == NULL) {
4995 return UNKNOWN_ERROR;
4996 }
4997
4998 if (entry->getType() == Entry::TYPE_UNKNOWN) {
4999 // The value for this resource has never been set,
5000 // meaning we're good!
5001 entry->setItem(source, String16(nestedResourcePath));
5002 break;
5003 }
5004
5005 // We failed (name already exists), so try with a different name
5006 // (increment the suffix).
5007 }
5008
5009 if (bundle->getVerbose()) {
5010 source.printf("generating nested resource %s:%s/%s",
5011 mAssets->getPackage().string(), target->getResourceType().string(),
5012 nestedResourceName.string());
5013 }
5014
5015 // Build the attribute reference and assign it to the parent.
5016 String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
5017 mAssets->getPackage().string(), target->getResourceType().string(),
5018 nestedResourceName.string()));
5019
5020 String16 attrNs = buildNamespace(attrPackage);
5021 if (parent->getAttribute(attrNs, attrName) != NULL) {
5022 SourcePos(parent->getFilename(), parent->getStartLineNumber())
5023 .error("parent of nested resource already defines attribute '%s:%s'",
5024 String8(attrPackage).string(), String8(attrName).string());
5025 return UNKNOWN_ERROR;
5026 }
5027
5028 // Add the reference to the inline resource.
5029 parent->addAttribute(attrNs, attrName, nestedResourceRef);
5030
5031 // Remove the <aapt:attr> child element from here.
5032 children.removeAt(i);
5033 i--;
5034
5035 // Append all namespace declarations that we've seen on this branch in the XML tree
5036 // to this resource.
5037 // We do this because the order of namespace declarations and prefix usage is determined
5038 // by the developer and we do not want to override any decisions. Be conservative.
5039 for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
5040 const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
5041 sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
5042 ns->getNamespaceUri());
5043 newNs->addChild(nestedRoot);
5044 nestedRoot = newNs;
5045 }
5046
5047 // Schedule compilation of the nested resource.
5048 CompileResourceWorkItem workItem;
5049 workItem.resPath = nestedResourcePath;
5050 workItem.resourceName = String16(nestedResourceName);
5051 workItem.xmlRoot = nestedRoot;
5052 workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
5053 target->getResourceType());
5054 mWorkQueue.push(workItem);
5055 }
5056 return NO_ERROR;
5057}