2 // Copyright 2006 The Android Open Source Project
5 #include "AaptAssets.h"
6 #include "AaptConfig.h"
9 #include "ResourceFilter.h"
11 #include <utils/misc.h>
12 #include <utils/SortedVector.h>
18 static const char* kAssetDir = "assets";
19 static const char* kResourceDir = "res";
20 static const char* kValuesDir = "values";
21 static const char* kMipmapDir = "mipmap";
22 static const char* kInvalidChars = "/\\:";
23 static const size_t kMaxAssetFileName = 100;
25 static const String8 kResString(kResourceDir);
28 * Names of asset files must meet the following criteria:
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 { '/' '\\' ':' }
35 * Pass in just the filename, not the full path.
37 static bool validateFileName(const char* fileName)
39 const char* cp = fileName;
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
53 if (len < 1 || len > kMaxAssetFileName)
54 return false; // reject empty or too long
59 // The default to use if no other ignore pattern is defined.
60 const 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
63 const char * gUserIgnoreAssets = NULL;
65 static bool isHidden(const char *root, const char *path)
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.
80 if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
84 const char *delim = ":";
85 const char *p = gUserIgnoreAssets;
87 p = getenv("ANDROID_AAPT_IGNORE");
90 p = gDefaultIgnoreAssets;
92 char *patterns = strdup(p);
96 char *matchedPattern = NULL;
98 String8 fullPath(root);
99 fullPath.appendPath(path);
100 FileType type = getFileType(fullPath);
102 int plen = strlen(path);
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;
114 if (strncasecmp(token, "<file>", 6) == 0) {
115 if (type != kFileTypeRegular) continue;
119 matchedPattern = token;
120 int n = strlen(token);
122 if (token[0] == '*') {
127 ignore = strncasecmp(token, path + plen - n, n) == 0;
129 } else if (n > 1 && token[n - 1] == '*') {
131 ignore = strncasecmp(token, path, n - 1) == 0;
133 ignore = strcasecmp(token, path) == 0;
137 if (ignore && chatty) {
138 fprintf(stderr, " (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
139 type == kFileTypeDirectory ? "dir" : "file",
141 matchedPattern ? matchedPattern : "");
148 // =========================================================================
149 // =========================================================================
150 // =========================================================================
153 inline 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])) {
165 inline 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])) {
176 void AaptLocaleValue::setLanguage(const char* languageChars) {
178 while ((*languageChars) != '\0' && i < sizeof(language)/sizeof(language[0])) {
179 language[i++] = tolower(*languageChars);
184 void AaptLocaleValue::setRegion(const char* regionChars) {
186 while ((*regionChars) != '\0' && i < sizeof(region)/sizeof(region[0])) {
187 region[i++] = toupper(*regionChars);
192 void AaptLocaleValue::setScript(const char* scriptChars) {
194 while ((*scriptChars) != '\0' && i < sizeof(script)/sizeof(script[0])) {
196 script[i++] = toupper(*scriptChars);
198 script[i++] = tolower(*scriptChars);
204 void AaptLocaleValue::setVariant(const char* variantChars) {
206 while ((*variantChars) != '\0' && i < sizeof(variant)/sizeof(variant[0])) {
207 variant[i++] = *variantChars;
212 bool 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".
215 Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '_');
217 const int numTags = parts.size();
220 const String8& lang = parts[0];
221 if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
222 setLanguage(lang.string());
227 if (!valid || numTags == 1) {
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());
238 } else if (part2.length() >= 4 && part2.length() <= 8) {
239 setVariant(part2.string());
244 if (!valid || numTags == 2) {
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());
253 } else if (part3.length() >= 4 && part3.length() <= 8) {
254 setVariant(part3.string());
259 if (!valid || numTags == 3) {
263 const String8& part4 = parts[3];
264 if (part4.length() >= 4 && part4.length() <= 8) {
265 setVariant(part4.string());
270 if (!valid || numTags > 4) {
277 int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int startIndex) {
278 const int size = parts.size();
279 int currentIndex = startIndex;
281 String8 part = parts[currentIndex];
282 if (part[0] == 'b' && part[1] == '+') {
283 // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
284 // except that the separator is "+" and not "-".
285 Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+');
286 subtags.removeItemsAt(0);
287 if (subtags.size() == 1) {
288 setLanguage(subtags[0]);
289 } else if (subtags.size() == 2) {
290 setLanguage(subtags[0]);
292 // The second tag can either be a region, a variant or a script.
293 switch (subtags[1].size()) {
296 setRegion(subtags[1]);
299 if (isAlpha(subtags[1])) {
300 setScript(subtags[1]);
303 // This is not alphabetical, so we fall through to variant
308 setVariant(subtags[1]);
311 fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n",
315 } else if (subtags.size() == 3) {
316 // The language is always the first subtag.
317 setLanguage(subtags[0]);
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]);
328 fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string());
332 // The third tag can either be a region code (if the second tag was
333 // a script), else a variant code.
334 if (subtags[2].size() >= 4) {
335 setVariant(subtags[2]);
337 setRegion(subtags[2]);
339 } else if (subtags.size() == 4) {
340 setLanguage(subtags[0]);
341 setScript(subtags[1]);
342 setRegion(subtags[2]);
343 setVariant(subtags[3]);
345 fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name: %s\n", part.string());
349 return ++currentIndex;
351 if ((part.length() == 2 || part.length() == 3)
352 && isAlpha(part) && strcmp("car", part.string())) {
354 if (++currentIndex == size) {
361 part = parts[currentIndex];
362 if (part.string()[0] == 'r' && part.length() == 3) {
363 setRegion(part.string() + 1);
364 if (++currentIndex == size) {
373 void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
374 config.unpackLanguage(language);
375 config.unpackRegion(region);
376 if (config.localeScript[0] && !config.localeScriptWasComputed) {
377 memcpy(script, config.localeScript, sizeof(config.localeScript));
380 if (config.localeVariant[0]) {
381 memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
385 void AaptLocaleValue::writeTo(ResTable_config* out) const {
386 out->packLanguage(language);
387 out->packRegion(region);
390 memcpy(out->localeScript, script, sizeof(out->localeScript));
394 memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
399 AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
401 const char* q = strchr(dir, '-');
406 typeLen = strlen(dir);
409 String8 type(dir, typeLen);
410 if (!isValidResourceType(type)) {
415 if (!AaptConfig::parse(String8(q + 1), &mParams)) {
425 AaptGroupEntry::toDirName(const String8& resType) const
428 String8 params = mParams.toString();
429 if (params.length() > 0) {
430 if (s.length() > 0) {
439 // =========================================================================
440 // =========================================================================
441 // =========================================================================
443 void* AaptFile::editData(size_t size)
445 if (size <= mBufferSize) {
449 size_t allocSize = (size*3)/2;
450 void* buf = realloc(mData, allocSize);
456 mBufferSize = allocSize;
460 void* AaptFile::editDataInRange(size_t offset, size_t size)
462 return (void*)(((uint8_t*) editData(offset + size)) + offset);
465 void* AaptFile::editData(size_t* outSize)
468 *outSize = mDataSize;
473 void* AaptFile::padData(size_t wordSize)
475 const size_t extra = mDataSize%wordSize;
480 size_t initial = mDataSize;
481 void* data = editData(initial+(wordSize-extra));
483 memset(((uint8_t*)data) + initial, 0, wordSize-extra);
488 status_t AaptFile::writeData(const void* data, size_t size)
490 size_t end = mDataSize;
491 size_t total = size + end;
492 void* buf = editData(total);
494 return UNKNOWN_ERROR;
496 memcpy(((char*)buf)+end, data, size);
500 void AaptFile::clearData()
502 if (mData != NULL) free(mData);
508 String8 AaptFile::getPrintableSource() const
511 String8 name(mGroupEntry.toDirName(String8()));
512 name.appendPath(mPath);
513 name.append(" #generated");
519 // =========================================================================
520 // =========================================================================
521 // =========================================================================
523 status_t AaptGroup::addFile(const sp<AaptFile>& file, const bool overwriteDuplicate)
525 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());
536 mFiles.add(file->getGroupEntry(), file);
540 // Check if the version is automatically applied. This is a common source of
542 ConfigDescription withoutVersion = file->getGroupEntry().toParams();
543 withoutVersion.version = 0;
544 AaptConfig::applyVersionForCompatibility(&withoutVersion);
546 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." : "");
551 return UNKNOWN_ERROR;
554 void AaptGroup::removeFile(size_t index)
556 mFiles.removeItemsAt(index);
559 void AaptGroup::print(const String8& prefix) const
561 printf("%s%s\n", prefix.string(), getPath().string());
562 const size_t N=mFiles.size();
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());
571 printf("%s Src: (%s) %s\n", prefix.string(), e.toDirName(String8()).string(),
572 file->getPrintableSource().string());
574 //printf("%s File Group Entry: %s\n", prefix.string(),
575 // file->getGroupEntry().toDirName(String8()).string());
579 String8 AaptGroup::getPrintableSource() const
581 if (mFiles.size() > 0) {
582 // Arbitrarily pull the first source file out of the list.
583 return mFiles.valueAt(0)->getPrintableSource();
586 // Should never hit this case, but to be safe...
591 // =========================================================================
592 // =========================================================================
593 // =========================================================================
595 status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file)
597 if (mFiles.indexOfKey(name) >= 0) {
598 return ALREADY_EXISTS;
600 mFiles.add(name, file);
604 status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir)
606 if (mDirs.indexOfKey(name) >= 0) {
607 return ALREADY_EXISTS;
609 mDirs.add(name, dir);
613 sp<AaptDir> AaptDir::makeDir(const String8& path)
616 String8 remain = path;
618 sp<AaptDir> subdir = this;
619 while (name = remain.walkPath(&remain), remain != "") {
620 subdir = subdir->makeDir(name);
623 ssize_t i = subdir->mDirs.indexOfKey(name);
625 return subdir->mDirs.valueAt(i);
627 sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
628 subdir->mDirs.add(name, dir);
632 void AaptDir::removeFile(const String8& name)
634 mFiles.removeItem(name);
637 void AaptDir::removeDir(const String8& name)
639 mDirs.removeItem(name);
642 status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file,
643 const bool overwrite)
646 if (mFiles.indexOfKey(leafName) >= 0) {
647 group = mFiles.valueFor(leafName);
649 group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
650 mFiles.add(leafName, group);
653 return group->addFile(file, overwrite);
656 ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
657 const AaptGroupEntry& kind, const String8& resType,
658 sp<FilePathStore>& fullResPaths, const bool overwrite)
660 Vector<String8> fileNames;
664 dir = opendir(srcDir.string());
666 fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
667 return UNKNOWN_ERROR;
671 * Slurp the filenames out of the directory.
674 struct dirent* entry;
676 entry = readdir(dir);
680 if (isHidden(srcDir.string(), entry->d_name))
683 String8 name(entry->d_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));
697 * Stash away the files and recursively descend into subdirectories.
699 const size_t N = fileNames.size();
701 for (i = 0; i < N; i++) {
702 String8 pathName(srcDir);
705 pathName.appendPath(fileNames[i].string());
706 type = getFileType(pathName.string());
707 if (type == kFileTypeDirectory) {
709 bool notAdded = false;
710 if (mDirs.indexOfKey(fileNames[i]) >= 0) {
711 subdir = mDirs.valueFor(fileNames[i]);
713 subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
716 ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
717 resType, fullResPaths, overwrite);
718 if (res < NO_ERROR) {
721 if (res > 0 && notAdded) {
722 mDirs.add(fileNames[i], subdir);
725 } else if (type == kFileTypeRegular) {
726 sp<AaptFile> file = new AaptFile(pathName, kind, resType);
727 status_t err = addLeafFile(fileNames[i], file, overwrite);
728 if (err != NO_ERROR) {
735 if (bundle->getVerbose())
736 printf(" (ignoring non-file/dir '%s')\n", pathName.string());
743 status_t AaptDir::validate() const
745 const size_t NF = mFiles.size();
746 const size_t ND = mDirs.size();
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;
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;
765 // TODO: if ".gz", check for non-.gz; if non-, check for ".gz"
766 // (this is mostly caught by the "marked" stuff, below)
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;
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;
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;
798 status_t err = mDirs.valueAt(i)->validate();
799 if (err != NO_ERROR) {
807 void AaptDir::print(const String8& prefix) const
809 const size_t ND=getDirs().size();
811 for (i=0; i<ND; i++) {
812 getDirs().valueAt(i)->print(prefix);
815 const size_t NF=getFiles().size();
816 for (i=0; i<NF; i++) {
817 getFiles().valueAt(i)->print(prefix);
821 String8 AaptDir::getPrintableSource() const
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();
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();
832 // Should never hit this case, but to be safe...
837 // =========================================================================
838 // =========================================================================
839 // =========================================================================
841 status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols)
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);
850 entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string());
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;
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);
866 pos.error("Java symbol dir %s not defined\n", name.string());
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) {
880 // =========================================================================
881 // =========================================================================
882 // =========================================================================
884 AaptAssets::AaptAssets()
885 : AaptDir(String8(), String8()),
886 mHavePrivateSymbols(false),
887 mChanged(false), mHaveIncludedAssets(false),
890 const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
893 return mGroupEntries;
896 status_t AaptAssets::addFile(const String8& name, const sp<AaptGroup>& file)
899 return AaptDir::addFile(name, file);
902 sp<AaptFile> AaptAssets::addFile(
903 const String8& filePath, const AaptGroupEntry& entry,
904 const String8& srcDir, sp<AaptGroup>* outGroup,
905 const String8& resType)
907 sp<AaptDir> dir = this;
910 String8 root, remain(filePath), partialPath;
911 while (remain.length() > 0) {
912 root = remain.walkPath(&remain);
913 partialPath.appendPath(root);
915 const String8 rootStr(root);
917 if (remain.length() == 0) {
918 ssize_t i = dir->getFiles().indexOfKey(rootStr);
920 group = dir->getFiles().valueAt(i);
922 group = new AaptGroup(rootStr, filePath);
923 status_t res = dir->addFile(rootStr, group);
924 if (res != NO_ERROR) {
928 file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
929 status_t res = group->addFile(file);
930 if (res != NO_ERROR) {
936 ssize_t i = dir->getDirs().indexOfKey(rootStr);
938 dir = dir->getDirs().valueAt(i);
940 sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
941 status_t res = dir->addDir(rootStr, subdir);
942 if (res != NO_ERROR) {
950 mGroupEntries.add(entry);
951 if (outGroup) *outGroup = group;
955 void AaptAssets::addResource(const String8& leafName, const String8& path,
956 const sp<AaptFile>& file, const String8& resType)
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);
964 subdir->addFile(leafName, grr);
968 ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
973 const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
974 const size_t dirCount =resDirs.size();
975 sp<AaptAssets> current = this;
977 const int N = bundle->getFileSpecCount();
980 * If a package manifest was specified, include that first.
982 if (bundle->getAndroidManifestFile() != NULL) {
983 // place at root of zip.
984 String8 srcFile(bundle->getAndroidManifestFile());
985 addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
991 * If a directory of custom assets was supplied, slurp 'em up.
993 const Vector<const char*>& assetDirs = bundle->getAssetSourceDirs();
994 const int AN = assetDirs.size();
995 for (int i = 0; i < AN; i++) {
996 FileType type = getFileType(assetDirs[i]);
997 if (type == kFileTypeNonexistent) {
998 fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDirs[i]);
999 return UNKNOWN_ERROR;
1001 if (type != kFileTypeDirectory) {
1002 fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDirs[i]);
1003 return UNKNOWN_ERROR;
1006 String8 assetRoot(assetDirs[i]);
1007 sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
1008 AaptGroupEntry group;
1009 count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
1010 String8(), mFullAssetPaths, true);
1016 mGroupEntries.add(group);
1018 totalCount += count;
1020 if (bundle->getVerbose()) {
1021 printf("Found %d custom asset file%s in %s\n",
1022 count, (count==1) ? "" : "s", assetDirs[i]);
1027 * If a directory of resource-specific assets was supplied, slurp 'em up.
1029 for (size_t i=0; i<dirCount; i++) {
1030 const char *res = resDirs[i];
1032 type = getFileType(res);
1033 if (type == kFileTypeNonexistent) {
1034 fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
1035 return UNKNOWN_ERROR;
1037 if (type == kFileTypeDirectory) {
1039 sp<AaptAssets> nextOverlay = new AaptAssets();
1040 current->setOverlay(nextOverlay);
1041 current = nextOverlay;
1042 current->setFullResPaths(mFullResPaths);
1044 count = current->slurpResourceTree(bundle, String8(res));
1045 if (i > 0 && count > 0) {
1046 count = current->filter(bundle);
1053 totalCount += count;
1056 fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
1057 return UNKNOWN_ERROR;
1063 * Now do any additional raw files.
1065 for (int arg=0; arg<N; arg++) {
1066 const char* assetDir = bundle->getFileSpecEntry(arg);
1068 FileType type = getFileType(assetDir);
1069 if (type == kFileTypeNonexistent) {
1070 fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
1071 return UNKNOWN_ERROR;
1073 if (type != kFileTypeDirectory) {
1074 fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
1075 return UNKNOWN_ERROR;
1078 String8 assetRoot(assetDir);
1080 if (bundle->getVerbose())
1081 printf("Processing raw dir '%s'\n", (const char*) assetDir);
1084 * Do a recursive traversal of subdir tree. We don't make any
1085 * guarantees about ordering, so we're okay with an inorder search
1086 * using whatever order the OS happens to hand back to us.
1088 count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
1090 /* failure; report error and remove archive */
1094 totalCount += count;
1096 if (bundle->getVerbose())
1097 printf("Found %d asset file%s in %s\n",
1098 count, (count==1) ? "" : "s", assetDir);
1102 if (count != NO_ERROR) {
1107 count = filter(bundle);
1108 if (count != NO_ERROR) {
1117 ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
1118 const AaptGroupEntry& kind,
1119 const String8& resType,
1120 sp<FilePathStore>& fullResPaths,
1121 const bool overwrite)
1123 ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths, overwrite);
1125 mGroupEntries.add(kind);
1131 ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
1135 DIR* dir = opendir(srcDir.string());
1137 fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
1138 return UNKNOWN_ERROR;
1144 * Run through the directory, looking for dirs that match the
1148 struct dirent* entry = readdir(dir);
1149 if (entry == NULL) {
1153 if (isHidden(srcDir.string(), entry->d_name)) {
1157 String8 subdirName(srcDir);
1158 subdirName.appendPath(entry->d_name);
1160 AaptGroupEntry group;
1162 bool b = group.initFromDirName(entry->d_name, &resType);
1164 fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(),
1170 if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) {
1171 int maxResInt = atoi(bundle->getMaxResVersion());
1172 const char *verString = group.getVersionString().string();
1173 int dirVersionInt = atoi(verString + 1); // skip 'v' in version name
1174 if (dirVersionInt > maxResInt) {
1175 fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name);
1180 FileType type = getFileType(subdirName.string());
1182 if (type == kFileTypeDirectory) {
1183 sp<AaptDir> dir = makeDir(resType);
1184 ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
1185 resType, mFullResPaths);
1191 mGroupEntries.add(group);
1195 // Only add this directory if we don't already have a resource dir
1196 // for the current type. This ensures that we only add the dir once
1198 sp<AaptDir> rdir = resDir(resType);
1203 if (bundle->getVerbose()) {
1204 fprintf(stderr, " (ignoring file '%s')\n", subdirName.string());
1220 AaptAssets::slurpResourceZip(Bundle* /* bundle */, const char* filename)
1223 SortedVector<AaptGroupEntry> entries;
1225 ZipFile* zip = new ZipFile;
1226 status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
1227 if (err != NO_ERROR) {
1228 fprintf(stderr, "error opening zip file %s\n", filename);
1234 const int N = zip->getNumEntries();
1235 for (int i=0; i<N; i++) {
1236 ZipEntry* entry = zip->getEntryByIndex(i);
1237 if (entry->getDeleted()) {
1241 String8 entryName(entry->getFileName());
1243 String8 dirName = entryName.getPathDir();
1244 sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
1247 AaptGroupEntry kind;
1250 if (entryName.walkPath(&remain) == kResourceDir) {
1251 // these are the resources, pull their type out of the directory name
1252 kind.initFromDirName(remain.walkPath().string(), &resType);
1254 // these are untyped and don't have an AaptGroupEntry
1256 if (entries.indexOf(kind) < 0) {
1258 mGroupEntries.add(kind);
1261 // use the one from the zip file if they both exist.
1262 dir->removeFile(entryName.getPathLeaf());
1264 sp<AaptFile> file = new AaptFile(entryName, kind, resType);
1265 status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
1266 if (err != NO_ERROR) {
1267 fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
1271 file->setCompressionMethod(entry->getCompressionMethod());
1274 if (entryName == "AndroidManifest.xml") {
1275 printf("AndroidManifest.xml\n");
1277 printf("\n\nfile: %s\n", entryName.string());
1280 size_t len = entry->getUncompressedLen();
1281 void* data = zip->uncompress(entry);
1282 void* buf = file->editData(len);
1283 memcpy(buf, data, len);
1287 const unsigned char* p = (unsigned char*)data;
1288 const unsigned char* end = p+len;
1290 for (int i=0; i<32 && p < end; i++) {
1291 printf("0x%03x ", i*0x10 + OFF);
1292 for (int j=0; j<0x10 && p < end; j++) {
1293 printf(" %02x", *p);
1310 status_t AaptAssets::filter(Bundle* bundle)
1312 WeakResourceFilter reqFilter;
1313 status_t err = reqFilter.parse(bundle->getConfigurations());
1314 if (err != NO_ERROR) {
1318 uint32_t preferredDensity = 0;
1319 if (bundle->getPreferredDensity().size() > 0) {
1320 ResTable_config preferredConfig;
1321 if (!AaptConfig::parseDensity(bundle->getPreferredDensity().string(), &preferredConfig)) {
1322 fprintf(stderr, "Error parsing preferred density: %s\n",
1323 bundle->getPreferredDensity().string());
1324 return UNKNOWN_ERROR;
1326 preferredDensity = preferredConfig.density;
1329 if (reqFilter.isEmpty() && preferredDensity == 0) {
1333 if (bundle->getVerbose()) {
1334 if (!reqFilter.isEmpty()) {
1335 printf("Applying required filter: %s\n",
1336 bundle->getConfigurations().string());
1338 if (preferredDensity > 0) {
1339 printf("Applying preferred density filter: %s\n",
1340 bundle->getPreferredDensity().string());
1344 const Vector<sp<AaptDir> >& resdirs = mResDirs;
1345 const size_t ND = resdirs.size();
1346 for (size_t i=0; i<ND; i++) {
1347 const sp<AaptDir>& dir = resdirs.itemAt(i);
1348 if (dir->getLeaf() == kValuesDir) {
1349 // The "value" dir is special since a single file defines
1350 // multiple resources, so we can not do filtering on the
1351 // files themselves.
1354 if (dir->getLeaf() == kMipmapDir) {
1355 // We also skip the "mipmap" directory, since the point of this
1356 // is to include all densities without stripping. If you put
1357 // other configurations in here as well they won't be stripped
1358 // either... So don't do that. Seriously. What is wrong with you?
1362 const size_t NG = dir->getFiles().size();
1363 for (size_t j=0; j<NG; j++) {
1364 sp<AaptGroup> grp = dir->getFiles().valueAt(j);
1366 // First remove any configurations we know we don't need.
1367 for (size_t k=0; k<grp->getFiles().size(); k++) {
1368 sp<AaptFile> file = grp->getFiles().valueAt(k);
1369 if (k == 0 && grp->getFiles().size() == 1) {
1370 // If this is the only file left, we need to keep it.
1371 // Otherwise the resource IDs we are using will be inconsistent
1372 // with what we get when not stripping. Sucky, but at least
1373 // for now we can rely on the back-end doing another filtering
1374 // pass to take this out and leave us with this resource name
1375 // containing no entries.
1378 if (file->getPath().getPathExtension() == ".xml") {
1379 // We can't remove .xml files at this point, because when
1380 // we parse them they may add identifier resources, so
1381 // removing them can cause our resource identifiers to
1382 // become inconsistent.
1385 const ResTable_config& config(file->getGroupEntry().toParams());
1386 if (!reqFilter.match(config)) {
1387 if (bundle->getVerbose()) {
1388 printf("Pruning unneeded resource: %s\n",
1389 file->getPrintableSource().string());
1396 // Quick check: no preferred filters, nothing more to do.
1397 if (preferredDensity == 0) {
1401 // Get the preferred density if there is one. We do not match exactly for density.
1402 // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we
1404 for (size_t k=0; k<grp->getFiles().size(); k++) {
1405 sp<AaptFile> file = grp->getFiles().valueAt(k);
1406 if (k == 0 && grp->getFiles().size() == 1) {
1407 // If this is the only file left, we need to keep it.
1408 // Otherwise the resource IDs we are using will be inconsistent
1409 // with what we get when not stripping. Sucky, but at least
1410 // for now we can rely on the back-end doing another filtering
1411 // pass to take this out and leave us with this resource name
1412 // containing no entries.
1415 if (file->getPath().getPathExtension() == ".xml") {
1416 // We can't remove .xml files at this point, because when
1417 // we parse them they may add identifier resources, so
1418 // removing them can cause our resource identifiers to
1419 // become inconsistent.
1422 const ResTable_config& config(file->getGroupEntry().toParams());
1423 if (config.density != 0 && config.density != preferredDensity) {
1424 // This is a resource we would prefer not to have. Check
1425 // to see if have a similar variation that we would like
1426 // to have and, if so, we can drop it.
1427 uint32_t bestDensity = config.density;
1429 for (size_t m=0; m<grp->getFiles().size(); m++) {
1434 sp<AaptFile> mfile = grp->getFiles().valueAt(m);
1435 const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
1436 if (AaptConfig::isSameExcept(config, mconfig, ResTable_config::CONFIG_DENSITY)) {
1437 // See if there is a better density resource
1438 if (mconfig.density < bestDensity &&
1439 mconfig.density >= preferredDensity &&
1440 bestDensity > preferredDensity) {
1441 // This density is our preferred density, or between our best density and
1442 // the preferred density, therefore it is better.
1443 bestDensity = mconfig.density;
1444 } else if (mconfig.density > bestDensity &&
1445 bestDensity < preferredDensity) {
1446 // This density is better than our best density and
1447 // our best density was smaller than our preferred
1448 // density, so it is better.
1449 bestDensity = mconfig.density;
1454 if (bestDensity != config.density) {
1455 if (bundle->getVerbose()) {
1456 printf("Pruning unneeded resource: %s\n",
1457 file->getPrintableSource().string());
1470 sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name)
1472 sp<AaptSymbols> sym = mSymbols.valueFor(name);
1474 sym = new AaptSymbols();
1475 mSymbols.add(name, sym);
1480 sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name)
1482 sp<AaptSymbols> sym = mJavaSymbols.valueFor(name);
1484 sym = new AaptSymbols();
1485 mJavaSymbols.add(name, sym);
1490 status_t AaptAssets::applyJavaSymbols()
1492 size_t N = mJavaSymbols.size();
1493 for (size_t i=0; i<N; i++) {
1494 const String8& name = mJavaSymbols.keyAt(i);
1495 const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i);
1496 ssize_t pos = mSymbols.indexOfKey(name);
1499 pos.error("Java symbol dir %s not defined\n", name.string());
1500 return UNKNOWN_ERROR;
1502 //printf("**** applying java symbols in dir %s\n", name.string());
1503 status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols);
1504 if (err != NO_ERROR) {
1512 bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const {
1513 //printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n",
1514 // sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0,
1515 // sym.isJavaSymbol ? 1 : 0);
1516 if (!mHavePrivateSymbols) return true;
1517 if (sym.isPublic) return true;
1518 if (includePrivate && sym.isJavaSymbol) return true;
1522 status_t AaptAssets::buildIncludedResources(Bundle* bundle)
1524 if (mHaveIncludedAssets) {
1528 // Add in all includes.
1529 const Vector<String8>& includes = bundle->getPackageIncludes();
1530 const size_t packageIncludeCount = includes.size();
1531 for (size_t i = 0; i < packageIncludeCount; i++) {
1532 if (bundle->getVerbose()) {
1533 printf("Including resources from package: %s\n", includes[i].string());
1536 if (!mIncludedAssets.addAssetPath(includes[i], NULL)) {
1537 fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
1538 includes[i].string());
1539 return UNKNOWN_ERROR;
1543 const String8& featureOfBase = bundle->getFeatureOfPackage();
1544 if (!featureOfBase.isEmpty()) {
1545 if (bundle->getVerbose()) {
1546 printf("Including base feature resources from package: %s\n",
1547 featureOfBase.string());
1550 if (!mIncludedAssets.addAssetPath(featureOfBase, NULL)) {
1551 fprintf(stderr, "ERROR: base feature package '%s' not found.\n",
1552 featureOfBase.string());
1553 return UNKNOWN_ERROR;
1557 mHaveIncludedAssets = true;
1562 status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file)
1564 const ResTable& res = getIncludedResources();
1566 return const_cast<ResTable&>(res).add(file->getData(), file->getSize());
1569 const ResTable& AaptAssets::getIncludedResources() const
1571 return mIncludedAssets.getResources(false);
1574 AssetManager& AaptAssets::getAssetManager()
1576 return mIncludedAssets;
1579 void AaptAssets::print(const String8& prefix) const
1581 String8 innerPrefix(prefix);
1582 innerPrefix.append(" ");
1583 String8 innerInnerPrefix(innerPrefix);
1584 innerInnerPrefix.append(" ");
1585 printf("%sConfigurations:\n", prefix.string());
1586 const size_t N=mGroupEntries.size();
1587 for (size_t i=0; i<N; i++) {
1588 String8 cname = mGroupEntries.itemAt(i).toDirName(String8());
1589 printf("%s %s\n", prefix.string(),
1590 cname != "" ? cname.string() : "(default)");
1593 printf("\n%sFiles:\n", prefix.string());
1594 AaptDir::print(innerPrefix);
1596 printf("\n%sResource Dirs:\n", prefix.string());
1597 const Vector<sp<AaptDir> >& resdirs = mResDirs;
1598 const size_t NR = resdirs.size();
1599 for (size_t i=0; i<NR; i++) {
1600 const sp<AaptDir>& d = resdirs.itemAt(i);
1601 printf("%s Type %s\n", prefix.string(), d->getLeaf().string());
1602 d->print(innerInnerPrefix);
1606 sp<AaptDir> AaptAssets::resDir(const String8& name) const
1608 const Vector<sp<AaptDir> >& resdirs = mResDirs;
1609 const size_t N = resdirs.size();
1610 for (size_t i=0; i<N; i++) {
1611 const sp<AaptDir>& d = resdirs.itemAt(i);
1612 if (d->getLeaf() == name) {
1620 valid_symbol_name(const String8& symbol)
1622 static char const * const KEYWORDS[] = {
1623 "abstract", "assert", "boolean", "break",
1624 "byte", "case", "catch", "char", "class", "const", "continue",
1625 "default", "do", "double", "else", "enum", "extends", "final",
1626 "finally", "float", "for", "goto", "if", "implements", "import",
1627 "instanceof", "int", "interface", "long", "native", "new", "package",
1628 "private", "protected", "public", "return", "short", "static",
1629 "strictfp", "super", "switch", "synchronized", "this", "throw",
1630 "throws", "transient", "try", "void", "volatile", "while",
1631 "true", "false", "null",
1634 const char*const* k = KEYWORDS;
1635 const char*const s = symbol.string();
1637 if (0 == strcmp(s, *k)) {