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