blob: e599643bfdb4fb2b681abd1f69a721c0a792954f [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#include "Main.h"
7#include "AaptAssets.h"
8#include "StringPool.h"
9#include "XMLNode.h"
10#include "ResourceTable.h"
11#include "Images.h"
12
13#include "CrunchCache.h"
14#include "FileFinder.h"
15#include "CacheUpdater.h"
16
17#include "WorkQueue.h"
18
19#if HAVE_PRINTF_ZD
20# define ZD "%zd"
21# define ZD_TYPE ssize_t
22#else
23# define ZD "%ld"
24# define ZD_TYPE long
25#endif
26
27#define NOISY(x) // x
28
29// Number of threads to use for preprocessing images.
30static const size_t MAX_THREADS = 4;
31
32// ==========================================================================
33// ==========================================================================
34// ==========================================================================
35
36class PackageInfo
37{
38public:
39 PackageInfo()
40 {
41 }
42 ~PackageInfo()
43 {
44 }
45
46 status_t parsePackage(const sp<AaptGroup>& grp);
47};
48
49// ==========================================================================
50// ==========================================================================
51// ==========================================================================
52
53static String8 parseResourceName(const String8& leaf)
54{
55 const char* firstDot = strchr(leaf.string(), '.');
56 const char* str = leaf.string();
57
58 if (firstDot) {
59 return String8(str, firstDot-str);
60 } else {
61 return String8(str);
62 }
63}
64
65ResourceTypeSet::ResourceTypeSet()
66 :RefBase(),
67 KeyedVector<String8,sp<AaptGroup> >()
68{
69}
70
71FilePathStore::FilePathStore()
72 :RefBase(),
73 Vector<String8>()
74{
75}
76
77class ResourceDirIterator
78{
79public:
80 ResourceDirIterator(const sp<ResourceTypeSet>& set, const String8& resType)
81 : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0)
82 {
Narayan Kamath91447d82014-01-21 15:32:36 +000083 memset(&mParams, 0, sizeof(ResTable_config));
Adam Lesinski282e1812014-01-23 18:17:42 -080084 }
85
86 inline const sp<AaptGroup>& getGroup() const { return mGroup; }
87 inline const sp<AaptFile>& getFile() const { return mFile; }
88
89 inline const String8& getBaseName() const { return mBaseName; }
90 inline const String8& getLeafName() const { return mLeafName; }
91 inline String8 getPath() const { return mPath; }
92 inline const ResTable_config& getParams() const { return mParams; }
93
94 enum {
95 EOD = 1
96 };
97
98 ssize_t next()
99 {
100 while (true) {
101 sp<AaptGroup> group;
102 sp<AaptFile> file;
103
104 // Try to get next file in this current group.
105 if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) {
106 group = mGroup;
107 file = group->getFiles().valueAt(mGroupPos++);
108
109 // Try to get the next group/file in this directory
110 } else if (mSetPos < mSet->size()) {
111 mGroup = group = mSet->valueAt(mSetPos++);
112 if (group->getFiles().size() < 1) {
113 continue;
114 }
115 file = group->getFiles().valueAt(0);
116 mGroupPos = 1;
117
118 // All done!
119 } else {
120 return EOD;
121 }
122
123 mFile = file;
124
125 String8 leaf(group->getLeaf());
126 mLeafName = String8(leaf);
127 mParams = file->getGroupEntry().toParams();
128 NOISY(printf("Dir %s: mcc=%d mnc=%d lang=%c%c cnt=%c%c orient=%d ui=%d density=%d touch=%d key=%d inp=%d nav=%d\n",
129 group->getPath().string(), mParams.mcc, mParams.mnc,
130 mParams.language[0] ? mParams.language[0] : '-',
131 mParams.language[1] ? mParams.language[1] : '-',
132 mParams.country[0] ? mParams.country[0] : '-',
133 mParams.country[1] ? mParams.country[1] : '-',
134 mParams.orientation, mParams.uiMode,
135 mParams.density, mParams.touchscreen, mParams.keyboard,
136 mParams.inputFlags, mParams.navigation));
137 mPath = "res";
138 mPath.appendPath(file->getGroupEntry().toDirName(mResType));
139 mPath.appendPath(leaf);
140 mBaseName = parseResourceName(leaf);
141 if (mBaseName == "") {
142 fprintf(stderr, "Error: malformed resource filename %s\n",
143 file->getPrintableSource().string());
144 return UNKNOWN_ERROR;
145 }
146
147 NOISY(printf("file name=%s\n", mBaseName.string()));
148
149 return NO_ERROR;
150 }
151 }
152
153private:
154 String8 mResType;
155
156 const sp<ResourceTypeSet> mSet;
157 size_t mSetPos;
158
159 sp<AaptGroup> mGroup;
160 size_t mGroupPos;
161
162 sp<AaptFile> mFile;
163 String8 mBaseName;
164 String8 mLeafName;
165 String8 mPath;
166 ResTable_config mParams;
167};
168
169// ==========================================================================
170// ==========================================================================
171// ==========================================================================
172
173bool isValidResourceType(const String8& type)
174{
175 return type == "anim" || type == "animator" || type == "interpolator"
Chet Haase7cce7bb2013-09-04 17:41:11 -0700176 || type == "transition"
Adam Lesinski282e1812014-01-23 18:17:42 -0800177 || type == "drawable" || type == "layout"
178 || type == "values" || type == "xml" || type == "raw"
179 || type == "color" || type == "menu" || type == "mipmap";
180}
181
Adam Lesinski282e1812014-01-23 18:17:42 -0800182static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
183 const sp<AaptGroup>& grp)
184{
185 if (grp->getFiles().size() != 1) {
186 fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
187 grp->getFiles().valueAt(0)->getPrintableSource().string());
188 }
189
190 sp<AaptFile> file = grp->getFiles().valueAt(0);
191
192 ResXMLTree block;
193 status_t err = parseXMLResource(file, &block);
194 if (err != NO_ERROR) {
195 return err;
196 }
197 //printXMLBlock(&block);
198
199 ResXMLTree::event_code_t code;
200 while ((code=block.next()) != ResXMLTree::START_TAG
201 && code != ResXMLTree::END_DOCUMENT
202 && code != ResXMLTree::BAD_DOCUMENT) {
203 }
204
205 size_t len;
206 if (code != ResXMLTree::START_TAG) {
207 fprintf(stderr, "%s:%d: No start tag found\n",
208 file->getPrintableSource().string(), block.getLineNumber());
209 return UNKNOWN_ERROR;
210 }
211 if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
212 fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
213 file->getPrintableSource().string(), block.getLineNumber(),
214 String8(block.getElementName(&len)).string());
215 return UNKNOWN_ERROR;
216 }
217
218 ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
219 if (nameIndex < 0) {
220 fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
221 file->getPrintableSource().string(), block.getLineNumber());
222 return UNKNOWN_ERROR;
223 }
224
225 assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
226
227 String16 uses_sdk16("uses-sdk");
228 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
229 && code != ResXMLTree::BAD_DOCUMENT) {
230 if (code == ResXMLTree::START_TAG) {
231 if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
232 ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
233 "minSdkVersion");
234 if (minSdkIndex >= 0) {
235 const uint16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len);
236 const char* minSdk8 = strdup(String8(minSdk16).string());
237 bundle->setManifestMinSdkVersion(minSdk8);
238 }
239 }
240 }
241 }
242
243 return NO_ERROR;
244}
245
246// ==========================================================================
247// ==========================================================================
248// ==========================================================================
249
250static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
251 ResourceTable* table,
252 const sp<ResourceTypeSet>& set,
253 const char* resType)
254{
255 String8 type8(resType);
256 String16 type16(resType);
257
258 bool hasErrors = false;
259
260 ResourceDirIterator it(set, String8(resType));
261 ssize_t res;
262 while ((res=it.next()) == NO_ERROR) {
263 if (bundle->getVerbose()) {
264 printf(" (new resource id %s from %s)\n",
265 it.getBaseName().string(), it.getFile()->getPrintableSource().string());
266 }
267 String16 baseName(it.getBaseName());
268 const char16_t* str = baseName.string();
269 const char16_t* const end = str + baseName.size();
270 while (str < end) {
271 if (!((*str >= 'a' && *str <= 'z')
272 || (*str >= '0' && *str <= '9')
273 || *str == '_' || *str == '.')) {
274 fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
275 it.getPath().string());
276 hasErrors = true;
277 }
278 str++;
279 }
280 String8 resPath = it.getPath();
281 resPath.convertToResPath();
282 table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
283 type16,
284 baseName,
285 String16(resPath),
286 NULL,
287 &it.getParams());
288 assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
289 }
290
291 return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
292}
293
294class PreProcessImageWorkUnit : public WorkQueue::WorkUnit {
295public:
296 PreProcessImageWorkUnit(const Bundle* bundle, const sp<AaptAssets>& assets,
297 const sp<AaptFile>& file, volatile bool* hasErrors) :
298 mBundle(bundle), mAssets(assets), mFile(file), mHasErrors(hasErrors) {
299 }
300
301 virtual bool run() {
302 status_t status = preProcessImage(mBundle, mAssets, mFile, NULL);
303 if (status) {
304 *mHasErrors = true;
305 }
306 return true; // continue even if there are errors
307 }
308
309private:
310 const Bundle* mBundle;
311 sp<AaptAssets> mAssets;
312 sp<AaptFile> mFile;
313 volatile bool* mHasErrors;
314};
315
316static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& assets,
317 const sp<ResourceTypeSet>& set, const char* type)
318{
319 volatile bool hasErrors = false;
320 ssize_t res = NO_ERROR;
321 if (bundle->getUseCrunchCache() == false) {
322 WorkQueue wq(MAX_THREADS, false);
323 ResourceDirIterator it(set, String8(type));
324 while ((res=it.next()) == NO_ERROR) {
325 PreProcessImageWorkUnit* w = new PreProcessImageWorkUnit(
326 bundle, assets, it.getFile(), &hasErrors);
327 status_t status = wq.schedule(w);
328 if (status) {
329 fprintf(stderr, "preProcessImages failed: schedule() returned %d\n", status);
330 hasErrors = true;
331 delete w;
332 break;
333 }
334 }
335 status_t status = wq.finish();
336 if (status) {
337 fprintf(stderr, "preProcessImages failed: finish() returned %d\n", status);
338 hasErrors = true;
339 }
340 }
341 return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
342}
343
Adam Lesinski282e1812014-01-23 18:17:42 -0800344static void collect_files(const sp<AaptDir>& dir,
345 KeyedVector<String8, sp<ResourceTypeSet> >* resources)
346{
347 const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
348 int N = groups.size();
349 for (int i=0; i<N; i++) {
350 String8 leafName = groups.keyAt(i);
351 const sp<AaptGroup>& group = groups.valueAt(i);
352
353 const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
354 = group->getFiles();
355
356 if (files.size() == 0) {
357 continue;
358 }
359
360 String8 resType = files.valueAt(0)->getResourceType();
361
362 ssize_t index = resources->indexOfKey(resType);
363
364 if (index < 0) {
365 sp<ResourceTypeSet> set = new ResourceTypeSet();
366 NOISY(printf("Creating new resource type set for leaf %s with group %s (%p)\n",
367 leafName.string(), group->getPath().string(), group.get()));
368 set->add(leafName, group);
369 resources->add(resType, set);
370 } else {
371 sp<ResourceTypeSet> set = resources->valueAt(index);
372 index = set->indexOfKey(leafName);
373 if (index < 0) {
374 NOISY(printf("Adding to resource type set for leaf %s group %s (%p)\n",
375 leafName.string(), group->getPath().string(), group.get()));
376 set->add(leafName, group);
377 } else {
378 sp<AaptGroup> existingGroup = set->valueAt(index);
379 NOISY(printf("Extending to resource type set for leaf %s group %s (%p)\n",
380 leafName.string(), group->getPath().string(), group.get()));
381 for (size_t j=0; j<files.size(); j++) {
382 NOISY(printf("Adding file %s in group %s resType %s\n",
383 files.valueAt(j)->getSourceFile().string(),
384 files.keyAt(j).toDirName(String8()).string(),
385 resType.string()));
386 status_t err = existingGroup->addFile(files.valueAt(j));
387 }
388 }
389 }
390 }
391}
392
393static void collect_files(const sp<AaptAssets>& ass,
394 KeyedVector<String8, sp<ResourceTypeSet> >* resources)
395{
396 const Vector<sp<AaptDir> >& dirs = ass->resDirs();
397 int N = dirs.size();
398
399 for (int i=0; i<N; i++) {
400 sp<AaptDir> d = dirs.itemAt(i);
401 NOISY(printf("Collecting dir #%d %p: %s, leaf %s\n", i, d.get(), d->getPath().string(),
402 d->getLeaf().string()));
403 collect_files(d, resources);
404
405 // don't try to include the res dir
406 NOISY(printf("Removing dir leaf %s\n", d->getLeaf().string()));
407 ass->removeDir(d->getLeaf());
408 }
409}
410
411enum {
412 ATTR_OKAY = -1,
413 ATTR_NOT_FOUND = -2,
414 ATTR_LEADING_SPACES = -3,
415 ATTR_TRAILING_SPACES = -4
416};
417static int validateAttr(const String8& path, const ResTable& table,
418 const ResXMLParser& parser,
419 const char* ns, const char* attr, const char* validChars, bool required)
420{
421 size_t len;
422
423 ssize_t index = parser.indexOfAttribute(ns, attr);
424 const uint16_t* str;
425 Res_value value;
426 if (index >= 0 && parser.getAttributeValue(index, &value) >= 0) {
427 const ResStringPool* pool = &parser.getStrings();
428 if (value.dataType == Res_value::TYPE_REFERENCE) {
429 uint32_t specFlags = 0;
430 int strIdx;
431 if ((strIdx=table.resolveReference(&value, 0x10000000, NULL, &specFlags)) < 0) {
432 fprintf(stderr, "%s:%d: Tag <%s> attribute %s references unknown resid 0x%08x.\n",
433 path.string(), parser.getLineNumber(),
434 String8(parser.getElementName(&len)).string(), attr,
435 value.data);
436 return ATTR_NOT_FOUND;
437 }
438
439 pool = table.getTableStringBlock(strIdx);
440 #if 0
441 if (pool != NULL) {
442 str = pool->stringAt(value.data, &len);
443 }
444 printf("***** RES ATTR: %s specFlags=0x%x strIdx=%d: %s\n", attr,
445 specFlags, strIdx, str != NULL ? String8(str).string() : "???");
446 #endif
447 if ((specFlags&~ResTable_typeSpec::SPEC_PUBLIC) != 0 && false) {
448 fprintf(stderr, "%s:%d: Tag <%s> attribute %s varies by configurations 0x%x.\n",
449 path.string(), parser.getLineNumber(),
450 String8(parser.getElementName(&len)).string(), attr,
451 specFlags);
452 return ATTR_NOT_FOUND;
453 }
454 }
455 if (value.dataType == Res_value::TYPE_STRING) {
456 if (pool == NULL) {
457 fprintf(stderr, "%s:%d: Tag <%s> attribute %s has no string block.\n",
458 path.string(), parser.getLineNumber(),
459 String8(parser.getElementName(&len)).string(), attr);
460 return ATTR_NOT_FOUND;
461 }
462 if ((str=pool->stringAt(value.data, &len)) == NULL) {
463 fprintf(stderr, "%s:%d: Tag <%s> attribute %s has corrupt string value.\n",
464 path.string(), parser.getLineNumber(),
465 String8(parser.getElementName(&len)).string(), attr);
466 return ATTR_NOT_FOUND;
467 }
468 } else {
469 fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid type %d.\n",
470 path.string(), parser.getLineNumber(),
471 String8(parser.getElementName(&len)).string(), attr,
472 value.dataType);
473 return ATTR_NOT_FOUND;
474 }
475 if (validChars) {
476 for (size_t i=0; i<len; i++) {
477 uint16_t c = str[i];
478 const char* p = validChars;
479 bool okay = false;
480 while (*p) {
481 if (c == *p) {
482 okay = true;
483 break;
484 }
485 p++;
486 }
487 if (!okay) {
488 fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid character '%c'.\n",
489 path.string(), parser.getLineNumber(),
490 String8(parser.getElementName(&len)).string(), attr, (char)str[i]);
491 return (int)i;
492 }
493 }
494 }
495 if (*str == ' ') {
496 fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not start with a space.\n",
497 path.string(), parser.getLineNumber(),
498 String8(parser.getElementName(&len)).string(), attr);
499 return ATTR_LEADING_SPACES;
500 }
501 if (str[len-1] == ' ') {
502 fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not end with a space.\n",
503 path.string(), parser.getLineNumber(),
504 String8(parser.getElementName(&len)).string(), attr);
505 return ATTR_TRAILING_SPACES;
506 }
507 return ATTR_OKAY;
508 }
509 if (required) {
510 fprintf(stderr, "%s:%d: Tag <%s> missing required attribute %s.\n",
511 path.string(), parser.getLineNumber(),
512 String8(parser.getElementName(&len)).string(), attr);
513 return ATTR_NOT_FOUND;
514 }
515 return ATTR_OKAY;
516}
517
518static void checkForIds(const String8& path, ResXMLParser& parser)
519{
520 ResXMLTree::event_code_t code;
521 while ((code=parser.next()) != ResXMLTree::END_DOCUMENT
522 && code > ResXMLTree::BAD_DOCUMENT) {
523 if (code == ResXMLTree::START_TAG) {
524 ssize_t index = parser.indexOfAttribute(NULL, "id");
525 if (index >= 0) {
526 fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n",
527 path.string(), parser.getLineNumber());
528 }
529 }
530 }
531}
532
533static bool applyFileOverlay(Bundle *bundle,
534 const sp<AaptAssets>& assets,
535 sp<ResourceTypeSet> *baseSet,
536 const char *resType)
537{
538 if (bundle->getVerbose()) {
539 printf("applyFileOverlay for %s\n", resType);
540 }
541
542 // Replace any base level files in this category with any found from the overlay
543 // Also add any found only in the overlay.
544 sp<AaptAssets> overlay = assets->getOverlay();
545 String8 resTypeString(resType);
546
547 // work through the linked list of overlays
548 while (overlay.get()) {
549 KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources();
550
551 // get the overlay resources of the requested type
552 ssize_t index = overlayRes->indexOfKey(resTypeString);
553 if (index >= 0) {
554 sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index);
555
556 // for each of the resources, check for a match in the previously built
557 // non-overlay "baseset".
558 size_t overlayCount = overlaySet->size();
559 for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) {
560 if (bundle->getVerbose()) {
561 printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string());
562 }
563 size_t baseIndex = UNKNOWN_ERROR;
564 if (baseSet->get() != NULL) {
565 baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex));
566 }
567 if (baseIndex < UNKNOWN_ERROR) {
568 // look for same flavor. For a given file (strings.xml, for example)
569 // there may be a locale specific or other flavors - we want to match
570 // the same flavor.
571 sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
572 sp<AaptGroup> baseGroup = (*baseSet)->valueAt(baseIndex);
573
574 DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
575 overlayGroup->getFiles();
576 if (bundle->getVerbose()) {
577 DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
578 baseGroup->getFiles();
579 for (size_t i=0; i < baseFiles.size(); i++) {
580 printf("baseFile " ZD " has flavor %s\n", (ZD_TYPE) i,
581 baseFiles.keyAt(i).toString().string());
582 }
583 for (size_t i=0; i < overlayFiles.size(); i++) {
584 printf("overlayFile " ZD " has flavor %s\n", (ZD_TYPE) i,
585 overlayFiles.keyAt(i).toString().string());
586 }
587 }
588
589 size_t overlayGroupSize = overlayFiles.size();
590 for (size_t overlayGroupIndex = 0;
591 overlayGroupIndex<overlayGroupSize;
592 overlayGroupIndex++) {
593 size_t baseFileIndex =
594 baseGroup->getFiles().indexOfKey(overlayFiles.
595 keyAt(overlayGroupIndex));
596 if (baseFileIndex < UNKNOWN_ERROR) {
597 if (bundle->getVerbose()) {
598 printf("found a match (" ZD ") for overlay file %s, for flavor %s\n",
599 (ZD_TYPE) baseFileIndex,
600 overlayGroup->getLeaf().string(),
601 overlayFiles.keyAt(overlayGroupIndex).toString().string());
602 }
603 baseGroup->removeFile(baseFileIndex);
604 } else {
605 // didn't find a match fall through and add it..
606 if (true || bundle->getVerbose()) {
607 printf("nothing matches overlay file %s, for flavor %s\n",
608 overlayGroup->getLeaf().string(),
609 overlayFiles.keyAt(overlayGroupIndex).toString().string());
610 }
611 }
612 baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex));
613 assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
614 }
615 } else {
616 if (baseSet->get() == NULL) {
617 *baseSet = new ResourceTypeSet();
618 assets->getResources()->add(String8(resType), *baseSet);
619 }
620 // this group doesn't exist (a file that's only in the overlay)
621 (*baseSet)->add(overlaySet->keyAt(overlayIndex),
622 overlaySet->valueAt(overlayIndex));
623 // make sure all flavors are defined in the resources.
624 sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
625 DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
626 overlayGroup->getFiles();
627 size_t overlayGroupSize = overlayFiles.size();
628 for (size_t overlayGroupIndex = 0;
629 overlayGroupIndex<overlayGroupSize;
630 overlayGroupIndex++) {
631 assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
632 }
633 }
634 }
635 // this overlay didn't have resources for this type
636 }
637 // try next overlay
638 overlay = overlay->getOverlay();
639 }
640 return true;
641}
642
643/*
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800644 * Inserts an attribute in a given node.
Adam Lesinski282e1812014-01-23 18:17:42 -0800645 * If errorOnFailedInsert is true, and the attribute already exists, returns false.
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800646 * If replaceExisting is true, the attribute will be updated if it already exists.
647 * Returns true otherwise, even if the attribute already exists, and does not modify
648 * the existing attribute's value.
Adam Lesinski282e1812014-01-23 18:17:42 -0800649 */
650bool addTagAttribute(const sp<XMLNode>& node, const char* ns8,
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800651 const char* attr8, const char* value, bool errorOnFailedInsert,
652 bool replaceExisting)
Adam Lesinski282e1812014-01-23 18:17:42 -0800653{
654 if (value == NULL) {
655 return true;
656 }
657
658 const String16 ns(ns8);
659 const String16 attr(attr8);
660
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800661 XMLNode::attribute_entry* existingEntry = node->editAttribute(ns, attr);
662 if (existingEntry != NULL) {
663 if (replaceExisting) {
664 NOISY(printf("Info: AndroidManifest.xml already defines %s (in %s);"
665 " overwriting existing value from manifest.\n",
666 String8(attr).string(), String8(ns).string()));
667 existingEntry->string = String16(value);
668 return true;
669 }
670
Adam Lesinski282e1812014-01-23 18:17:42 -0800671 if (errorOnFailedInsert) {
672 fprintf(stderr, "Error: AndroidManifest.xml already defines %s (in %s);"
673 " cannot insert new value %s.\n",
674 String8(attr).string(), String8(ns).string(), value);
675 return false;
676 }
677
678 fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s);"
679 " using existing value in manifest.\n",
680 String8(attr).string(), String8(ns).string());
681
682 // don't stop the build.
683 return true;
684 }
685
686 node->addAttribute(ns, attr, String16(value));
687 return true;
688}
689
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800690/*
691 * Inserts an attribute in a given node, only if the attribute does not
692 * exist.
693 * If errorOnFailedInsert is true, and the attribute already exists, returns false.
694 * Returns true otherwise, even if the attribute already exists.
695 */
696bool addTagAttribute(const sp<XMLNode>& node, const char* ns8,
697 const char* attr8, const char* value, bool errorOnFailedInsert)
698{
699 return addTagAttribute(node, ns8, attr8, value, errorOnFailedInsert, false);
700}
701
Adam Lesinski282e1812014-01-23 18:17:42 -0800702static void fullyQualifyClassName(const String8& package, sp<XMLNode> node,
703 const String16& attrName) {
704 XMLNode::attribute_entry* attr = node->editAttribute(
705 String16("http://schemas.android.com/apk/res/android"), attrName);
706 if (attr != NULL) {
707 String8 name(attr->string);
708
709 // asdf --> package.asdf
710 // .asdf .a.b --> package.asdf package.a.b
711 // asdf.adsf --> asdf.asdf
712 String8 className;
713 const char* p = name.string();
714 const char* q = strchr(p, '.');
715 if (p == q) {
716 className += package;
717 className += name;
718 } else if (q == NULL) {
719 className += package;
720 className += ".";
721 className += name;
722 } else {
723 className += name;
724 }
725 NOISY(printf("Qualifying class '%s' to '%s'", name.string(), className.string()));
726 attr->string.setTo(String16(className));
727 }
728}
729
730status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
731{
732 root = root->searchElement(String16(), String16("manifest"));
733 if (root == NULL) {
734 fprintf(stderr, "No <manifest> tag.\n");
735 return UNKNOWN_ERROR;
736 }
737
738 bool errorOnFailedInsert = bundle->getErrorOnFailedInsert();
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800739 bool replaceVersion = bundle->getReplaceVersion();
Adam Lesinski282e1812014-01-23 18:17:42 -0800740
741 if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode",
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800742 bundle->getVersionCode(), errorOnFailedInsert, replaceVersion)) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800743 return UNKNOWN_ERROR;
744 }
745 if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName",
Jeff Davidsondf08d1c2014-02-25 12:28:08 -0800746 bundle->getVersionName(), errorOnFailedInsert, replaceVersion)) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800747 return UNKNOWN_ERROR;
748 }
749
750 if (bundle->getMinSdkVersion() != NULL
751 || bundle->getTargetSdkVersion() != NULL
752 || bundle->getMaxSdkVersion() != NULL) {
753 sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk"));
754 if (vers == NULL) {
755 vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk"));
756 root->insertChildAt(vers, 0);
757 }
758
759 if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion",
760 bundle->getMinSdkVersion(), errorOnFailedInsert)) {
761 return UNKNOWN_ERROR;
762 }
763 if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "targetSdkVersion",
764 bundle->getTargetSdkVersion(), errorOnFailedInsert)) {
765 return UNKNOWN_ERROR;
766 }
767 if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "maxSdkVersion",
768 bundle->getMaxSdkVersion(), errorOnFailedInsert)) {
769 return UNKNOWN_ERROR;
770 }
771 }
772
773 if (bundle->getDebugMode()) {
774 sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
775 if (application != NULL) {
776 if (!addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable", "true",
777 errorOnFailedInsert)) {
778 return UNKNOWN_ERROR;
779 }
780 }
781 }
782
783 // Deal with manifest package name overrides
784 const char* manifestPackageNameOverride = bundle->getManifestPackageNameOverride();
785 if (manifestPackageNameOverride != NULL) {
786 // Update the actual package name
787 XMLNode::attribute_entry* attr = root->editAttribute(String16(), String16("package"));
788 if (attr == NULL) {
789 fprintf(stderr, "package name is required with --rename-manifest-package.\n");
790 return UNKNOWN_ERROR;
791 }
792 String8 origPackage(attr->string);
793 attr->string.setTo(String16(manifestPackageNameOverride));
794 NOISY(printf("Overriding package '%s' to be '%s'\n", origPackage.string(), manifestPackageNameOverride));
795
796 // Make class names fully qualified
797 sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
798 if (application != NULL) {
799 fullyQualifyClassName(origPackage, application, String16("name"));
800 fullyQualifyClassName(origPackage, application, String16("backupAgent"));
801
802 Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(application->getChildren());
803 for (size_t i = 0; i < children.size(); i++) {
804 sp<XMLNode> child = children.editItemAt(i);
805 String8 tag(child->getElementName());
806 if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
807 fullyQualifyClassName(origPackage, child, String16("name"));
808 } else if (tag == "activity-alias") {
809 fullyQualifyClassName(origPackage, child, String16("name"));
810 fullyQualifyClassName(origPackage, child, String16("targetActivity"));
811 }
812 }
813 }
814 }
815
816 // Deal with manifest package name overrides
817 const char* instrumentationPackageNameOverride = bundle->getInstrumentationPackageNameOverride();
818 if (instrumentationPackageNameOverride != NULL) {
819 // Fix up instrumentation targets.
820 Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(root->getChildren());
821 for (size_t i = 0; i < children.size(); i++) {
822 sp<XMLNode> child = children.editItemAt(i);
823 String8 tag(child->getElementName());
824 if (tag == "instrumentation") {
825 XMLNode::attribute_entry* attr = child->editAttribute(
826 String16("http://schemas.android.com/apk/res/android"), String16("targetPackage"));
827 if (attr != NULL) {
828 attr->string.setTo(String16(instrumentationPackageNameOverride));
829 }
830 }
831 }
832 }
833
834 return NO_ERROR;
835}
836
837#define ASSIGN_IT(n) \
838 do { \
839 ssize_t index = resources->indexOfKey(String8(#n)); \
840 if (index >= 0) { \
841 n ## s = resources->valueAt(index); \
842 } \
843 } while (0)
844
845status_t updatePreProcessedCache(Bundle* bundle)
846{
847 #if BENCHMARK
848 fprintf(stdout, "BENCHMARK: Starting PNG PreProcessing \n");
849 long startPNGTime = clock();
850 #endif /* BENCHMARK */
851
852 String8 source(bundle->getResourceSourceDirs()[0]);
853 String8 dest(bundle->getCrunchedOutputDir());
854
855 FileFinder* ff = new SystemFileFinder();
856 CrunchCache cc(source,dest,ff);
857
858 CacheUpdater* cu = new SystemCacheUpdater(bundle);
859 size_t numFiles = cc.crunch(cu);
860
861 if (bundle->getVerbose())
862 fprintf(stdout, "Crunched %d PNG files to update cache\n", (int)numFiles);
863
864 delete ff;
865 delete cu;
866
867 #if BENCHMARK
868 fprintf(stdout, "BENCHMARK: End PNG PreProcessing. Time Elapsed: %f ms \n"
869 ,(clock() - startPNGTime)/1000.0);
870 #endif /* BENCHMARK */
871 return 0;
872}
873
Adam Lesinskifab50872014-04-16 14:40:42 -0700874status_t generateAndroidManifestForSplit(const String16& package, const sp<ApkSplit>& split,
875 sp<AaptFile>& outFile) {
876 const String8 filename("AndroidManifest.xml");
877 const String16 androidPrefix("android");
878 const String16 androidNSUri("http://schemas.android.com/apk/res/android");
879 sp<XMLNode> root = XMLNode::newNamespace(filename, androidPrefix, androidNSUri);
880
881 // Build the <manifest> tag
882 sp<XMLNode> manifest = XMLNode::newElement(filename, String16(), String16("manifest"));
883
884 // Add the 'package' attribute which is set to the original package name.
885 manifest->addAttribute(String16(), String16("package"), package);
886
887 // Add the 'split' attribute which describes the configurations included.
888 String8 splitName("config_");
889 splitName.append(split->getDirectorySafeName());
890 manifest->addAttribute(String16(), String16("split"), String16(splitName));
891
892 // Build an empty <application> tag (required).
893 sp<XMLNode> app = XMLNode::newElement(filename, String16(), String16("application"));
894 manifest->addChild(app);
895 root->addChild(manifest);
896
897 status_t err = root->flatten(outFile, true, true);
898 if (err != NO_ERROR) {
899 return err;
900 }
901 outFile->setCompressionMethod(ZipEntry::kCompressDeflated);
902 return NO_ERROR;
903}
904
905status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
Adam Lesinski282e1812014-01-23 18:17:42 -0800906{
907 // First, look for a package file to parse. This is required to
908 // be able to generate the resource information.
909 sp<AaptGroup> androidManifestFile =
910 assets->getFiles().valueFor(String8("AndroidManifest.xml"));
911 if (androidManifestFile == NULL) {
912 fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
913 return UNKNOWN_ERROR;
914 }
915
916 status_t err = parsePackage(bundle, assets, androidManifestFile);
917 if (err != NO_ERROR) {
918 return err;
919 }
920
921 NOISY(printf("Creating resources for package %s\n",
922 assets->getPackage().string()));
923
924 ResourceTable table(bundle, String16(assets->getPackage()));
925 err = table.addIncludedResources(bundle, assets);
926 if (err != NO_ERROR) {
927 return err;
928 }
929
930 NOISY(printf("Found %d included resource packages\n", (int)table.size()));
931
932 // Standard flags for compiled XML and optional UTF-8 encoding
933 int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
934
935 /* Only enable UTF-8 if the caller of aapt didn't specifically
936 * request UTF-16 encoding and the parameters of this package
937 * allow UTF-8 to be used.
938 */
939 if (!bundle->getUTF16StringsOption()) {
940 xmlFlags |= XML_COMPILE_UTF8;
941 }
942
943 // --------------------------------------------------------------
944 // First, gather all resource information.
945 // --------------------------------------------------------------
946
947 // resType -> leafName -> group
948 KeyedVector<String8, sp<ResourceTypeSet> > *resources =
949 new KeyedVector<String8, sp<ResourceTypeSet> >;
950 collect_files(assets, resources);
951
952 sp<ResourceTypeSet> drawables;
953 sp<ResourceTypeSet> layouts;
954 sp<ResourceTypeSet> anims;
955 sp<ResourceTypeSet> animators;
956 sp<ResourceTypeSet> interpolators;
957 sp<ResourceTypeSet> transitions;
Adam Lesinski282e1812014-01-23 18:17:42 -0800958 sp<ResourceTypeSet> xmls;
959 sp<ResourceTypeSet> raws;
960 sp<ResourceTypeSet> colors;
961 sp<ResourceTypeSet> menus;
962 sp<ResourceTypeSet> mipmaps;
963
964 ASSIGN_IT(drawable);
965 ASSIGN_IT(layout);
966 ASSIGN_IT(anim);
967 ASSIGN_IT(animator);
968 ASSIGN_IT(interpolator);
969 ASSIGN_IT(transition);
Adam Lesinski282e1812014-01-23 18:17:42 -0800970 ASSIGN_IT(xml);
971 ASSIGN_IT(raw);
972 ASSIGN_IT(color);
973 ASSIGN_IT(menu);
974 ASSIGN_IT(mipmap);
975
976 assets->setResources(resources);
977 // now go through any resource overlays and collect their files
978 sp<AaptAssets> current = assets->getOverlay();
979 while(current.get()) {
980 KeyedVector<String8, sp<ResourceTypeSet> > *resources =
981 new KeyedVector<String8, sp<ResourceTypeSet> >;
982 current->setResources(resources);
983 collect_files(current, resources);
984 current = current->getOverlay();
985 }
986 // apply the overlay files to the base set
987 if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
988 !applyFileOverlay(bundle, assets, &layouts, "layout") ||
989 !applyFileOverlay(bundle, assets, &anims, "anim") ||
990 !applyFileOverlay(bundle, assets, &animators, "animator") ||
991 !applyFileOverlay(bundle, assets, &interpolators, "interpolator") ||
992 !applyFileOverlay(bundle, assets, &transitions, "transition") ||
Adam Lesinski282e1812014-01-23 18:17:42 -0800993 !applyFileOverlay(bundle, assets, &xmls, "xml") ||
994 !applyFileOverlay(bundle, assets, &raws, "raw") ||
995 !applyFileOverlay(bundle, assets, &colors, "color") ||
996 !applyFileOverlay(bundle, assets, &menus, "menu") ||
997 !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
998 return UNKNOWN_ERROR;
999 }
1000
1001 bool hasErrors = false;
1002
1003 if (drawables != NULL) {
1004 if (bundle->getOutputAPKFile() != NULL) {
1005 err = preProcessImages(bundle, assets, drawables, "drawable");
1006 }
1007 if (err == NO_ERROR) {
1008 err = makeFileResources(bundle, assets, &table, drawables, "drawable");
1009 if (err != NO_ERROR) {
1010 hasErrors = true;
1011 }
1012 } else {
1013 hasErrors = true;
1014 }
1015 }
1016
1017 if (mipmaps != NULL) {
1018 if (bundle->getOutputAPKFile() != NULL) {
1019 err = preProcessImages(bundle, assets, mipmaps, "mipmap");
1020 }
1021 if (err == NO_ERROR) {
1022 err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap");
1023 if (err != NO_ERROR) {
1024 hasErrors = true;
1025 }
1026 } else {
1027 hasErrors = true;
1028 }
1029 }
1030
1031 if (layouts != NULL) {
1032 err = makeFileResources(bundle, assets, &table, layouts, "layout");
1033 if (err != NO_ERROR) {
1034 hasErrors = true;
1035 }
1036 }
1037
1038 if (anims != NULL) {
1039 err = makeFileResources(bundle, assets, &table, anims, "anim");
1040 if (err != NO_ERROR) {
1041 hasErrors = true;
1042 }
1043 }
1044
1045 if (animators != NULL) {
1046 err = makeFileResources(bundle, assets, &table, animators, "animator");
1047 if (err != NO_ERROR) {
1048 hasErrors = true;
1049 }
1050 }
1051
1052 if (transitions != NULL) {
1053 err = makeFileResources(bundle, assets, &table, transitions, "transition");
1054 if (err != NO_ERROR) {
1055 hasErrors = true;
1056 }
1057 }
1058
Adam Lesinski282e1812014-01-23 18:17:42 -08001059 if (interpolators != NULL) {
1060 err = makeFileResources(bundle, assets, &table, interpolators, "interpolator");
1061 if (err != NO_ERROR) {
1062 hasErrors = true;
1063 }
1064 }
1065
1066 if (xmls != NULL) {
1067 err = makeFileResources(bundle, assets, &table, xmls, "xml");
1068 if (err != NO_ERROR) {
1069 hasErrors = true;
1070 }
1071 }
1072
1073 if (raws != NULL) {
1074 err = makeFileResources(bundle, assets, &table, raws, "raw");
1075 if (err != NO_ERROR) {
1076 hasErrors = true;
1077 }
1078 }
1079
1080 // compile resources
1081 current = assets;
1082 while(current.get()) {
1083 KeyedVector<String8, sp<ResourceTypeSet> > *resources =
1084 current->getResources();
1085
1086 ssize_t index = resources->indexOfKey(String8("values"));
1087 if (index >= 0) {
1088 ResourceDirIterator it(resources->valueAt(index), String8("values"));
1089 ssize_t res;
1090 while ((res=it.next()) == NO_ERROR) {
1091 sp<AaptFile> file = it.getFile();
1092 res = compileResourceFile(bundle, assets, file, it.getParams(),
1093 (current!=assets), &table);
1094 if (res != NO_ERROR) {
1095 hasErrors = true;
1096 }
1097 }
1098 }
1099 current = current->getOverlay();
1100 }
1101
1102 if (colors != NULL) {
1103 err = makeFileResources(bundle, assets, &table, colors, "color");
1104 if (err != NO_ERROR) {
1105 hasErrors = true;
1106 }
1107 }
1108
1109 if (menus != NULL) {
1110 err = makeFileResources(bundle, assets, &table, menus, "menu");
1111 if (err != NO_ERROR) {
1112 hasErrors = true;
1113 }
1114 }
1115
1116 // --------------------------------------------------------------------
1117 // Assignment of resource IDs and initial generation of resource table.
1118 // --------------------------------------------------------------------
1119
1120 if (table.hasResources()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001121 err = table.assignResourceIds();
1122 if (err < NO_ERROR) {
1123 return err;
1124 }
1125 }
1126
1127 // --------------------------------------------------------------
1128 // Finally, we can now we can compile XML files, which may reference
1129 // resources.
1130 // --------------------------------------------------------------
1131
1132 if (layouts != NULL) {
1133 ResourceDirIterator it(layouts, String8("layout"));
1134 while ((err=it.next()) == NO_ERROR) {
1135 String8 src = it.getFile()->getPrintableSource();
1136 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
1137 if (err == NO_ERROR) {
1138 ResXMLTree block;
1139 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
1140 checkForIds(src, block);
1141 } else {
1142 hasErrors = true;
1143 }
1144 }
1145
1146 if (err < NO_ERROR) {
1147 hasErrors = true;
1148 }
1149 err = NO_ERROR;
1150 }
1151
1152 if (anims != NULL) {
1153 ResourceDirIterator it(anims, String8("anim"));
1154 while ((err=it.next()) == NO_ERROR) {
1155 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
1156 if (err != NO_ERROR) {
1157 hasErrors = true;
1158 }
1159 }
1160
1161 if (err < NO_ERROR) {
1162 hasErrors = true;
1163 }
1164 err = NO_ERROR;
1165 }
1166
1167 if (animators != NULL) {
1168 ResourceDirIterator it(animators, String8("animator"));
1169 while ((err=it.next()) == NO_ERROR) {
1170 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
1171 if (err != NO_ERROR) {
1172 hasErrors = true;
1173 }
1174 }
1175
1176 if (err < NO_ERROR) {
1177 hasErrors = true;
1178 }
1179 err = NO_ERROR;
1180 }
1181
1182 if (interpolators != NULL) {
1183 ResourceDirIterator it(interpolators, String8("interpolator"));
1184 while ((err=it.next()) == NO_ERROR) {
1185 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
1186 if (err != NO_ERROR) {
1187 hasErrors = true;
1188 }
1189 }
1190
1191 if (err < NO_ERROR) {
1192 hasErrors = true;
1193 }
1194 err = NO_ERROR;
1195 }
1196
1197 if (transitions != NULL) {
1198 ResourceDirIterator it(transitions, String8("transition"));
1199 while ((err=it.next()) == NO_ERROR) {
1200 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
1201 if (err != NO_ERROR) {
1202 hasErrors = true;
1203 }
1204 }
1205
1206 if (err < NO_ERROR) {
1207 hasErrors = true;
1208 }
1209 err = NO_ERROR;
1210 }
1211
Adam Lesinski282e1812014-01-23 18:17:42 -08001212 if (xmls != NULL) {
1213 ResourceDirIterator it(xmls, String8("xml"));
1214 while ((err=it.next()) == NO_ERROR) {
1215 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
1216 if (err != NO_ERROR) {
1217 hasErrors = true;
1218 }
1219 }
1220
1221 if (err < NO_ERROR) {
1222 hasErrors = true;
1223 }
1224 err = NO_ERROR;
1225 }
1226
1227 if (drawables != NULL) {
Adam Lesinskifab50872014-04-16 14:40:42 -07001228 ResourceDirIterator it(drawables, String8("drawable"));
1229 while ((err=it.next()) == NO_ERROR) {
1230 err = postProcessImage(assets, &table, it.getFile());
1231 if (err != NO_ERROR) {
1232 hasErrors = true;
1233 }
1234 }
1235
1236 if (err < NO_ERROR) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001237 hasErrors = true;
1238 }
Adam Lesinskifab50872014-04-16 14:40:42 -07001239 err = NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001240 }
1241
1242 if (colors != NULL) {
1243 ResourceDirIterator it(colors, String8("color"));
1244 while ((err=it.next()) == NO_ERROR) {
Adam Lesinskifab50872014-04-16 14:40:42 -07001245 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
Adam Lesinski282e1812014-01-23 18:17:42 -08001246 if (err != NO_ERROR) {
1247 hasErrors = true;
1248 }
1249 }
1250
1251 if (err < NO_ERROR) {
1252 hasErrors = true;
1253 }
1254 err = NO_ERROR;
1255 }
1256
1257 if (menus != NULL) {
1258 ResourceDirIterator it(menus, String8("menu"));
1259 while ((err=it.next()) == NO_ERROR) {
1260 String8 src = it.getFile()->getPrintableSource();
1261 err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
Adam Lesinskifab50872014-04-16 14:40:42 -07001262 if (err == NO_ERROR) {
1263 ResXMLTree block;
1264 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
1265 checkForIds(src, block);
1266 } else {
Adam Lesinski282e1812014-01-23 18:17:42 -08001267 hasErrors = true;
1268 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001269 }
1270
1271 if (err < NO_ERROR) {
1272 hasErrors = true;
1273 }
1274 err = NO_ERROR;
1275 }
1276
1277 if (table.validateLocalizations()) {
1278 hasErrors = true;
1279 }
1280
1281 if (hasErrors) {
1282 return UNKNOWN_ERROR;
1283 }
1284
1285 const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
1286 String8 manifestPath(manifestFile->getPrintableSource());
1287
1288 // Generate final compiled manifest file.
1289 manifestFile->clearData();
1290 sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
1291 if (manifestTree == NULL) {
1292 return UNKNOWN_ERROR;
1293 }
1294 err = massageManifest(bundle, manifestTree);
1295 if (err < NO_ERROR) {
1296 return err;
1297 }
1298 err = compileXmlFile(assets, manifestTree, manifestFile, &table);
1299 if (err < NO_ERROR) {
1300 return err;
1301 }
1302
1303 //block.restart();
1304 //printXMLBlock(&block);
1305
1306 // --------------------------------------------------------------
1307 // Generate the final resource table.
1308 // Re-flatten because we may have added new resource IDs
1309 // --------------------------------------------------------------
1310
1311 ResTable finalResTable;
1312 sp<AaptFile> resFile;
1313
1314 if (table.hasResources()) {
1315 sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1316 err = table.addSymbols(symbols);
1317 if (err < NO_ERROR) {
1318 return err;
1319 }
1320
Adam Lesinskifab50872014-04-16 14:40:42 -07001321 Vector<sp<ApkSplit> >& splits = builder->getSplits();
1322 const size_t numSplits = splits.size();
1323 for (size_t i = 0; i < numSplits; i++) {
1324 sp<ApkSplit>& split = splits.editItemAt(i);
1325 sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"),
1326 AaptGroupEntry(), String8());
1327 err = table.flatten(bundle, split->getResourceFilter(), flattenedTable);
1328 if (err != NO_ERROR) {
1329 fprintf(stderr, "Failed to generate resource table for split '%s'\n",
1330 split->getPrintableName().string());
1331 return err;
1332 }
1333 split->addEntry(String8("resources.arsc"), flattenedTable);
Adam Lesinski282e1812014-01-23 18:17:42 -08001334
Adam Lesinskifab50872014-04-16 14:40:42 -07001335 if (split->isBase()) {
1336 resFile = flattenedTable;
1337 finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
1338 } else {
1339 sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"),
1340 AaptGroupEntry(), String8());
1341 err = generateAndroidManifestForSplit(String16(assets->getPackage()), split,
1342 generatedManifest);
1343 if (err != NO_ERROR) {
1344 fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n",
1345 split->getPrintableName().string());
1346 return err;
1347 }
1348 split->addEntry(String8("AndroidManifest.xml"), generatedManifest);
1349 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001350 }
1351
1352 if (bundle->getPublicOutputFile()) {
1353 FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
1354 if (fp == NULL) {
1355 fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n",
1356 (const char*)bundle->getPublicOutputFile(), strerror(errno));
1357 return UNKNOWN_ERROR;
1358 }
1359 if (bundle->getVerbose()) {
1360 printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile());
1361 }
1362 table.writePublicDefinitions(String16(assets->getPackage()), fp);
1363 fclose(fp);
1364 }
Adam Lesinskifab50872014-04-16 14:40:42 -07001365
1366 if (finalResTable.getTableCount() == 0 || resFile == NULL) {
1367 fprintf(stderr, "No resource table was generated.\n");
1368 return UNKNOWN_ERROR;
1369 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001370 }
Adam Lesinskifab50872014-04-16 14:40:42 -07001371
Adam Lesinski282e1812014-01-23 18:17:42 -08001372 // Perform a basic validation of the manifest file. This time we
1373 // parse it with the comments intact, so that we can use them to
1374 // generate java docs... so we are not going to write this one
1375 // back out to the final manifest data.
1376 sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
1377 manifestFile->getGroupEntry(),
1378 manifestFile->getResourceType());
1379 err = compileXmlFile(assets, manifestFile,
1380 outManifestFile, &table,
1381 XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
1382 | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
1383 if (err < NO_ERROR) {
1384 return err;
1385 }
1386 ResXMLTree block;
1387 block.setTo(outManifestFile->getData(), outManifestFile->getSize(), true);
1388 String16 manifest16("manifest");
1389 String16 permission16("permission");
1390 String16 permission_group16("permission-group");
1391 String16 uses_permission16("uses-permission");
1392 String16 instrumentation16("instrumentation");
1393 String16 application16("application");
1394 String16 provider16("provider");
1395 String16 service16("service");
1396 String16 receiver16("receiver");
1397 String16 activity16("activity");
1398 String16 action16("action");
1399 String16 category16("category");
1400 String16 data16("scheme");
1401 const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
1402 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
1403 const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
1404 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
1405 const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz"
1406 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$";
1407 const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz"
1408 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:";
1409 const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz"
1410 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;";
1411 const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz"
1412 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+";
1413 const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz"
1414 "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
1415 ResXMLTree::event_code_t code;
1416 sp<AaptSymbols> permissionSymbols;
1417 sp<AaptSymbols> permissionGroupSymbols;
1418 while ((code=block.next()) != ResXMLTree::END_DOCUMENT
1419 && code > ResXMLTree::BAD_DOCUMENT) {
1420 if (code == ResXMLTree::START_TAG) {
1421 size_t len;
1422 if (block.getElementNamespace(&len) != NULL) {
1423 continue;
1424 }
1425 if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
1426 if (validateAttr(manifestPath, finalResTable, block, NULL, "package",
1427 packageIdentChars, true) != ATTR_OKAY) {
1428 hasErrors = true;
1429 }
1430 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1431 "sharedUserId", packageIdentChars, false) != ATTR_OKAY) {
1432 hasErrors = true;
1433 }
1434 } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0
1435 || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) {
1436 const bool isGroup = strcmp16(block.getElementName(&len),
1437 permission_group16.string()) == 0;
1438 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1439 "name", isGroup ? packageIdentCharsWithTheStupid
1440 : packageIdentChars, true) != ATTR_OKAY) {
1441 hasErrors = true;
1442 }
1443 SourcePos srcPos(manifestPath, block.getLineNumber());
1444 sp<AaptSymbols> syms;
1445 if (!isGroup) {
1446 syms = permissionSymbols;
1447 if (syms == NULL) {
1448 sp<AaptSymbols> symbols =
1449 assets->getSymbolsFor(String8("Manifest"));
1450 syms = permissionSymbols = symbols->addNestedSymbol(
1451 String8("permission"), srcPos);
1452 }
1453 } else {
1454 syms = permissionGroupSymbols;
1455 if (syms == NULL) {
1456 sp<AaptSymbols> symbols =
1457 assets->getSymbolsFor(String8("Manifest"));
1458 syms = permissionGroupSymbols = symbols->addNestedSymbol(
1459 String8("permission_group"), srcPos);
1460 }
1461 }
1462 size_t len;
1463 ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name");
1464 const uint16_t* id = block.getAttributeStringValue(index, &len);
1465 if (id == NULL) {
1466 fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n",
1467 manifestPath.string(), block.getLineNumber(),
1468 String8(block.getElementName(&len)).string());
1469 hasErrors = true;
1470 break;
1471 }
1472 String8 idStr(id);
1473 char* p = idStr.lockBuffer(idStr.size());
1474 char* e = p + idStr.size();
1475 bool begins_with_digit = true; // init to true so an empty string fails
1476 while (e > p) {
1477 e--;
1478 if (*e >= '0' && *e <= '9') {
1479 begins_with_digit = true;
1480 continue;
1481 }
1482 if ((*e >= 'a' && *e <= 'z') ||
1483 (*e >= 'A' && *e <= 'Z') ||
1484 (*e == '_')) {
1485 begins_with_digit = false;
1486 continue;
1487 }
1488 if (isGroup && (*e == '-')) {
1489 *e = '_';
1490 begins_with_digit = false;
1491 continue;
1492 }
1493 e++;
1494 break;
1495 }
1496 idStr.unlockBuffer();
1497 // verify that we stopped because we hit a period or
1498 // the beginning of the string, and that the
1499 // identifier didn't begin with a digit.
1500 if (begins_with_digit || (e != p && *(e-1) != '.')) {
1501 fprintf(stderr,
1502 "%s:%d: Permission name <%s> is not a valid Java symbol\n",
1503 manifestPath.string(), block.getLineNumber(), idStr.string());
1504 hasErrors = true;
1505 }
1506 syms->addStringSymbol(String8(e), idStr, srcPos);
1507 const uint16_t* cmt = block.getComment(&len);
1508 if (cmt != NULL && *cmt != 0) {
1509 //printf("Comment of %s: %s\n", String8(e).string(),
1510 // String8(cmt).string());
1511 syms->appendComment(String8(e), String16(cmt), srcPos);
1512 } else {
1513 //printf("No comment for %s\n", String8(e).string());
1514 }
1515 syms->makeSymbolPublic(String8(e), srcPos);
1516 } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
1517 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1518 "name", packageIdentChars, true) != ATTR_OKAY) {
1519 hasErrors = true;
1520 }
1521 } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) {
1522 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1523 "name", classIdentChars, true) != ATTR_OKAY) {
1524 hasErrors = true;
1525 }
1526 if (validateAttr(manifestPath, finalResTable, block,
1527 RESOURCES_ANDROID_NAMESPACE, "targetPackage",
1528 packageIdentChars, true) != ATTR_OKAY) {
1529 hasErrors = true;
1530 }
1531 } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) {
1532 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1533 "name", classIdentChars, false) != ATTR_OKAY) {
1534 hasErrors = true;
1535 }
1536 if (validateAttr(manifestPath, finalResTable, block,
1537 RESOURCES_ANDROID_NAMESPACE, "permission",
1538 packageIdentChars, false) != ATTR_OKAY) {
1539 hasErrors = true;
1540 }
1541 if (validateAttr(manifestPath, finalResTable, block,
1542 RESOURCES_ANDROID_NAMESPACE, "process",
1543 processIdentChars, false) != ATTR_OKAY) {
1544 hasErrors = true;
1545 }
1546 if (validateAttr(manifestPath, finalResTable, block,
1547 RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
1548 processIdentChars, false) != ATTR_OKAY) {
1549 hasErrors = true;
1550 }
1551 } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) {
1552 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1553 "name", classIdentChars, true) != ATTR_OKAY) {
1554 hasErrors = true;
1555 }
1556 if (validateAttr(manifestPath, finalResTable, block,
1557 RESOURCES_ANDROID_NAMESPACE, "authorities",
1558 authoritiesIdentChars, true) != ATTR_OKAY) {
1559 hasErrors = true;
1560 }
1561 if (validateAttr(manifestPath, finalResTable, block,
1562 RESOURCES_ANDROID_NAMESPACE, "permission",
1563 packageIdentChars, false) != ATTR_OKAY) {
1564 hasErrors = true;
1565 }
1566 if (validateAttr(manifestPath, finalResTable, block,
1567 RESOURCES_ANDROID_NAMESPACE, "process",
1568 processIdentChars, false) != ATTR_OKAY) {
1569 hasErrors = true;
1570 }
1571 } else if (strcmp16(block.getElementName(&len), service16.string()) == 0
1572 || strcmp16(block.getElementName(&len), receiver16.string()) == 0
1573 || strcmp16(block.getElementName(&len), activity16.string()) == 0) {
1574 if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
1575 "name", classIdentChars, true) != ATTR_OKAY) {
1576 hasErrors = true;
1577 }
1578 if (validateAttr(manifestPath, finalResTable, block,
1579 RESOURCES_ANDROID_NAMESPACE, "permission",
1580 packageIdentChars, false) != ATTR_OKAY) {
1581 hasErrors = true;
1582 }
1583 if (validateAttr(manifestPath, finalResTable, block,
1584 RESOURCES_ANDROID_NAMESPACE, "process",
1585 processIdentChars, false) != ATTR_OKAY) {
1586 hasErrors = true;
1587 }
1588 if (validateAttr(manifestPath, finalResTable, block,
1589 RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
1590 processIdentChars, false) != ATTR_OKAY) {
1591 hasErrors = true;
1592 }
1593 } else if (strcmp16(block.getElementName(&len), action16.string()) == 0
1594 || strcmp16(block.getElementName(&len), category16.string()) == 0) {
1595 if (validateAttr(manifestPath, finalResTable, block,
1596 RESOURCES_ANDROID_NAMESPACE, "name",
1597 packageIdentChars, true) != ATTR_OKAY) {
1598 hasErrors = true;
1599 }
1600 } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) {
1601 if (validateAttr(manifestPath, finalResTable, block,
1602 RESOURCES_ANDROID_NAMESPACE, "mimeType",
1603 typeIdentChars, true) != ATTR_OKAY) {
1604 hasErrors = true;
1605 }
1606 if (validateAttr(manifestPath, finalResTable, block,
1607 RESOURCES_ANDROID_NAMESPACE, "scheme",
1608 schemeIdentChars, true) != ATTR_OKAY) {
1609 hasErrors = true;
1610 }
1611 }
1612 }
1613 }
1614
1615 if (resFile != NULL) {
1616 // These resources are now considered to be a part of the included
1617 // resources, for others to reference.
1618 err = assets->addIncludedResources(resFile);
1619 if (err < NO_ERROR) {
1620 fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
1621 return err;
1622 }
1623 }
1624
1625 return err;
1626}
1627
1628static const char* getIndentSpace(int indent)
1629{
1630static const char whitespace[] =
1631" ";
1632
1633 return whitespace + sizeof(whitespace) - 1 - indent*4;
1634}
1635
1636static String8 flattenSymbol(const String8& symbol) {
1637 String8 result(symbol);
1638 ssize_t first;
1639 if ((first = symbol.find(":", 0)) >= 0
1640 || (first = symbol.find(".", 0)) >= 0) {
1641 size_t size = symbol.size();
1642 char* buf = result.lockBuffer(size);
1643 for (size_t i = first; i < size; i++) {
1644 if (buf[i] == ':' || buf[i] == '.') {
1645 buf[i] = '_';
1646 }
1647 }
1648 result.unlockBuffer(size);
1649 }
1650 return result;
1651}
1652
1653static String8 getSymbolPackage(const String8& symbol, const sp<AaptAssets>& assets, bool pub) {
1654 ssize_t colon = symbol.find(":", 0);
1655 if (colon >= 0) {
1656 return String8(symbol.string(), colon);
1657 }
1658 return pub ? assets->getPackage() : assets->getSymbolsPrivatePackage();
1659}
1660
1661static String8 getSymbolName(const String8& symbol) {
1662 ssize_t colon = symbol.find(":", 0);
1663 if (colon >= 0) {
1664 return String8(symbol.string() + colon + 1);
1665 }
1666 return symbol;
1667}
1668
1669static String16 getAttributeComment(const sp<AaptAssets>& assets,
1670 const String8& name,
1671 String16* outTypeComment = NULL)
1672{
1673 sp<AaptSymbols> asym = assets->getSymbolsFor(String8("R"));
1674 if (asym != NULL) {
1675 //printf("Got R symbols!\n");
1676 asym = asym->getNestedSymbols().valueFor(String8("attr"));
1677 if (asym != NULL) {
1678 //printf("Got attrs symbols! comment %s=%s\n",
1679 // name.string(), String8(asym->getComment(name)).string());
1680 if (outTypeComment != NULL) {
1681 *outTypeComment = asym->getTypeComment(name);
1682 }
1683 return asym->getComment(name);
1684 }
1685 }
1686 return String16();
1687}
1688
1689static status_t writeLayoutClasses(
1690 FILE* fp, const sp<AaptAssets>& assets,
1691 const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
1692{
1693 const char* indentStr = getIndentSpace(indent);
1694 if (!includePrivate) {
1695 fprintf(fp, "%s/** @doconly */\n", indentStr);
1696 }
1697 fprintf(fp, "%spublic static final class styleable {\n", indentStr);
1698 indent++;
1699
1700 String16 attr16("attr");
1701 String16 package16(assets->getPackage());
1702
1703 indentStr = getIndentSpace(indent);
1704 bool hasErrors = false;
1705
1706 size_t i;
1707 size_t N = symbols->getNestedSymbols().size();
1708 for (i=0; i<N; i++) {
1709 sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
1710 String8 realClassName(symbols->getNestedSymbols().keyAt(i));
1711 String8 nclassName(flattenSymbol(realClassName));
1712
1713 SortedVector<uint32_t> idents;
1714 Vector<uint32_t> origOrder;
1715 Vector<bool> publicFlags;
1716
1717 size_t a;
1718 size_t NA = nsymbols->getSymbols().size();
1719 for (a=0; a<NA; a++) {
1720 const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
1721 int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
1722 ? sym.int32Val : 0;
1723 bool isPublic = true;
1724 if (code == 0) {
1725 String16 name16(sym.name);
1726 uint32_t typeSpecFlags;
1727 code = assets->getIncludedResources().identifierForName(
1728 name16.string(), name16.size(),
1729 attr16.string(), attr16.size(),
1730 package16.string(), package16.size(), &typeSpecFlags);
1731 if (code == 0) {
1732 fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
1733 nclassName.string(), sym.name.string());
1734 hasErrors = true;
1735 }
1736 isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
1737 }
1738 idents.add(code);
1739 origOrder.add(code);
1740 publicFlags.add(isPublic);
1741 }
1742
1743 NA = idents.size();
1744
1745 bool deprecated = false;
1746
1747 String16 comment = symbols->getComment(realClassName);
1748 fprintf(fp, "%s/** ", indentStr);
1749 if (comment.size() > 0) {
1750 String8 cmt(comment);
1751 fprintf(fp, "%s\n", cmt.string());
1752 if (strstr(cmt.string(), "@deprecated") != NULL) {
1753 deprecated = true;
1754 }
1755 } else {
1756 fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string());
1757 }
1758 bool hasTable = false;
1759 for (a=0; a<NA; a++) {
1760 ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1761 if (pos >= 0) {
1762 if (!hasTable) {
1763 hasTable = true;
1764 fprintf(fp,
1765 "%s <p>Includes the following attributes:</p>\n"
1766 "%s <table>\n"
1767 "%s <colgroup align=\"left\" />\n"
1768 "%s <colgroup align=\"left\" />\n"
1769 "%s <tr><th>Attribute</th><th>Description</th></tr>\n",
1770 indentStr,
1771 indentStr,
1772 indentStr,
1773 indentStr,
1774 indentStr);
1775 }
1776 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1777 if (!publicFlags.itemAt(a) && !includePrivate) {
1778 continue;
1779 }
1780 String8 name8(sym.name);
1781 String16 comment(sym.comment);
1782 if (comment.size() <= 0) {
1783 comment = getAttributeComment(assets, name8);
1784 }
1785 if (comment.size() > 0) {
1786 const char16_t* p = comment.string();
1787 while (*p != 0 && *p != '.') {
1788 if (*p == '{') {
1789 while (*p != 0 && *p != '}') {
1790 p++;
1791 }
1792 } else {
1793 p++;
1794 }
1795 }
1796 if (*p == '.') {
1797 p++;
1798 }
1799 comment = String16(comment.string(), p-comment.string());
1800 }
1801 fprintf(fp, "%s <tr><td><code>{@link #%s_%s %s:%s}</code></td><td>%s</td></tr>\n",
1802 indentStr, nclassName.string(),
1803 flattenSymbol(name8).string(),
1804 getSymbolPackage(name8, assets, true).string(),
1805 getSymbolName(name8).string(),
1806 String8(comment).string());
1807 }
1808 }
1809 if (hasTable) {
1810 fprintf(fp, "%s </table>\n", indentStr);
1811 }
1812 for (a=0; a<NA; a++) {
1813 ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1814 if (pos >= 0) {
1815 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1816 if (!publicFlags.itemAt(a) && !includePrivate) {
1817 continue;
1818 }
1819 fprintf(fp, "%s @see #%s_%s\n",
1820 indentStr, nclassName.string(),
1821 flattenSymbol(sym.name).string());
1822 }
1823 }
1824 fprintf(fp, "%s */\n", getIndentSpace(indent));
1825
1826 if (deprecated) {
1827 fprintf(fp, "%s@Deprecated\n", indentStr);
1828 }
1829
1830 fprintf(fp,
1831 "%spublic static final int[] %s = {\n"
1832 "%s",
1833 indentStr, nclassName.string(),
1834 getIndentSpace(indent+1));
1835
1836 for (a=0; a<NA; a++) {
1837 if (a != 0) {
1838 if ((a&3) == 0) {
1839 fprintf(fp, ",\n%s", getIndentSpace(indent+1));
1840 } else {
1841 fprintf(fp, ", ");
1842 }
1843 }
1844 fprintf(fp, "0x%08x", idents[a]);
1845 }
1846
1847 fprintf(fp, "\n%s};\n", indentStr);
1848
1849 for (a=0; a<NA; a++) {
1850 ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1851 if (pos >= 0) {
1852 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1853 if (!publicFlags.itemAt(a) && !includePrivate) {
1854 continue;
1855 }
1856 String8 name8(sym.name);
1857 String16 comment(sym.comment);
1858 String16 typeComment;
1859 if (comment.size() <= 0) {
1860 comment = getAttributeComment(assets, name8, &typeComment);
1861 } else {
1862 getAttributeComment(assets, name8, &typeComment);
1863 }
1864
1865 uint32_t typeSpecFlags = 0;
1866 String16 name16(sym.name);
1867 assets->getIncludedResources().identifierForName(
1868 name16.string(), name16.size(),
1869 attr16.string(), attr16.size(),
1870 package16.string(), package16.size(), &typeSpecFlags);
1871 //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
1872 // String8(attr16).string(), String8(name16).string(), typeSpecFlags);
1873 const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
1874
1875 bool deprecated = false;
1876
1877 fprintf(fp, "%s/**\n", indentStr);
1878 if (comment.size() > 0) {
1879 String8 cmt(comment);
1880 fprintf(fp, "%s <p>\n%s @attr description\n", indentStr, indentStr);
1881 fprintf(fp, "%s %s\n", indentStr, cmt.string());
1882 if (strstr(cmt.string(), "@deprecated") != NULL) {
1883 deprecated = true;
1884 }
1885 } else {
1886 fprintf(fp,
1887 "%s <p>This symbol is the offset where the {@link %s.R.attr#%s}\n"
1888 "%s attribute's value can be found in the {@link #%s} array.\n",
1889 indentStr,
1890 getSymbolPackage(name8, assets, pub).string(),
1891 getSymbolName(name8).string(),
1892 indentStr, nclassName.string());
1893 }
1894 if (typeComment.size() > 0) {
1895 String8 cmt(typeComment);
1896 fprintf(fp, "\n\n%s %s\n", indentStr, cmt.string());
1897 if (strstr(cmt.string(), "@deprecated") != NULL) {
1898 deprecated = true;
1899 }
1900 }
1901 if (comment.size() > 0) {
1902 if (pub) {
1903 fprintf(fp,
1904 "%s <p>This corresponds to the global attribute\n"
1905 "%s resource symbol {@link %s.R.attr#%s}.\n",
1906 indentStr, indentStr,
1907 getSymbolPackage(name8, assets, true).string(),
1908 getSymbolName(name8).string());
1909 } else {
1910 fprintf(fp,
1911 "%s <p>This is a private symbol.\n", indentStr);
1912 }
1913 }
1914 fprintf(fp, "%s @attr name %s:%s\n", indentStr,
1915 getSymbolPackage(name8, assets, pub).string(),
1916 getSymbolName(name8).string());
1917 fprintf(fp, "%s*/\n", indentStr);
1918 if (deprecated) {
1919 fprintf(fp, "%s@Deprecated\n", indentStr);
1920 }
1921 fprintf(fp,
1922 "%spublic static final int %s_%s = %d;\n",
1923 indentStr, nclassName.string(),
1924 flattenSymbol(name8).string(), (int)pos);
1925 }
1926 }
1927 }
1928
1929 indent--;
1930 fprintf(fp, "%s};\n", getIndentSpace(indent));
1931 return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
1932}
1933
1934static status_t writeTextLayoutClasses(
1935 FILE* fp, const sp<AaptAssets>& assets,
1936 const sp<AaptSymbols>& symbols, bool includePrivate)
1937{
1938 String16 attr16("attr");
1939 String16 package16(assets->getPackage());
1940
1941 bool hasErrors = false;
1942
1943 size_t i;
1944 size_t N = symbols->getNestedSymbols().size();
1945 for (i=0; i<N; i++) {
1946 sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
1947 String8 realClassName(symbols->getNestedSymbols().keyAt(i));
1948 String8 nclassName(flattenSymbol(realClassName));
1949
1950 SortedVector<uint32_t> idents;
1951 Vector<uint32_t> origOrder;
1952 Vector<bool> publicFlags;
1953
1954 size_t a;
1955 size_t NA = nsymbols->getSymbols().size();
1956 for (a=0; a<NA; a++) {
1957 const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
1958 int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
1959 ? sym.int32Val : 0;
1960 bool isPublic = true;
1961 if (code == 0) {
1962 String16 name16(sym.name);
1963 uint32_t typeSpecFlags;
1964 code = assets->getIncludedResources().identifierForName(
1965 name16.string(), name16.size(),
1966 attr16.string(), attr16.size(),
1967 package16.string(), package16.size(), &typeSpecFlags);
1968 if (code == 0) {
1969 fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
1970 nclassName.string(), sym.name.string());
1971 hasErrors = true;
1972 }
1973 isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
1974 }
1975 idents.add(code);
1976 origOrder.add(code);
1977 publicFlags.add(isPublic);
1978 }
1979
1980 NA = idents.size();
1981
1982 fprintf(fp, "int[] styleable %s {", nclassName.string());
1983
1984 for (a=0; a<NA; a++) {
1985 if (a != 0) {
1986 fprintf(fp, ",");
1987 }
1988 fprintf(fp, " 0x%08x", idents[a]);
1989 }
1990
1991 fprintf(fp, " }\n");
1992
1993 for (a=0; a<NA; a++) {
1994 ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1995 if (pos >= 0) {
1996 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1997 if (!publicFlags.itemAt(a) && !includePrivate) {
1998 continue;
1999 }
2000 String8 name8(sym.name);
2001 String16 comment(sym.comment);
2002 String16 typeComment;
2003 if (comment.size() <= 0) {
2004 comment = getAttributeComment(assets, name8, &typeComment);
2005 } else {
2006 getAttributeComment(assets, name8, &typeComment);
2007 }
2008
2009 uint32_t typeSpecFlags = 0;
2010 String16 name16(sym.name);
2011 assets->getIncludedResources().identifierForName(
2012 name16.string(), name16.size(),
2013 attr16.string(), attr16.size(),
2014 package16.string(), package16.size(), &typeSpecFlags);
2015 //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
2016 // String8(attr16).string(), String8(name16).string(), typeSpecFlags);
2017 const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
2018
2019 fprintf(fp,
2020 "int styleable %s_%s %d\n",
2021 nclassName.string(),
2022 flattenSymbol(name8).string(), (int)pos);
2023 }
2024 }
2025 }
2026
2027 return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
2028}
2029
2030static status_t writeSymbolClass(
2031 FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
2032 const sp<AaptSymbols>& symbols, const String8& className, int indent,
2033 bool nonConstantId)
2034{
2035 fprintf(fp, "%spublic %sfinal class %s {\n",
2036 getIndentSpace(indent),
2037 indent != 0 ? "static " : "", className.string());
2038 indent++;
2039
2040 size_t i;
2041 status_t err = NO_ERROR;
2042
2043 const char * id_format = nonConstantId ?
2044 "%spublic static int %s=0x%08x;\n" :
2045 "%spublic static final int %s=0x%08x;\n";
2046
2047 size_t N = symbols->getSymbols().size();
2048 for (i=0; i<N; i++) {
2049 const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
2050 if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
2051 continue;
2052 }
2053 if (!assets->isJavaSymbol(sym, includePrivate)) {
2054 continue;
2055 }
2056 String8 name8(sym.name);
2057 String16 comment(sym.comment);
2058 bool haveComment = false;
2059 bool deprecated = false;
2060 if (comment.size() > 0) {
2061 haveComment = true;
2062 String8 cmt(comment);
2063 fprintf(fp,
2064 "%s/** %s\n",
2065 getIndentSpace(indent), cmt.string());
2066 if (strstr(cmt.string(), "@deprecated") != NULL) {
2067 deprecated = true;
2068 }
2069 } else if (sym.isPublic && !includePrivate) {
2070 sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
2071 assets->getPackage().string(), className.string(),
2072 String8(sym.name).string());
2073 }
2074 String16 typeComment(sym.typeComment);
2075 if (typeComment.size() > 0) {
2076 String8 cmt(typeComment);
2077 if (!haveComment) {
2078 haveComment = true;
2079 fprintf(fp,
2080 "%s/** %s\n", getIndentSpace(indent), cmt.string());
2081 } else {
2082 fprintf(fp,
2083 "%s %s\n", getIndentSpace(indent), cmt.string());
2084 }
2085 if (strstr(cmt.string(), "@deprecated") != NULL) {
2086 deprecated = true;
2087 }
2088 }
2089 if (haveComment) {
2090 fprintf(fp,"%s */\n", getIndentSpace(indent));
2091 }
2092 if (deprecated) {
2093 fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
2094 }
2095 fprintf(fp, id_format,
2096 getIndentSpace(indent),
2097 flattenSymbol(name8).string(), (int)sym.int32Val);
2098 }
2099
2100 for (i=0; i<N; i++) {
2101 const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
2102 if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) {
2103 continue;
2104 }
2105 if (!assets->isJavaSymbol(sym, includePrivate)) {
2106 continue;
2107 }
2108 String8 name8(sym.name);
2109 String16 comment(sym.comment);
2110 bool deprecated = false;
2111 if (comment.size() > 0) {
2112 String8 cmt(comment);
2113 fprintf(fp,
2114 "%s/** %s\n"
2115 "%s */\n",
2116 getIndentSpace(indent), cmt.string(),
2117 getIndentSpace(indent));
2118 if (strstr(cmt.string(), "@deprecated") != NULL) {
2119 deprecated = true;
2120 }
2121 } else if (sym.isPublic && !includePrivate) {
2122 sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
2123 assets->getPackage().string(), className.string(),
2124 String8(sym.name).string());
2125 }
2126 if (deprecated) {
2127 fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
2128 }
2129 fprintf(fp, "%spublic static final String %s=\"%s\";\n",
2130 getIndentSpace(indent),
2131 flattenSymbol(name8).string(), sym.stringVal.string());
2132 }
2133
2134 sp<AaptSymbols> styleableSymbols;
2135
2136 N = symbols->getNestedSymbols().size();
2137 for (i=0; i<N; i++) {
2138 sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
2139 String8 nclassName(symbols->getNestedSymbols().keyAt(i));
2140 if (nclassName == "styleable") {
2141 styleableSymbols = nsymbols;
2142 } else {
2143 err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId);
2144 }
2145 if (err != NO_ERROR) {
2146 return err;
2147 }
2148 }
2149
2150 if (styleableSymbols != NULL) {
2151 err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
2152 if (err != NO_ERROR) {
2153 return err;
2154 }
2155 }
2156
2157 indent--;
2158 fprintf(fp, "%s}\n", getIndentSpace(indent));
2159 return NO_ERROR;
2160}
2161
2162static status_t writeTextSymbolClass(
2163 FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
2164 const sp<AaptSymbols>& symbols, const String8& className)
2165{
2166 size_t i;
2167 status_t err = NO_ERROR;
2168
2169 size_t N = symbols->getSymbols().size();
2170 for (i=0; i<N; i++) {
2171 const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
2172 if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
2173 continue;
2174 }
2175
2176 if (!assets->isJavaSymbol(sym, includePrivate)) {
2177 continue;
2178 }
2179
2180 String8 name8(sym.name);
2181 fprintf(fp, "int %s %s 0x%08x\n",
2182 className.string(),
2183 flattenSymbol(name8).string(), (int)sym.int32Val);
2184 }
2185
2186 N = symbols->getNestedSymbols().size();
2187 for (i=0; i<N; i++) {
2188 sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
2189 String8 nclassName(symbols->getNestedSymbols().keyAt(i));
2190 if (nclassName == "styleable") {
2191 err = writeTextLayoutClasses(fp, assets, nsymbols, includePrivate);
2192 } else {
2193 err = writeTextSymbolClass(fp, assets, includePrivate, nsymbols, nclassName);
2194 }
2195 if (err != NO_ERROR) {
2196 return err;
2197 }
2198 }
2199
2200 return NO_ERROR;
2201}
2202
2203status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
2204 const String8& package, bool includePrivate)
2205{
2206 if (!bundle->getRClassDir()) {
2207 return NO_ERROR;
2208 }
2209
2210 const char* textSymbolsDest = bundle->getOutputTextSymbols();
2211
2212 String8 R("R");
2213 const size_t N = assets->getSymbols().size();
2214 for (size_t i=0; i<N; i++) {
2215 sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i);
2216 String8 className(assets->getSymbols().keyAt(i));
2217 String8 dest(bundle->getRClassDir());
2218
2219 if (bundle->getMakePackageDirs()) {
2220 String8 pkg(package);
2221 const char* last = pkg.string();
2222 const char* s = last-1;
2223 do {
2224 s++;
2225 if (s > last && (*s == '.' || *s == 0)) {
2226 String8 part(last, s-last);
2227 dest.appendPath(part);
2228#ifdef HAVE_MS_C_RUNTIME
2229 _mkdir(dest.string());
2230#else
2231 mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
2232#endif
2233 last = s+1;
2234 }
2235 } while (*s);
2236 }
2237 dest.appendPath(className);
2238 dest.append(".java");
2239 FILE* fp = fopen(dest.string(), "w+");
2240 if (fp == NULL) {
2241 fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
2242 dest.string(), strerror(errno));
2243 return UNKNOWN_ERROR;
2244 }
2245 if (bundle->getVerbose()) {
2246 printf(" Writing symbols for class %s.\n", className.string());
2247 }
2248
2249 fprintf(fp,
2250 "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
2251 " *\n"
2252 " * This class was automatically generated by the\n"
2253 " * aapt tool from the resource data it found. It\n"
2254 " * should not be modified by hand.\n"
2255 " */\n"
2256 "\n"
2257 "package %s;\n\n", package.string());
2258
2259 status_t err = writeSymbolClass(fp, assets, includePrivate, symbols,
2260 className, 0, bundle->getNonConstantId());
Elliott Hughesb30296b2013-10-29 15:25:52 -07002261 fclose(fp);
Adam Lesinski282e1812014-01-23 18:17:42 -08002262 if (err != NO_ERROR) {
2263 return err;
2264 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002265
2266 if (textSymbolsDest != NULL && R == className) {
2267 String8 textDest(textSymbolsDest);
2268 textDest.appendPath(className);
2269 textDest.append(".txt");
2270
2271 FILE* fp = fopen(textDest.string(), "w+");
2272 if (fp == NULL) {
2273 fprintf(stderr, "ERROR: Unable to open text symbol file %s: %s\n",
2274 textDest.string(), strerror(errno));
2275 return UNKNOWN_ERROR;
2276 }
2277 if (bundle->getVerbose()) {
2278 printf(" Writing text symbols for class %s.\n", className.string());
2279 }
2280
2281 status_t err = writeTextSymbolClass(fp, assets, includePrivate, symbols,
2282 className);
Elliott Hughesb30296b2013-10-29 15:25:52 -07002283 fclose(fp);
Adam Lesinski282e1812014-01-23 18:17:42 -08002284 if (err != NO_ERROR) {
2285 return err;
2286 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002287 }
2288
2289 // If we were asked to generate a dependency file, we'll go ahead and add this R.java
2290 // as a target in the dependency file right next to it.
2291 if (bundle->getGenDependencies() && R == className) {
2292 // Add this R.java to the dependency file
2293 String8 dependencyFile(bundle->getRClassDir());
2294 dependencyFile.appendPath("R.java.d");
2295
2296 FILE *fp = fopen(dependencyFile.string(), "a");
2297 fprintf(fp,"%s \\\n", dest.string());
2298 fclose(fp);
2299 }
2300 }
2301
2302 return NO_ERROR;
2303}
2304
2305
2306class ProguardKeepSet
2307{
2308public:
2309 // { rule --> { file locations } }
2310 KeyedVector<String8, SortedVector<String8> > rules;
2311
2312 void add(const String8& rule, const String8& where);
2313};
2314
2315void ProguardKeepSet::add(const String8& rule, const String8& where)
2316{
2317 ssize_t index = rules.indexOfKey(rule);
2318 if (index < 0) {
2319 index = rules.add(rule, SortedVector<String8>());
2320 }
2321 rules.editValueAt(index).add(where);
2322}
2323
2324void
2325addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName,
2326 const char* pkg, const String8& srcName, int line)
2327{
2328 String8 className(inClassName);
2329 if (pkg != NULL) {
2330 // asdf --> package.asdf
2331 // .asdf .a.b --> package.asdf package.a.b
2332 // asdf.adsf --> asdf.asdf
2333 const char* p = className.string();
2334 const char* q = strchr(p, '.');
2335 if (p == q) {
2336 className = pkg;
2337 className.append(inClassName);
2338 } else if (q == NULL) {
2339 className = pkg;
2340 className.append(".");
2341 className.append(inClassName);
2342 }
2343 }
2344
2345 String8 rule("-keep class ");
2346 rule += className;
2347 rule += " { <init>(...); }";
2348
2349 String8 location("view ");
2350 location += srcName;
2351 char lineno[20];
2352 sprintf(lineno, ":%d", line);
2353 location += lineno;
2354
2355 keep->add(rule, location);
2356}
2357
2358void
2359addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
2360 const char* pkg, const String8& srcName, int line)
2361{
2362 String8 rule("-keepclassmembers class * { *** ");
2363 rule += memberName;
2364 rule += "(...); }";
2365
2366 String8 location("onClick ");
2367 location += srcName;
2368 char lineno[20];
2369 sprintf(lineno, ":%d", line);
2370 location += lineno;
2371
2372 keep->add(rule, location);
2373}
2374
2375status_t
2376writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
2377{
2378 status_t err;
2379 ResXMLTree tree;
2380 size_t len;
2381 ResXMLTree::event_code_t code;
2382 int depth = 0;
2383 bool inApplication = false;
2384 String8 error;
2385 sp<AaptGroup> assGroup;
2386 sp<AaptFile> assFile;
2387 String8 pkg;
2388
2389 // First, look for a package file to parse. This is required to
2390 // be able to generate the resource information.
2391 assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml"));
2392 if (assGroup == NULL) {
2393 fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
2394 return -1;
2395 }
2396
2397 if (assGroup->getFiles().size() != 1) {
2398 fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
2399 assGroup->getFiles().valueAt(0)->getPrintableSource().string());
2400 }
2401
2402 assFile = assGroup->getFiles().valueAt(0);
2403
2404 err = parseXMLResource(assFile, &tree);
2405 if (err != NO_ERROR) {
2406 return err;
2407 }
2408
2409 tree.restart();
2410
2411 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
2412 if (code == ResXMLTree::END_TAG) {
2413 if (/* name == "Application" && */ depth == 2) {
2414 inApplication = false;
2415 }
2416 depth--;
2417 continue;
2418 }
2419 if (code != ResXMLTree::START_TAG) {
2420 continue;
2421 }
2422 depth++;
2423 String8 tag(tree.getElementName(&len));
2424 // printf("Depth %d tag %s\n", depth, tag.string());
2425 bool keepTag = false;
2426 if (depth == 1) {
2427 if (tag != "manifest") {
2428 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
2429 return -1;
2430 }
2431 pkg = getAttribute(tree, NULL, "package", NULL);
2432 } else if (depth == 2) {
2433 if (tag == "application") {
2434 inApplication = true;
2435 keepTag = true;
2436
2437 String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android",
2438 "backupAgent", &error);
2439 if (agent.length() > 0) {
2440 addProguardKeepRule(keep, agent, pkg.string(),
2441 assFile->getPrintableSource(), tree.getLineNumber());
2442 }
2443 } else if (tag == "instrumentation") {
2444 keepTag = true;
2445 }
2446 }
2447 if (!keepTag && inApplication && depth == 3) {
2448 if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
2449 keepTag = true;
2450 }
2451 }
2452 if (keepTag) {
2453 String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
2454 "name", &error);
2455 if (error != "") {
2456 fprintf(stderr, "ERROR: %s\n", error.string());
2457 return -1;
2458 }
2459 if (name.length() > 0) {
2460 addProguardKeepRule(keep, name, pkg.string(),
2461 assFile->getPrintableSource(), tree.getLineNumber());
2462 }
2463 }
2464 }
2465
2466 return NO_ERROR;
2467}
2468
2469struct NamespaceAttributePair {
2470 const char* ns;
2471 const char* attr;
2472
2473 NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {}
2474 NamespaceAttributePair() : ns(NULL), attr(NULL) {}
2475};
2476
2477status_t
2478writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile,
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002479 const Vector<String8>& startTags, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs)
Adam Lesinski282e1812014-01-23 18:17:42 -08002480{
2481 status_t err;
2482 ResXMLTree tree;
2483 size_t len;
2484 ResXMLTree::event_code_t code;
2485
2486 err = parseXMLResource(layoutFile, &tree);
2487 if (err != NO_ERROR) {
2488 return err;
2489 }
2490
2491 tree.restart();
2492
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002493 if (!startTags.isEmpty()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08002494 bool haveStart = false;
2495 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
2496 if (code != ResXMLTree::START_TAG) {
2497 continue;
2498 }
2499 String8 tag(tree.getElementName(&len));
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002500 const size_t numStartTags = startTags.size();
2501 for (size_t i = 0; i < numStartTags; i++) {
2502 if (tag == startTags[i]) {
2503 haveStart = true;
2504 }
Adam Lesinski282e1812014-01-23 18:17:42 -08002505 }
2506 break;
2507 }
2508 if (!haveStart) {
2509 return NO_ERROR;
2510 }
2511 }
2512
2513 while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
2514 if (code != ResXMLTree::START_TAG) {
2515 continue;
2516 }
2517 String8 tag(tree.getElementName(&len));
2518
2519 // If there is no '.', we'll assume that it's one of the built in names.
2520 if (strchr(tag.string(), '.')) {
2521 addProguardKeepRule(keep, tag, NULL,
2522 layoutFile->getPrintableSource(), tree.getLineNumber());
2523 } else if (tagAttrPairs != NULL) {
2524 ssize_t tagIndex = tagAttrPairs->indexOfKey(tag);
2525 if (tagIndex >= 0) {
2526 const Vector<NamespaceAttributePair>& nsAttrVector = tagAttrPairs->valueAt(tagIndex);
2527 for (size_t i = 0; i < nsAttrVector.size(); i++) {
2528 const NamespaceAttributePair& nsAttr = nsAttrVector[i];
2529
2530 ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr);
2531 if (attrIndex < 0) {
2532 // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n",
2533 // layoutFile->getPrintableSource().string(), tree.getLineNumber(),
2534 // tag.string(), nsAttr.ns, nsAttr.attr);
2535 } else {
2536 size_t len;
2537 addProguardKeepRule(keep,
2538 String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
2539 layoutFile->getPrintableSource(), tree.getLineNumber());
2540 }
2541 }
2542 }
2543 }
2544 ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick");
2545 if (attrIndex >= 0) {
2546 size_t len;
2547 addProguardKeepMethodRule(keep,
2548 String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
2549 layoutFile->getPrintableSource(), tree.getLineNumber());
2550 }
2551 }
2552
2553 return NO_ERROR;
2554}
2555
2556static void addTagAttrPair(KeyedVector<String8, Vector<NamespaceAttributePair> >* dest,
2557 const char* tag, const char* ns, const char* attr) {
2558 String8 tagStr(tag);
2559 ssize_t index = dest->indexOfKey(tagStr);
2560
2561 if (index < 0) {
2562 Vector<NamespaceAttributePair> vector;
2563 vector.add(NamespaceAttributePair(ns, attr));
2564 dest->add(tagStr, vector);
2565 } else {
2566 dest->editValueAt(index).add(NamespaceAttributePair(ns, attr));
2567 }
2568}
2569
2570status_t
2571writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
2572{
2573 status_t err;
2574
2575 // tag:attribute pairs that should be checked in layout files.
2576 KeyedVector<String8, Vector<NamespaceAttributePair> > kLayoutTagAttrPairs;
2577 addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class");
2578 addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class");
2579 addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name");
2580
2581 // tag:attribute pairs that should be checked in xml files.
2582 KeyedVector<String8, Vector<NamespaceAttributePair> > kXmlTagAttrPairs;
2583 addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment");
2584 addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment");
2585
2586 const Vector<sp<AaptDir> >& dirs = assets->resDirs();
2587 const size_t K = dirs.size();
2588 for (size_t k=0; k<K; k++) {
2589 const sp<AaptDir>& d = dirs.itemAt(k);
2590 const String8& dirName = d->getLeaf();
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002591 Vector<String8> startTags;
Adam Lesinski282e1812014-01-23 18:17:42 -08002592 const char* startTag = NULL;
2593 const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs = NULL;
2594 if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) {
2595 tagAttrPairs = &kLayoutTagAttrPairs;
2596 } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002597 startTags.add(String8("PreferenceScreen"));
2598 startTags.add(String8("preference-headers"));
Adam Lesinski282e1812014-01-23 18:17:42 -08002599 tagAttrPairs = &kXmlTagAttrPairs;
2600 } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002601 startTags.add(String8("menu"));
Adam Lesinski282e1812014-01-23 18:17:42 -08002602 tagAttrPairs = NULL;
2603 } else {
2604 continue;
2605 }
2606
2607 const KeyedVector<String8,sp<AaptGroup> > groups = d->getFiles();
2608 const size_t N = groups.size();
2609 for (size_t i=0; i<N; i++) {
2610 const sp<AaptGroup>& group = groups.valueAt(i);
2611 const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles();
2612 const size_t M = files.size();
2613 for (size_t j=0; j<M; j++) {
Adam Lesinski9cf4b4a2014-04-25 11:36:02 -07002614 err = writeProguardForXml(keep, files.valueAt(j), startTags, tagAttrPairs);
Adam Lesinski282e1812014-01-23 18:17:42 -08002615 if (err < 0) {
2616 return err;
2617 }
2618 }
2619 }
2620 }
2621 // Handle the overlays
2622 sp<AaptAssets> overlay = assets->getOverlay();
2623 if (overlay.get()) {
2624 return writeProguardForLayouts(keep, overlay);
2625 }
2626
2627 return NO_ERROR;
2628}
2629
2630status_t
2631writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
2632{
2633 status_t err = -1;
2634
2635 if (!bundle->getProguardFile()) {
2636 return NO_ERROR;
2637 }
2638
2639 ProguardKeepSet keep;
2640
2641 err = writeProguardForAndroidManifest(&keep, assets);
2642 if (err < 0) {
2643 return err;
2644 }
2645
2646 err = writeProguardForLayouts(&keep, assets);
2647 if (err < 0) {
2648 return err;
2649 }
2650
2651 FILE* fp = fopen(bundle->getProguardFile(), "w+");
2652 if (fp == NULL) {
2653 fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
2654 bundle->getProguardFile(), strerror(errno));
2655 return UNKNOWN_ERROR;
2656 }
2657
2658 const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
2659 const size_t N = rules.size();
2660 for (size_t i=0; i<N; i++) {
2661 const SortedVector<String8>& locations = rules.valueAt(i);
2662 const size_t M = locations.size();
2663 for (size_t j=0; j<M; j++) {
2664 fprintf(fp, "# %s\n", locations.itemAt(j).string());
2665 }
2666 fprintf(fp, "%s\n\n", rules.keyAt(i).string());
2667 }
2668 fclose(fp);
2669
2670 return err;
2671}
2672
2673// Loops through the string paths and writes them to the file pointer
2674// Each file path is written on its own line with a terminating backslash.
2675status_t writePathsToFile(const sp<FilePathStore>& files, FILE* fp)
2676{
2677 status_t deps = -1;
2678 for (size_t file_i = 0; file_i < files->size(); ++file_i) {
2679 // Add the full file path to the dependency file
2680 fprintf(fp, "%s \\\n", files->itemAt(file_i).string());
2681 deps++;
2682 }
2683 return deps;
2684}
2685
2686status_t
2687writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, FILE* fp, bool includeRaw)
2688{
2689 status_t deps = -1;
2690 deps += writePathsToFile(assets->getFullResPaths(), fp);
2691 if (includeRaw) {
2692 deps += writePathsToFile(assets->getFullAssetPaths(), fp);
2693 }
2694 return deps;
2695}