blob: 13ae24b7d29eb09309634858aa15f4efa13d55af [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001//
2// Copyright 2006 The Android Open Source Project
3//
4
5#include "AaptAssets.h"
Adam Lesinskifab50872014-04-16 14:40:42 -07006#include "AaptConfig.h"
7#include "AaptUtil.h"
Adam Lesinski282e1812014-01-23 18:17:42 -08008#include "Main.h"
Adam Lesinskifab50872014-04-16 14:40:42 -07009#include "ResourceFilter.h"
Adam Lesinski282e1812014-01-23 18:17:42 -080010
11#include <utils/misc.h>
12#include <utils/SortedVector.h>
13
14#include <ctype.h>
15#include <dirent.h>
16#include <errno.h>
17
Adam Lesinski282e1812014-01-23 18:17:42 -080018static const char* kAssetDir = "assets";
19static const char* kResourceDir = "res";
20static const char* kValuesDir = "values";
21static const char* kMipmapDir = "mipmap";
22static const char* kInvalidChars = "/\\:";
23static const size_t kMaxAssetFileName = 100;
24
25static const String8 kResString(kResourceDir);
26
27/*
28 * Names of asset files must meet the following criteria:
29 *
30 * - the filename length must be less than kMaxAssetFileName bytes long
31 * (and can't be empty)
32 * - all characters must be 7-bit printable ASCII
33 * - none of { '/' '\\' ':' }
34 *
35 * Pass in just the filename, not the full path.
36 */
37static bool validateFileName(const char* fileName)
38{
39 const char* cp = fileName;
40 size_t len = 0;
41
42 while (*cp != '\0') {
43 if ((*cp & 0x80) != 0)
44 return false; // reject high ASCII
45 if (*cp < 0x20 || *cp >= 0x7f)
46 return false; // reject control chars and 0x7f
47 if (strchr(kInvalidChars, *cp) != NULL)
48 return false; // reject path sep chars
49 cp++;
50 len++;
51 }
52
53 if (len < 1 || len > kMaxAssetFileName)
54 return false; // reject empty or too long
55
56 return true;
57}
58
59// The default to use if no other ignore pattern is defined.
60const char * const gDefaultIgnoreAssets =
61 "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~";
62// The ignore pattern that can be passed via --ignore-assets in Main.cpp
63const char * gUserIgnoreAssets = NULL;
64
65static bool isHidden(const char *root, const char *path)
66{
67 // Patterns syntax:
68 // - Delimiter is :
69 // - Entry can start with the flag ! to avoid printing a warning
70 // about the file being ignored.
71 // - Entry can have the flag "<dir>" to match only directories
72 // or <file> to match only files. Default is to match both.
73 // - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
74 // where prefix/suffix must have at least 1 character (so that
75 // we don't match a '*' catch-all pattern.)
76 // - The special filenames "." and ".." are always ignored.
77 // - Otherwise the full string is matched.
78 // - match is not case-sensitive.
79
80 if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
81 return true;
82 }
83
84 const char *delim = ":";
85 const char *p = gUserIgnoreAssets;
86 if (!p || !p[0]) {
87 p = getenv("ANDROID_AAPT_IGNORE");
88 }
89 if (!p || !p[0]) {
90 p = gDefaultIgnoreAssets;
91 }
92 char *patterns = strdup(p);
93
94 bool ignore = false;
95 bool chatty = true;
96 char *matchedPattern = NULL;
97
98 String8 fullPath(root);
99 fullPath.appendPath(path);
100 FileType type = getFileType(fullPath);
101
102 int plen = strlen(path);
103
104 // Note: we don't have strtok_r under mingw.
105 for(char *token = strtok(patterns, delim);
106 !ignore && token != NULL;
107 token = strtok(NULL, delim)) {
108 chatty = token[0] != '!';
109 if (!chatty) token++; // skip !
110 if (strncasecmp(token, "<dir>" , 5) == 0) {
111 if (type != kFileTypeDirectory) continue;
112 token += 5;
113 }
114 if (strncasecmp(token, "<file>", 6) == 0) {
115 if (type != kFileTypeRegular) continue;
116 token += 6;
117 }
118
119 matchedPattern = token;
120 int n = strlen(token);
121
122 if (token[0] == '*') {
123 // Match *suffix
124 token++;
125 n--;
126 if (n <= plen) {
127 ignore = strncasecmp(token, path + plen - n, n) == 0;
128 }
129 } else if (n > 1 && token[n - 1] == '*') {
130 // Match prefix*
131 ignore = strncasecmp(token, path, n - 1) == 0;
132 } else {
133 ignore = strcasecmp(token, path) == 0;
134 }
135 }
136
137 if (ignore && chatty) {
138 fprintf(stderr, " (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
139 type == kFileTypeDirectory ? "dir" : "file",
140 path,
141 matchedPattern ? matchedPattern : "");
142 }
143
144 free(patterns);
145 return ignore;
146}
147
148// =========================================================================
149// =========================================================================
150// =========================================================================
151
Narayan Kamath91447d82014-01-21 15:32:36 +0000152/* static */
153inline bool isAlpha(const String8& string) {
154 const size_t length = string.length();
155 for (size_t i = 0; i < length; ++i) {
156 if (!isalpha(string[i])) {
157 return false;
158 }
159 }
160
161 return true;
162}
163
164/* static */
165inline bool isNumber(const String8& string) {
166 const size_t length = string.length();
167 for (size_t i = 0; i < length; ++i) {
168 if (!isdigit(string[i])) {
169 return false;
170 }
171 }
172
173 return true;
174}
175
176void AaptLocaleValue::setLanguage(const char* languageChars) {
177 size_t i = 0;
Andreas Gampeb8dc7bc2014-10-01 19:07:51 -0700178 while ((*languageChars) != '\0' && i < sizeof(language)/sizeof(language[0])) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000179 language[i++] = tolower(*languageChars);
180 languageChars++;
181 }
182}
183
184void AaptLocaleValue::setRegion(const char* regionChars) {
185 size_t i = 0;
Andreas Gampeb8dc7bc2014-10-01 19:07:51 -0700186 while ((*regionChars) != '\0' && i < sizeof(region)/sizeof(region[0])) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000187 region[i++] = toupper(*regionChars);
188 regionChars++;
189 }
190}
191
192void AaptLocaleValue::setScript(const char* scriptChars) {
193 size_t i = 0;
Andreas Gampeb8dc7bc2014-10-01 19:07:51 -0700194 while ((*scriptChars) != '\0' && i < sizeof(script)/sizeof(script[0])) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000195 if (i == 0) {
196 script[i++] = toupper(*scriptChars);
197 } else {
198 script[i++] = tolower(*scriptChars);
199 }
200 scriptChars++;
201 }
202}
203
204void AaptLocaleValue::setVariant(const char* variantChars) {
205 size_t i = 0;
Andreas Gampeb8dc7bc2014-10-01 19:07:51 -0700206 while ((*variantChars) != '\0' && i < sizeof(variant)/sizeof(variant[0])) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000207 variant[i++] = *variantChars;
208 variantChars++;
209 }
210}
211
212bool AaptLocaleValue::initFromFilterString(const String8& str) {
213 // A locale (as specified in the filter) is an underscore separated name such
214 // as "en_US", "en_Latn_US", or "en_US_POSIX".
Adam Lesinskifab50872014-04-16 14:40:42 -0700215 Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '_');
Narayan Kamath91447d82014-01-21 15:32:36 +0000216
217 const int numTags = parts.size();
218 bool valid = false;
219 if (numTags >= 1) {
220 const String8& lang = parts[0];
221 if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
222 setLanguage(lang.string());
223 valid = true;
224 }
225 }
226
227 if (!valid || numTags == 1) {
228 return valid;
229 }
230
231 // At this point, valid == true && numTags > 1.
232 const String8& part2 = parts[1];
233 if ((part2.length() == 2 && isAlpha(part2)) ||
234 (part2.length() == 3 && isNumber(part2))) {
235 setRegion(part2.string());
236 } else if (part2.length() == 4 && isAlpha(part2)) {
237 setScript(part2.string());
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800238 } else if (part2.length() >= 4 && part2.length() <= 8) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000239 setVariant(part2.string());
240 } else {
241 valid = false;
242 }
243
244 if (!valid || numTags == 2) {
245 return valid;
246 }
247
248 // At this point, valid == true && numTags > 1.
249 const String8& part3 = parts[2];
250 if (((part3.length() == 2 && isAlpha(part3)) ||
251 (part3.length() == 3 && isNumber(part3))) && script[0]) {
252 setRegion(part3.string());
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800253 } else if (part3.length() >= 4 && part3.length() <= 8) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000254 setVariant(part3.string());
255 } else {
256 valid = false;
257 }
258
259 if (!valid || numTags == 3) {
260 return valid;
261 }
262
263 const String8& part4 = parts[3];
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800264 if (part4.length() >= 4 && part4.length() <= 8) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000265 setVariant(part4.string());
266 } else {
267 valid = false;
268 }
269
270 if (!valid || numTags > 4) {
271 return false;
272 }
273
274 return true;
275}
276
277int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int startIndex) {
278 const int size = parts.size();
279 int currentIndex = startIndex;
280
281 String8 part = parts[currentIndex];
282 if (part[0] == 'b' && part[1] == '+') {
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800283 // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
Narayan Kamath91447d82014-01-21 15:32:36 +0000284 // except that the separator is "+" and not "-".
Adam Lesinskifab50872014-04-16 14:40:42 -0700285 Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
Narayan Kamath91447d82014-01-21 15:32:36 +0000286 subtags.removeItemsAt(0);
287 if (subtags.size() == 1) {
288 setLanguage(subtags[0]);
289 } else if (subtags.size() == 2) {
290 setLanguage(subtags[0]);
291
292 // The second tag can either be a region, a variant or a script.
293 switch (subtags[1].size()) {
294 case 2:
295 case 3:
296 setRegion(subtags[1]);
297 break;
298 case 4:
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800299 if (isAlpha(subtags[1])) {
300 setScript(subtags[1]);
301 break;
302 }
303 // This is not alphabetical, so we fall through to variant
Narayan Kamath91447d82014-01-21 15:32:36 +0000304 case 5:
305 case 6:
306 case 7:
307 case 8:
308 setVariant(subtags[1]);
309 break;
310 default:
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800311 fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n",
Narayan Kamath91447d82014-01-21 15:32:36 +0000312 part.string());
313 return -1;
314 }
315 } else if (subtags.size() == 3) {
316 // The language is always the first subtag.
317 setLanguage(subtags[0]);
318
319 // The second subtag can either be a script or a region code.
320 // If its size is 4, it's a script code, else it's a region code.
321 bool hasRegion = false;
322 if (subtags[1].size() == 4) {
323 setScript(subtags[1]);
324 } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
325 setRegion(subtags[1]);
326 hasRegion = true;
327 } else {
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800328 fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string());
Narayan Kamath91447d82014-01-21 15:32:36 +0000329 return -1;
330 }
331
332 // The third tag can either be a region code (if the second tag was
333 // a script), else a variant code.
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800334 if (subtags[2].size() >= 4) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000335 setVariant(subtags[2]);
336 } else {
337 setRegion(subtags[2]);
338 }
339 } else if (subtags.size() == 4) {
340 setLanguage(subtags[0]);
341 setScript(subtags[1]);
342 setRegion(subtags[2]);
343 setVariant(subtags[3]);
344 } else {
Roozbeh Pournaderb927c552016-01-15 11:23:42 -0800345 fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name: %s\n", part.string());
Narayan Kamath91447d82014-01-21 15:32:36 +0000346 return -1;
347 }
348
349 return ++currentIndex;
350 } else {
Narayan Kamath7f1a8952015-02-10 16:11:55 +0000351 if ((part.length() == 2 || part.length() == 3)
352 && isAlpha(part) && strcmp("car", part.string())) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000353 setLanguage(part);
354 if (++currentIndex == size) {
355 return size;
356 }
357 } else {
358 return currentIndex;
359 }
360
361 part = parts[currentIndex];
362 if (part.string()[0] == 'r' && part.length() == 3) {
363 setRegion(part.string() + 1);
364 if (++currentIndex == size) {
365 return size;
366 }
367 }
368 }
369
370 return currentIndex;
371}
372
Narayan Kamath91447d82014-01-21 15:32:36 +0000373void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
374 config.unpackLanguage(language);
375 config.unpackRegion(region);
Roozbeh Pournader79608982016-03-03 15:06:46 -0800376 if (config.localeScript[0] && !config.localeScriptWasComputed) {
Narayan Kamath91447d82014-01-21 15:32:36 +0000377 memcpy(script, config.localeScript, sizeof(config.localeScript));
378 }
379
380 if (config.localeVariant[0]) {
381 memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
382 }
383}
384
385void AaptLocaleValue::writeTo(ResTable_config* out) const {
386 out->packLanguage(language);
387 out->packRegion(region);
388
389 if (script[0]) {
390 memcpy(out->localeScript, script, sizeof(out->localeScript));
391 }
392
393 if (variant[0]) {
394 memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
395 }
396}
397
Adam Lesinski282e1812014-01-23 18:17:42 -0800398bool
399AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
400{
Adam Lesinskifab50872014-04-16 14:40:42 -0700401 const char* q = strchr(dir, '-');
402 size_t typeLen;
403 if (q != NULL) {
404 typeLen = q - dir;
405 } else {
406 typeLen = strlen(dir);
407 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800408
Adam Lesinskifab50872014-04-16 14:40:42 -0700409 String8 type(dir, typeLen);
410 if (!isValidResourceType(type)) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800411 return false;
412 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800413
Adam Lesinskifab50872014-04-16 14:40:42 -0700414 if (q != NULL) {
415 if (!AaptConfig::parse(String8(q + 1), &mParams)) {
416 return false;
Adam Lesinski282e1812014-01-23 18:17:42 -0800417 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800418 }
419
Adam Lesinskifab50872014-04-16 14:40:42 -0700420 *resType = type;
Adam Lesinski282e1812014-01-23 18:17:42 -0800421 return true;
422}
423
424String8
Adam Lesinski282e1812014-01-23 18:17:42 -0800425AaptGroupEntry::toDirName(const String8& resType) const
426{
427 String8 s = resType;
Adam Lesinskifab50872014-04-16 14:40:42 -0700428 String8 params = mParams.toString();
429 if (params.length() > 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800430 if (s.length() > 0) {
431 s += "-";
432 }
Adam Lesinskifab50872014-04-16 14:40:42 -0700433 s += params;
Adam Lesinski282e1812014-01-23 18:17:42 -0800434 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800435 return s;
436}
437
Adam Lesinski282e1812014-01-23 18:17:42 -0800438
439// =========================================================================
440// =========================================================================
441// =========================================================================
442
443void* AaptFile::editData(size_t size)
444{
445 if (size <= mBufferSize) {
446 mDataSize = size;
447 return mData;
448 }
449 size_t allocSize = (size*3)/2;
450 void* buf = realloc(mData, allocSize);
451 if (buf == NULL) {
452 return NULL;
453 }
454 mData = buf;
455 mDataSize = size;
456 mBufferSize = allocSize;
457 return buf;
458}
459
Adam Lesinskide898ff2014-01-29 18:20:45 -0800460void* AaptFile::editDataInRange(size_t offset, size_t size)
461{
462 return (void*)(((uint8_t*) editData(offset + size)) + offset);
463}
464
Adam Lesinski282e1812014-01-23 18:17:42 -0800465void* AaptFile::editData(size_t* outSize)
466{
467 if (outSize) {
468 *outSize = mDataSize;
469 }
470 return mData;
471}
472
473void* AaptFile::padData(size_t wordSize)
474{
475 const size_t extra = mDataSize%wordSize;
476 if (extra == 0) {
477 return mData;
478 }
479
480 size_t initial = mDataSize;
481 void* data = editData(initial+(wordSize-extra));
482 if (data != NULL) {
483 memset(((uint8_t*)data) + initial, 0, wordSize-extra);
484 }
485 return data;
486}
487
488status_t AaptFile::writeData(const void* data, size_t size)
489{
490 size_t end = mDataSize;
491 size_t total = size + end;
492 void* buf = editData(total);
493 if (buf == NULL) {
494 return UNKNOWN_ERROR;
495 }
496 memcpy(((char*)buf)+end, data, size);
497 return NO_ERROR;
498}
499
500void AaptFile::clearData()
501{
502 if (mData != NULL) free(mData);
503 mData = NULL;
504 mDataSize = 0;
505 mBufferSize = 0;
506}
507
508String8 AaptFile::getPrintableSource() const
509{
510 if (hasData()) {
511 String8 name(mGroupEntry.toDirName(String8()));
512 name.appendPath(mPath);
513 name.append(" #generated");
514 return name;
515 }
516 return mSourceFile;
517}
518
519// =========================================================================
520// =========================================================================
521// =========================================================================
522
Adam Lesinski09384302014-01-22 16:07:42 -0800523status_t AaptGroup::addFile(const sp<AaptFile>& file, const bool overwriteDuplicate)
Adam Lesinski282e1812014-01-23 18:17:42 -0800524{
Adam Lesinski09384302014-01-22 16:07:42 -0800525 ssize_t index = mFiles.indexOfKey(file->getGroupEntry());
526 if (index >= 0 && overwriteDuplicate) {
527 fprintf(stderr, "warning: overwriting '%s' with '%s'\n",
528 mFiles[index]->getSourceFile().string(),
529 file->getSourceFile().string());
530 removeFile(index);
531 index = -1;
532 }
533
534 if (index < 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800535 file->mPath = mPath;
536 mFiles.add(file->getGroupEntry(), file);
537 return NO_ERROR;
538 }
539
Adam Lesinski48f05d22014-05-12 22:13:02 -0700540 // Check if the version is automatically applied. This is a common source of
541 // error.
542 ConfigDescription withoutVersion = file->getGroupEntry().toParams();
543 withoutVersion.version = 0;
544 AaptConfig::applyVersionForCompatibility(&withoutVersion);
Adam Lesinski282e1812014-01-23 18:17:42 -0800545
Adam Lesinski48f05d22014-05-12 22:13:02 -0700546 const sp<AaptFile>& originalFile = mFiles.valueAt(index);
547 SourcePos(file->getSourceFile(), -1)
548 .error("Duplicate file.\n%s: Original is here. %s",
549 originalFile->getPrintableSource().string(),
550 (withoutVersion.version != 0) ? "The version qualifier may be implied." : "");
Adam Lesinski282e1812014-01-23 18:17:42 -0800551 return UNKNOWN_ERROR;
552}
553
554void AaptGroup::removeFile(size_t index)
555{
556 mFiles.removeItemsAt(index);
557}
558
559void AaptGroup::print(const String8& prefix) const
560{
561 printf("%s%s\n", prefix.string(), getPath().string());
562 const size_t N=mFiles.size();
563 size_t i;
564 for (i=0; i<N; i++) {
565 sp<AaptFile> file = mFiles.valueAt(i);
566 const AaptGroupEntry& e = file->getGroupEntry();
567 if (file->hasData()) {
568 printf("%s Gen: (%s) %d bytes\n", prefix.string(), e.toDirName(String8()).string(),
569 (int)file->getSize());
570 } else {
571 printf("%s Src: (%s) %s\n", prefix.string(), e.toDirName(String8()).string(),
572 file->getPrintableSource().string());
573 }
574 //printf("%s File Group Entry: %s\n", prefix.string(),
575 // file->getGroupEntry().toDirName(String8()).string());
576 }
577}
578
579String8 AaptGroup::getPrintableSource() const
580{
581 if (mFiles.size() > 0) {
582 // Arbitrarily pull the first source file out of the list.
583 return mFiles.valueAt(0)->getPrintableSource();
584 }
585
586 // Should never hit this case, but to be safe...
587 return getPath();
588
589}
590
591// =========================================================================
592// =========================================================================
593// =========================================================================
594
595status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file)
596{
597 if (mFiles.indexOfKey(name) >= 0) {
598 return ALREADY_EXISTS;
599 }
600 mFiles.add(name, file);
601 return NO_ERROR;
602}
603
604status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir)
605{
606 if (mDirs.indexOfKey(name) >= 0) {
607 return ALREADY_EXISTS;
608 }
609 mDirs.add(name, dir);
610 return NO_ERROR;
611}
612
613sp<AaptDir> AaptDir::makeDir(const String8& path)
614{
615 String8 name;
616 String8 remain = path;
617
618 sp<AaptDir> subdir = this;
619 while (name = remain.walkPath(&remain), remain != "") {
620 subdir = subdir->makeDir(name);
621 }
622
623 ssize_t i = subdir->mDirs.indexOfKey(name);
624 if (i >= 0) {
625 return subdir->mDirs.valueAt(i);
626 }
627 sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
628 subdir->mDirs.add(name, dir);
629 return dir;
630}
631
632void AaptDir::removeFile(const String8& name)
633{
634 mFiles.removeItem(name);
635}
636
637void AaptDir::removeDir(const String8& name)
638{
639 mDirs.removeItem(name);
640}
641
Adam Lesinski09384302014-01-22 16:07:42 -0800642status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file,
643 const bool overwrite)
Adam Lesinski282e1812014-01-23 18:17:42 -0800644{
645 sp<AaptGroup> group;
646 if (mFiles.indexOfKey(leafName) >= 0) {
647 group = mFiles.valueFor(leafName);
648 } else {
649 group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
650 mFiles.add(leafName, group);
651 }
652
Adam Lesinski09384302014-01-22 16:07:42 -0800653 return group->addFile(file, overwrite);
Adam Lesinski282e1812014-01-23 18:17:42 -0800654}
655
656ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
657 const AaptGroupEntry& kind, const String8& resType,
Adam Lesinski09384302014-01-22 16:07:42 -0800658 sp<FilePathStore>& fullResPaths, const bool overwrite)
Adam Lesinski282e1812014-01-23 18:17:42 -0800659{
660 Vector<String8> fileNames;
661 {
662 DIR* dir = NULL;
663
664 dir = opendir(srcDir.string());
665 if (dir == NULL) {
666 fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
667 return UNKNOWN_ERROR;
668 }
669
670 /*
671 * Slurp the filenames out of the directory.
672 */
673 while (1) {
674 struct dirent* entry;
675
676 entry = readdir(dir);
677 if (entry == NULL)
678 break;
679
680 if (isHidden(srcDir.string(), entry->d_name))
681 continue;
682
683 String8 name(entry->d_name);
684 fileNames.add(name);
685 // Add fully qualified path for dependency purposes
686 // if we're collecting them
687 if (fullResPaths != NULL) {
688 fullResPaths->add(srcDir.appendPathCopy(name));
689 }
690 }
691 closedir(dir);
692 }
693
694 ssize_t count = 0;
695
696 /*
697 * Stash away the files and recursively descend into subdirectories.
698 */
699 const size_t N = fileNames.size();
700 size_t i;
701 for (i = 0; i < N; i++) {
702 String8 pathName(srcDir);
703 FileType type;
704
705 pathName.appendPath(fileNames[i].string());
706 type = getFileType(pathName.string());
707 if (type == kFileTypeDirectory) {
708 sp<AaptDir> subdir;
709 bool notAdded = false;
710 if (mDirs.indexOfKey(fileNames[i]) >= 0) {
711 subdir = mDirs.valueFor(fileNames[i]);
712 } else {
713 subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
714 notAdded = true;
715 }
716 ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
Adam Lesinski09384302014-01-22 16:07:42 -0800717 resType, fullResPaths, overwrite);
Adam Lesinski282e1812014-01-23 18:17:42 -0800718 if (res < NO_ERROR) {
719 return res;
720 }
721 if (res > 0 && notAdded) {
722 mDirs.add(fileNames[i], subdir);
723 }
724 count += res;
725 } else if (type == kFileTypeRegular) {
726 sp<AaptFile> file = new AaptFile(pathName, kind, resType);
Adam Lesinski09384302014-01-22 16:07:42 -0800727 status_t err = addLeafFile(fileNames[i], file, overwrite);
Adam Lesinski282e1812014-01-23 18:17:42 -0800728 if (err != NO_ERROR) {
729 return err;
730 }
731
732 count++;
733
734 } else {
735 if (bundle->getVerbose())
736 printf(" (ignoring non-file/dir '%s')\n", pathName.string());
737 }
738 }
739
740 return count;
741}
742
743status_t AaptDir::validate() const
744{
745 const size_t NF = mFiles.size();
746 const size_t ND = mDirs.size();
747 size_t i;
748 for (i = 0; i < NF; i++) {
749 if (!validateFileName(mFiles.valueAt(i)->getLeaf().string())) {
750 SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
751 "Invalid filename. Unable to add.");
752 return UNKNOWN_ERROR;
753 }
754
755 size_t j;
756 for (j = i+1; j < NF; j++) {
757 if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
758 mFiles.valueAt(j)->getLeaf().string()) == 0) {
759 SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
760 "File is case-insensitive equivalent to: %s",
761 mFiles.valueAt(j)->getPrintableSource().string());
762 return UNKNOWN_ERROR;
763 }
764
765 // TODO: if ".gz", check for non-.gz; if non-, check for ".gz"
766 // (this is mostly caught by the "marked" stuff, below)
767 }
768
769 for (j = 0; j < ND; j++) {
770 if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
771 mDirs.valueAt(j)->getLeaf().string()) == 0) {
772 SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
773 "File conflicts with dir from: %s",
774 mDirs.valueAt(j)->getPrintableSource().string());
775 return UNKNOWN_ERROR;
776 }
777 }
778 }
779
780 for (i = 0; i < ND; i++) {
781 if (!validateFileName(mDirs.valueAt(i)->getLeaf().string())) {
782 SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
783 "Invalid directory name, unable to add.");
784 return UNKNOWN_ERROR;
785 }
786
787 size_t j;
788 for (j = i+1; j < ND; j++) {
789 if (strcasecmp(mDirs.valueAt(i)->getLeaf().string(),
790 mDirs.valueAt(j)->getLeaf().string()) == 0) {
791 SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
792 "Directory is case-insensitive equivalent to: %s",
793 mDirs.valueAt(j)->getPrintableSource().string());
794 return UNKNOWN_ERROR;
795 }
796 }
797
798 status_t err = mDirs.valueAt(i)->validate();
799 if (err != NO_ERROR) {
800 return err;
801 }
802 }
803
804 return NO_ERROR;
805}
806
807void AaptDir::print(const String8& prefix) const
808{
809 const size_t ND=getDirs().size();
810 size_t i;
811 for (i=0; i<ND; i++) {
812 getDirs().valueAt(i)->print(prefix);
813 }
814
815 const size_t NF=getFiles().size();
816 for (i=0; i<NF; i++) {
817 getFiles().valueAt(i)->print(prefix);
818 }
819}
820
821String8 AaptDir::getPrintableSource() const
822{
823 if (mFiles.size() > 0) {
824 // Arbitrarily pull the first file out of the list as the source dir.
825 return mFiles.valueAt(0)->getPrintableSource().getPathDir();
826 }
827 if (mDirs.size() > 0) {
828 // Or arbitrarily pull the first dir out of the list as the source dir.
829 return mDirs.valueAt(0)->getPrintableSource().getPathDir();
830 }
831
832 // Should never hit this case, but to be safe...
833 return mPath;
834
835}
836
837// =========================================================================
838// =========================================================================
839// =========================================================================
840
841status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols)
842{
843 status_t err = NO_ERROR;
844 size_t N = javaSymbols->mSymbols.size();
845 for (size_t i=0; i<N; i++) {
846 const String8& name = javaSymbols->mSymbols.keyAt(i);
847 const AaptSymbolEntry& entry = javaSymbols->mSymbols.valueAt(i);
848 ssize_t pos = mSymbols.indexOfKey(name);
849 if (pos < 0) {
850 entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string());
851 err = UNKNOWN_ERROR;
852 continue;
853 }
854 //printf("**** setting symbol #%d/%d %s to isJavaSymbol=%d\n",
855 // i, N, name.string(), entry.isJavaSymbol ? 1 : 0);
856 mSymbols.editValueAt(pos).isJavaSymbol = entry.isJavaSymbol;
857 }
858
859 N = javaSymbols->mNestedSymbols.size();
860 for (size_t i=0; i<N; i++) {
861 const String8& name = javaSymbols->mNestedSymbols.keyAt(i);
862 const sp<AaptSymbols>& symbols = javaSymbols->mNestedSymbols.valueAt(i);
863 ssize_t pos = mNestedSymbols.indexOfKey(name);
864 if (pos < 0) {
865 SourcePos pos;
866 pos.error("Java symbol dir %s not defined\n", name.string());
867 err = UNKNOWN_ERROR;
868 continue;
869 }
870 //printf("**** applying java symbols in dir %s\n", name.string());
871 status_t myerr = mNestedSymbols.valueAt(pos)->applyJavaSymbols(symbols);
872 if (myerr != NO_ERROR) {
873 err = myerr;
874 }
875 }
876
877 return err;
878}
879
880// =========================================================================
881// =========================================================================
882// =========================================================================
883
884AaptAssets::AaptAssets()
885 : AaptDir(String8(), String8()),
Narayan Kamath91447d82014-01-21 15:32:36 +0000886 mHavePrivateSymbols(false),
887 mChanged(false), mHaveIncludedAssets(false),
Adam Lesinskifab50872014-04-16 14:40:42 -0700888 mRes(NULL) {}
Adam Lesinski282e1812014-01-23 18:17:42 -0800889
890const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
891 if (mChanged) {
892 }
893 return mGroupEntries;
894}
895
896status_t AaptAssets::addFile(const String8& name, const sp<AaptGroup>& file)
897{
898 mChanged = true;
899 return AaptDir::addFile(name, file);
900}
901
902sp<AaptFile> AaptAssets::addFile(
903 const String8& filePath, const AaptGroupEntry& entry,
904 const String8& srcDir, sp<AaptGroup>* outGroup,
905 const String8& resType)
906{
907 sp<AaptDir> dir = this;
908 sp<AaptGroup> group;
909 sp<AaptFile> file;
910 String8 root, remain(filePath), partialPath;
911 while (remain.length() > 0) {
912 root = remain.walkPath(&remain);
913 partialPath.appendPath(root);
914
915 const String8 rootStr(root);
916
917 if (remain.length() == 0) {
918 ssize_t i = dir->getFiles().indexOfKey(rootStr);
919 if (i >= 0) {
920 group = dir->getFiles().valueAt(i);
921 } else {
922 group = new AaptGroup(rootStr, filePath);
923 status_t res = dir->addFile(rootStr, group);
924 if (res != NO_ERROR) {
925 return NULL;
926 }
927 }
928 file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
929 status_t res = group->addFile(file);
930 if (res != NO_ERROR) {
931 return NULL;
932 }
933 break;
934
935 } else {
936 ssize_t i = dir->getDirs().indexOfKey(rootStr);
937 if (i >= 0) {
938 dir = dir->getDirs().valueAt(i);
939 } else {
940 sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
941 status_t res = dir->addDir(rootStr, subdir);
942 if (res != NO_ERROR) {
943 return NULL;
944 }
945 dir = subdir;
946 }
947 }
948 }
949
950 mGroupEntries.add(entry);
951 if (outGroup) *outGroup = group;
952 return file;
953}
954
955void AaptAssets::addResource(const String8& leafName, const String8& path,
956 const sp<AaptFile>& file, const String8& resType)
957{
958 sp<AaptDir> res = AaptDir::makeDir(kResString);
959 String8 dirname = file->getGroupEntry().toDirName(resType);
960 sp<AaptDir> subdir = res->makeDir(dirname);
961 sp<AaptGroup> grr = new AaptGroup(leafName, path);
962 grr->addFile(file);
963
964 subdir->addFile(leafName, grr);
965}
966
Adam Lesinski282e1812014-01-23 18:17:42 -0800967ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
968{
969 int count;
970 int totalCount = 0;
971 FileType type;
972 const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
973 const size_t dirCount =resDirs.size();
974 sp<AaptAssets> current = this;
975
976 const int N = bundle->getFileSpecCount();
977
978 /*
979 * If a package manifest was specified, include that first.
980 */
981 if (bundle->getAndroidManifestFile() != NULL) {
982 // place at root of zip.
983 String8 srcFile(bundle->getAndroidManifestFile());
984 addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
985 NULL, String8());
986 totalCount++;
987 }
988
989 /*
990 * If a directory of custom assets was supplied, slurp 'em up.
991 */
Adam Lesinski09384302014-01-22 16:07:42 -0800992 const Vector<const char*>& assetDirs = bundle->getAssetSourceDirs();
993 const int AN = assetDirs.size();
994 for (int i = 0; i < AN; i++) {
995 FileType type = getFileType(assetDirs[i]);
Adam Lesinski282e1812014-01-23 18:17:42 -0800996 if (type == kFileTypeNonexistent) {
Adam Lesinski09384302014-01-22 16:07:42 -0800997 fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDirs[i]);
Adam Lesinski282e1812014-01-23 18:17:42 -0800998 return UNKNOWN_ERROR;
999 }
1000 if (type != kFileTypeDirectory) {
Adam Lesinski09384302014-01-22 16:07:42 -08001001 fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDirs[i]);
Adam Lesinski282e1812014-01-23 18:17:42 -08001002 return UNKNOWN_ERROR;
1003 }
1004
Adam Lesinski09384302014-01-22 16:07:42 -08001005 String8 assetRoot(assetDirs[i]);
Adam Lesinski282e1812014-01-23 18:17:42 -08001006 sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
1007 AaptGroupEntry group;
1008 count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
Adam Lesinski09384302014-01-22 16:07:42 -08001009 String8(), mFullAssetPaths, true);
Adam Lesinski282e1812014-01-23 18:17:42 -08001010 if (count < 0) {
1011 totalCount = count;
1012 goto bail;
1013 }
1014 if (count > 0) {
1015 mGroupEntries.add(group);
1016 }
1017 totalCount += count;
1018
Adam Lesinski09384302014-01-22 16:07:42 -08001019 if (bundle->getVerbose()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001020 printf("Found %d custom asset file%s in %s\n",
Adam Lesinski09384302014-01-22 16:07:42 -08001021 count, (count==1) ? "" : "s", assetDirs[i]);
1022 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001023 }
1024
1025 /*
1026 * If a directory of resource-specific assets was supplied, slurp 'em up.
1027 */
1028 for (size_t i=0; i<dirCount; i++) {
1029 const char *res = resDirs[i];
1030 if (res) {
1031 type = getFileType(res);
1032 if (type == kFileTypeNonexistent) {
1033 fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
1034 return UNKNOWN_ERROR;
1035 }
1036 if (type == kFileTypeDirectory) {
1037 if (i>0) {
1038 sp<AaptAssets> nextOverlay = new AaptAssets();
1039 current->setOverlay(nextOverlay);
1040 current = nextOverlay;
1041 current->setFullResPaths(mFullResPaths);
1042 }
1043 count = current->slurpResourceTree(bundle, String8(res));
Bryan Mawhinney9ab9b932014-01-24 16:18:13 +00001044 if (i > 0 && count > 0) {
1045 count = current->filter(bundle);
1046 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001047
1048 if (count < 0) {
1049 totalCount = count;
1050 goto bail;
1051 }
1052 totalCount += count;
1053 }
1054 else {
1055 fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
1056 return UNKNOWN_ERROR;
1057 }
1058 }
1059
1060 }
1061 /*
1062 * Now do any additional raw files.
1063 */
1064 for (int arg=0; arg<N; arg++) {
1065 const char* assetDir = bundle->getFileSpecEntry(arg);
1066
1067 FileType type = getFileType(assetDir);
1068 if (type == kFileTypeNonexistent) {
1069 fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
1070 return UNKNOWN_ERROR;
1071 }
1072 if (type != kFileTypeDirectory) {
1073 fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
1074 return UNKNOWN_ERROR;
1075 }
1076
1077 String8 assetRoot(assetDir);
1078
1079 if (bundle->getVerbose())
1080 printf("Processing raw dir '%s'\n", (const char*) assetDir);
1081
1082 /*
1083 * Do a recursive traversal of subdir tree. We don't make any
1084 * guarantees about ordering, so we're okay with an inorder search
1085 * using whatever order the OS happens to hand back to us.
1086 */
1087 count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
1088 if (count < 0) {
1089 /* failure; report error and remove archive */
1090 totalCount = count;
1091 goto bail;
1092 }
1093 totalCount += count;
1094
1095 if (bundle->getVerbose())
1096 printf("Found %d asset file%s in %s\n",
1097 count, (count==1) ? "" : "s", assetDir);
1098 }
1099
1100 count = validate();
1101 if (count != NO_ERROR) {
1102 totalCount = count;
1103 goto bail;
1104 }
1105
1106 count = filter(bundle);
1107 if (count != NO_ERROR) {
1108 totalCount = count;
1109 goto bail;
1110 }
1111
1112bail:
1113 return totalCount;
1114}
1115
1116ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
1117 const AaptGroupEntry& kind,
1118 const String8& resType,
Adam Lesinski40e8eef2014-09-16 14:43:29 -07001119 sp<FilePathStore>& fullResPaths,
1120 const bool overwrite)
Adam Lesinski282e1812014-01-23 18:17:42 -08001121{
Adam Lesinski40e8eef2014-09-16 14:43:29 -07001122 ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths, overwrite);
Adam Lesinski282e1812014-01-23 18:17:42 -08001123 if (res > 0) {
1124 mGroupEntries.add(kind);
1125 }
1126
1127 return res;
1128}
1129
1130ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
1131{
1132 ssize_t err = 0;
1133
1134 DIR* dir = opendir(srcDir.string());
1135 if (dir == NULL) {
1136 fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
1137 return UNKNOWN_ERROR;
1138 }
1139
1140 status_t count = 0;
1141
1142 /*
1143 * Run through the directory, looking for dirs that match the
1144 * expected pattern.
1145 */
1146 while (1) {
1147 struct dirent* entry = readdir(dir);
1148 if (entry == NULL) {
1149 break;
1150 }
1151
1152 if (isHidden(srcDir.string(), entry->d_name)) {
1153 continue;
1154 }
1155
1156 String8 subdirName(srcDir);
1157 subdirName.appendPath(entry->d_name);
1158
1159 AaptGroupEntry group;
1160 String8 resType;
1161 bool b = group.initFromDirName(entry->d_name, &resType);
1162 if (!b) {
Adam Lesinskifab50872014-04-16 14:40:42 -07001163 fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(),
Adam Lesinski282e1812014-01-23 18:17:42 -08001164 entry->d_name);
1165 err = -1;
1166 continue;
1167 }
1168
1169 if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) {
1170 int maxResInt = atoi(bundle->getMaxResVersion());
1171 const char *verString = group.getVersionString().string();
1172 int dirVersionInt = atoi(verString + 1); // skip 'v' in version name
1173 if (dirVersionInt > maxResInt) {
1174 fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name);
1175 continue;
1176 }
1177 }
1178
1179 FileType type = getFileType(subdirName.string());
1180
1181 if (type == kFileTypeDirectory) {
1182 sp<AaptDir> dir = makeDir(resType);
1183 ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
1184 resType, mFullResPaths);
1185 if (res < 0) {
1186 count = res;
1187 goto bail;
1188 }
1189 if (res > 0) {
1190 mGroupEntries.add(group);
1191 count += res;
1192 }
1193
1194 // Only add this directory if we don't already have a resource dir
1195 // for the current type. This ensures that we only add the dir once
1196 // for all configs.
1197 sp<AaptDir> rdir = resDir(resType);
1198 if (rdir == NULL) {
1199 mResDirs.add(dir);
1200 }
1201 } else {
1202 if (bundle->getVerbose()) {
1203 fprintf(stderr, " (ignoring file '%s')\n", subdirName.string());
1204 }
1205 }
1206 }
1207
1208bail:
1209 closedir(dir);
1210 dir = NULL;
1211
1212 if (err != 0) {
1213 return err;
1214 }
1215 return count;
1216}
1217
1218ssize_t
Andreas Gampe2412f842014-09-30 20:55:57 -07001219AaptAssets::slurpResourceZip(Bundle* /* bundle */, const char* filename)
Adam Lesinski282e1812014-01-23 18:17:42 -08001220{
1221 int count = 0;
1222 SortedVector<AaptGroupEntry> entries;
1223
1224 ZipFile* zip = new ZipFile;
1225 status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
1226 if (err != NO_ERROR) {
1227 fprintf(stderr, "error opening zip file %s\n", filename);
1228 count = err;
1229 delete zip;
1230 return -1;
1231 }
1232
1233 const int N = zip->getNumEntries();
1234 for (int i=0; i<N; i++) {
1235 ZipEntry* entry = zip->getEntryByIndex(i);
1236 if (entry->getDeleted()) {
1237 continue;
1238 }
1239
1240 String8 entryName(entry->getFileName());
1241
1242 String8 dirName = entryName.getPathDir();
1243 sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
1244
1245 String8 resType;
1246 AaptGroupEntry kind;
1247
1248 String8 remain;
1249 if (entryName.walkPath(&remain) == kResourceDir) {
1250 // these are the resources, pull their type out of the directory name
1251 kind.initFromDirName(remain.walkPath().string(), &resType);
1252 } else {
1253 // these are untyped and don't have an AaptGroupEntry
1254 }
1255 if (entries.indexOf(kind) < 0) {
1256 entries.add(kind);
1257 mGroupEntries.add(kind);
1258 }
1259
1260 // use the one from the zip file if they both exist.
1261 dir->removeFile(entryName.getPathLeaf());
1262
1263 sp<AaptFile> file = new AaptFile(entryName, kind, resType);
1264 status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
1265 if (err != NO_ERROR) {
1266 fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
1267 count = err;
1268 goto bail;
1269 }
1270 file->setCompressionMethod(entry->getCompressionMethod());
1271
1272#if 0
1273 if (entryName == "AndroidManifest.xml") {
1274 printf("AndroidManifest.xml\n");
1275 }
1276 printf("\n\nfile: %s\n", entryName.string());
1277#endif
1278
1279 size_t len = entry->getUncompressedLen();
1280 void* data = zip->uncompress(entry);
1281 void* buf = file->editData(len);
1282 memcpy(buf, data, len);
1283
1284#if 0
1285 const int OFF = 0;
1286 const unsigned char* p = (unsigned char*)data;
1287 const unsigned char* end = p+len;
1288 p += OFF;
1289 for (int i=0; i<32 && p < end; i++) {
1290 printf("0x%03x ", i*0x10 + OFF);
1291 for (int j=0; j<0x10 && p < end; j++) {
1292 printf(" %02x", *p);
1293 p++;
1294 }
1295 printf("\n");
1296 }
1297#endif
1298
1299 free(data);
1300
1301 count++;
1302 }
1303
1304bail:
1305 delete zip;
1306 return count;
1307}
1308
1309status_t AaptAssets::filter(Bundle* bundle)
1310{
Hans Boehm25dfa752016-08-10 19:56:50 -07001311 sp<WeakResourceFilter> reqFilter(new WeakResourceFilter());
1312 status_t err = reqFilter->parse(bundle->getConfigurations());
Adam Lesinski282e1812014-01-23 18:17:42 -08001313 if (err != NO_ERROR) {
1314 return err;
1315 }
1316
Adam Lesinskifab50872014-04-16 14:40:42 -07001317 uint32_t preferredDensity = 0;
1318 if (bundle->getPreferredDensity().size() > 0) {
1319 ResTable_config preferredConfig;
1320 if (!AaptConfig::parseDensity(bundle->getPreferredDensity().string(), &preferredConfig)) {
1321 fprintf(stderr, "Error parsing preferred density: %s\n",
1322 bundle->getPreferredDensity().string());
1323 return UNKNOWN_ERROR;
1324 }
1325 preferredDensity = preferredConfig.density;
Adam Lesinski282e1812014-01-23 18:17:42 -08001326 }
1327
Hans Boehm25dfa752016-08-10 19:56:50 -07001328 if (reqFilter->isEmpty() && preferredDensity == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001329 return NO_ERROR;
1330 }
1331
1332 if (bundle->getVerbose()) {
Hans Boehm25dfa752016-08-10 19:56:50 -07001333 if (!reqFilter->isEmpty()) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001334 printf("Applying required filter: %s\n",
Adam Lesinskifab50872014-04-16 14:40:42 -07001335 bundle->getConfigurations().string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001336 }
Adam Lesinskifab50872014-04-16 14:40:42 -07001337 if (preferredDensity > 0) {
1338 printf("Applying preferred density filter: %s\n",
1339 bundle->getPreferredDensity().string());
Adam Lesinski282e1812014-01-23 18:17:42 -08001340 }
1341 }
1342
1343 const Vector<sp<AaptDir> >& resdirs = mResDirs;
1344 const size_t ND = resdirs.size();
1345 for (size_t i=0; i<ND; i++) {
1346 const sp<AaptDir>& dir = resdirs.itemAt(i);
1347 if (dir->getLeaf() == kValuesDir) {
1348 // The "value" dir is special since a single file defines
1349 // multiple resources, so we can not do filtering on the
1350 // files themselves.
1351 continue;
1352 }
1353 if (dir->getLeaf() == kMipmapDir) {
1354 // We also skip the "mipmap" directory, since the point of this
1355 // is to include all densities without stripping. If you put
1356 // other configurations in here as well they won't be stripped
1357 // either... So don't do that. Seriously. What is wrong with you?
1358 continue;
1359 }
1360
1361 const size_t NG = dir->getFiles().size();
1362 for (size_t j=0; j<NG; j++) {
1363 sp<AaptGroup> grp = dir->getFiles().valueAt(j);
1364
1365 // First remove any configurations we know we don't need.
1366 for (size_t k=0; k<grp->getFiles().size(); k++) {
1367 sp<AaptFile> file = grp->getFiles().valueAt(k);
1368 if (k == 0 && grp->getFiles().size() == 1) {
1369 // If this is the only file left, we need to keep it.
1370 // Otherwise the resource IDs we are using will be inconsistent
1371 // with what we get when not stripping. Sucky, but at least
1372 // for now we can rely on the back-end doing another filtering
1373 // pass to take this out and leave us with this resource name
1374 // containing no entries.
1375 continue;
1376 }
1377 if (file->getPath().getPathExtension() == ".xml") {
1378 // We can't remove .xml files at this point, because when
1379 // we parse them they may add identifier resources, so
1380 // removing them can cause our resource identifiers to
1381 // become inconsistent.
1382 continue;
1383 }
1384 const ResTable_config& config(file->getGroupEntry().toParams());
Hans Boehm25dfa752016-08-10 19:56:50 -07001385 if (!reqFilter->match(config)) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001386 if (bundle->getVerbose()) {
1387 printf("Pruning unneeded resource: %s\n",
1388 file->getPrintableSource().string());
1389 }
1390 grp->removeFile(k);
1391 k--;
1392 }
1393 }
1394
1395 // Quick check: no preferred filters, nothing more to do.
Adam Lesinskifab50872014-04-16 14:40:42 -07001396 if (preferredDensity == 0) {
Adam Lesinski282e1812014-01-23 18:17:42 -08001397 continue;
1398 }
1399
Adam Lesinski8cf61842013-10-18 13:42:09 -07001400 // Get the preferred density if there is one. We do not match exactly for density.
1401 // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we
1402 // pick xhdpi.
Adam Lesinskifab50872014-04-16 14:40:42 -07001403 for (size_t k=0; k<grp->getFiles().size(); k++) {
1404 sp<AaptFile> file = grp->getFiles().valueAt(k);
1405 if (k == 0 && grp->getFiles().size() == 1) {
1406 // If this is the only file left, we need to keep it.
1407 // Otherwise the resource IDs we are using will be inconsistent
1408 // with what we get when not stripping. Sucky, but at least
1409 // for now we can rely on the back-end doing another filtering
1410 // pass to take this out and leave us with this resource name
1411 // containing no entries.
1412 continue;
1413 }
1414 if (file->getPath().getPathExtension() == ".xml") {
1415 // We can't remove .xml files at this point, because when
1416 // we parse them they may add identifier resources, so
1417 // removing them can cause our resource identifiers to
1418 // become inconsistent.
1419 continue;
1420 }
1421 const ResTable_config& config(file->getGroupEntry().toParams());
1422 if (config.density != 0 && config.density != preferredDensity) {
1423 // This is a resource we would prefer not to have. Check
1424 // to see if have a similar variation that we would like
1425 // to have and, if so, we can drop it.
1426 uint32_t bestDensity = config.density;
Adam Lesinski8cf61842013-10-18 13:42:09 -07001427
Adam Lesinskifab50872014-04-16 14:40:42 -07001428 for (size_t m=0; m<grp->getFiles().size(); m++) {
1429 if (m == k) {
1430 continue;
Adam Lesinski282e1812014-01-23 18:17:42 -08001431 }
Adam Lesinski8cf61842013-10-18 13:42:09 -07001432
Adam Lesinskifab50872014-04-16 14:40:42 -07001433 sp<AaptFile> mfile = grp->getFiles().valueAt(m);
1434 const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
1435 if (AaptConfig::isSameExcept(config, mconfig, ResTable_config::CONFIG_DENSITY)) {
1436 // See if there is a better density resource
1437 if (mconfig.density < bestDensity &&
Bryan Mawhinneyb0db8de2014-06-06 13:27:11 +01001438 mconfig.density >= preferredDensity &&
Adam Lesinskifab50872014-04-16 14:40:42 -07001439 bestDensity > preferredDensity) {
Bryan Mawhinneyb0db8de2014-06-06 13:27:11 +01001440 // This density is our preferred density, or between our best density and
Adam Lesinskifab50872014-04-16 14:40:42 -07001441 // the preferred density, therefore it is better.
1442 bestDensity = mconfig.density;
1443 } else if (mconfig.density > bestDensity &&
1444 bestDensity < preferredDensity) {
1445 // This density is better than our best density and
1446 // our best density was smaller than our preferred
1447 // density, so it is better.
1448 bestDensity = mconfig.density;
Adam Lesinski8cf61842013-10-18 13:42:09 -07001449 }
Adam Lesinski8cf61842013-10-18 13:42:09 -07001450 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001451 }
Adam Lesinskifab50872014-04-16 14:40:42 -07001452
1453 if (bestDensity != config.density) {
1454 if (bundle->getVerbose()) {
1455 printf("Pruning unneeded resource: %s\n",
1456 file->getPrintableSource().string());
1457 }
1458 grp->removeFile(k);
1459 k--;
1460 }
Adam Lesinski282e1812014-01-23 18:17:42 -08001461 }
1462 }
1463 }
1464 }
1465
1466 return NO_ERROR;
1467}
1468
1469sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name)
1470{
1471 sp<AaptSymbols> sym = mSymbols.valueFor(name);
1472 if (sym == NULL) {
1473 sym = new AaptSymbols();
1474 mSymbols.add(name, sym);
1475 }
1476 return sym;
1477}
1478
1479sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name)
1480{
1481 sp<AaptSymbols> sym = mJavaSymbols.valueFor(name);
1482 if (sym == NULL) {
1483 sym = new AaptSymbols();
1484 mJavaSymbols.add(name, sym);
1485 }
1486 return sym;
1487}
1488
1489status_t AaptAssets::applyJavaSymbols()
1490{
1491 size_t N = mJavaSymbols.size();
1492 for (size_t i=0; i<N; i++) {
1493 const String8& name = mJavaSymbols.keyAt(i);
1494 const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i);
1495 ssize_t pos = mSymbols.indexOfKey(name);
1496 if (pos < 0) {
1497 SourcePos pos;
1498 pos.error("Java symbol dir %s not defined\n", name.string());
1499 return UNKNOWN_ERROR;
1500 }
1501 //printf("**** applying java symbols in dir %s\n", name.string());
1502 status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols);
1503 if (err != NO_ERROR) {
1504 return err;
1505 }
1506 }
1507
1508 return NO_ERROR;
1509}
1510
1511bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const {
1512 //printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n",
1513 // sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0,
1514 // sym.isJavaSymbol ? 1 : 0);
1515 if (!mHavePrivateSymbols) return true;
1516 if (sym.isPublic) return true;
1517 if (includePrivate && sym.isJavaSymbol) return true;
1518 return false;
1519}
1520
1521status_t AaptAssets::buildIncludedResources(Bundle* bundle)
1522{
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001523 if (mHaveIncludedAssets) {
1524 return NO_ERROR;
Adam Lesinski282e1812014-01-23 18:17:42 -08001525 }
1526
Adam Lesinski833f3cc2014-06-18 15:06:01 -07001527 // Add in all includes.
1528 const Vector<String8>& includes = bundle->getPackageIncludes();
1529 const size_t packageIncludeCount = includes.size();
1530 for (size_t i = 0; i < packageIncludeCount; i++) {
1531 if (bundle->getVerbose()) {
1532 printf("Including resources from package: %s\n", includes[i].string());
1533 }
1534
1535 if (!mIncludedAssets.addAssetPath(includes[i], NULL)) {
1536 fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
1537 includes[i].string());
1538 return UNKNOWN_ERROR;
1539 }
1540 }
1541
1542 const String8& featureOfBase = bundle->getFeatureOfPackage();
1543 if (!featureOfBase.isEmpty()) {
1544 if (bundle->getVerbose()) {
1545 printf("Including base feature resources from package: %s\n",
1546 featureOfBase.string());
1547 }
1548
1549 if (!mIncludedAssets.addAssetPath(featureOfBase, NULL)) {
1550 fprintf(stderr, "ERROR: base feature package '%s' not found.\n",
1551 featureOfBase.string());
1552 return UNKNOWN_ERROR;
1553 }
1554 }
1555
1556 mHaveIncludedAssets = true;
1557
Adam Lesinski282e1812014-01-23 18:17:42 -08001558 return NO_ERROR;
1559}
1560
1561status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file)
1562{
1563 const ResTable& res = getIncludedResources();
1564 // XXX dirty!
Narayan Kamath00b31442014-01-27 17:32:37 +00001565 return const_cast<ResTable&>(res).add(file->getData(), file->getSize());
Adam Lesinski282e1812014-01-23 18:17:42 -08001566}
1567
1568const ResTable& AaptAssets::getIncludedResources() const
1569{
1570 return mIncludedAssets.getResources(false);
1571}
1572
Adam Lesinskiad2d07d2014-08-27 16:21:08 -07001573AssetManager& AaptAssets::getAssetManager()
1574{
1575 return mIncludedAssets;
1576}
1577
Adam Lesinski282e1812014-01-23 18:17:42 -08001578void AaptAssets::print(const String8& prefix) const
1579{
1580 String8 innerPrefix(prefix);
1581 innerPrefix.append(" ");
1582 String8 innerInnerPrefix(innerPrefix);
1583 innerInnerPrefix.append(" ");
1584 printf("%sConfigurations:\n", prefix.string());
1585 const size_t N=mGroupEntries.size();
1586 for (size_t i=0; i<N; i++) {
1587 String8 cname = mGroupEntries.itemAt(i).toDirName(String8());
1588 printf("%s %s\n", prefix.string(),
1589 cname != "" ? cname.string() : "(default)");
1590 }
1591
1592 printf("\n%sFiles:\n", prefix.string());
1593 AaptDir::print(innerPrefix);
1594
1595 printf("\n%sResource Dirs:\n", prefix.string());
1596 const Vector<sp<AaptDir> >& resdirs = mResDirs;
1597 const size_t NR = resdirs.size();
1598 for (size_t i=0; i<NR; i++) {
1599 const sp<AaptDir>& d = resdirs.itemAt(i);
1600 printf("%s Type %s\n", prefix.string(), d->getLeaf().string());
1601 d->print(innerInnerPrefix);
1602 }
1603}
1604
1605sp<AaptDir> AaptAssets::resDir(const String8& name) const
1606{
1607 const Vector<sp<AaptDir> >& resdirs = mResDirs;
1608 const size_t N = resdirs.size();
1609 for (size_t i=0; i<N; i++) {
1610 const sp<AaptDir>& d = resdirs.itemAt(i);
1611 if (d->getLeaf() == name) {
1612 return d;
1613 }
1614 }
1615 return NULL;
1616}
1617
1618bool
1619valid_symbol_name(const String8& symbol)
1620{
1621 static char const * const KEYWORDS[] = {
1622 "abstract", "assert", "boolean", "break",
1623 "byte", "case", "catch", "char", "class", "const", "continue",
1624 "default", "do", "double", "else", "enum", "extends", "final",
1625 "finally", "float", "for", "goto", "if", "implements", "import",
1626 "instanceof", "int", "interface", "long", "native", "new", "package",
1627 "private", "protected", "public", "return", "short", "static",
1628 "strictfp", "super", "switch", "synchronized", "this", "throw",
1629 "throws", "transient", "try", "void", "volatile", "while",
1630 "true", "false", "null",
1631 NULL
1632 };
1633 const char*const* k = KEYWORDS;
1634 const char*const s = symbol.string();
1635 while (*k) {
1636 if (0 == strcmp(s, *k)) {
1637 return false;
1638 }
1639 k++;
1640 }
1641 return true;
1642}