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