• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2006 The Android Open Source Project
3 //
4 // Build resource files from raw assets.
5 //
6 #include "Main.h"
7 #include "AaptAssets.h"
8 #include "StringPool.h"
9 #include "XMLNode.h"
10 #include "ResourceTable.h"
11 #include "Images.h"
12 
13 #define NOISY(x) // x
14 
15 // ==========================================================================
16 // ==========================================================================
17 // ==========================================================================
18 
19 class PackageInfo
20 {
21 public:
PackageInfo()22     PackageInfo()
23     {
24     }
~PackageInfo()25     ~PackageInfo()
26     {
27     }
28 
29     status_t parsePackage(const sp<AaptGroup>& grp);
30 };
31 
32 // ==========================================================================
33 // ==========================================================================
34 // ==========================================================================
35 
parseResourceName(const String8 & leaf)36 static String8 parseResourceName(const String8& leaf)
37 {
38     const char* firstDot = strchr(leaf.string(), '.');
39     const char* str = leaf.string();
40 
41     if (firstDot) {
42         return String8(str, firstDot-str);
43     } else {
44         return String8(str);
45     }
46 }
47 
ResourceTypeSet()48 ResourceTypeSet::ResourceTypeSet()
49     :RefBase(),
50      KeyedVector<String8,sp<AaptGroup> >()
51 {
52 }
53 
54 class ResourceDirIterator
55 {
56 public:
ResourceDirIterator(const sp<ResourceTypeSet> & set,const String8 & resType)57     ResourceDirIterator(const sp<ResourceTypeSet>& set, const String8& resType)
58         : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0)
59     {
60     }
61 
getGroup() const62     inline const sp<AaptGroup>& getGroup() const { return mGroup; }
getFile() const63     inline const sp<AaptFile>& getFile() const { return mFile; }
64 
getBaseName() const65     inline const String8& getBaseName() const { return mBaseName; }
getLeafName() const66     inline const String8& getLeafName() const { return mLeafName; }
getPath() const67     inline String8 getPath() const { return mPath; }
getParams() const68     inline const ResTable_config& getParams() const { return mParams; }
69 
70     enum {
71         EOD = 1
72     };
73 
next()74     ssize_t next()
75     {
76         while (true) {
77             sp<AaptGroup> group;
78             sp<AaptFile> file;
79 
80             // Try to get next file in this current group.
81             if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) {
82                 group = mGroup;
83                 file = group->getFiles().valueAt(mGroupPos++);
84 
85             // Try to get the next group/file in this directory
86             } else if (mSetPos < mSet->size()) {
87                 mGroup = group = mSet->valueAt(mSetPos++);
88                 if (group->getFiles().size() < 1) {
89                     continue;
90                 }
91                 file = group->getFiles().valueAt(0);
92                 mGroupPos = 1;
93 
94             // All done!
95             } else {
96                 return EOD;
97             }
98 
99             mFile = file;
100 
101             String8 leaf(group->getLeaf());
102             mLeafName = String8(leaf);
103             mParams = file->getGroupEntry().toParams();
104             NOISY(printf("Dir %s: mcc=%d mnc=%d lang=%c%c cnt=%c%c orient=%d density=%d touch=%d key=%d inp=%d nav=%d\n",
105                    group->getPath().string(), mParams.mcc, mParams.mnc,
106                    mParams.language[0] ? mParams.language[0] : '-',
107                    mParams.language[1] ? mParams.language[1] : '-',
108                    mParams.country[0] ? mParams.country[0] : '-',
109                    mParams.country[1] ? mParams.country[1] : '-',
110                    mParams.orientation,
111                    mParams.density, mParams.touchscreen, mParams.keyboard,
112                    mParams.inputFlags, mParams.navigation));
113             mPath = "res";
114             mPath.appendPath(file->getGroupEntry().toDirName(mResType));
115             mPath.appendPath(leaf);
116             mBaseName = parseResourceName(leaf);
117             if (mBaseName == "") {
118                 fprintf(stderr, "Error: malformed resource filename %s\n",
119                         file->getPrintableSource().string());
120                 return UNKNOWN_ERROR;
121             }
122 
123             NOISY(printf("file name=%s\n", mBaseName.string()));
124 
125             return NO_ERROR;
126         }
127     }
128 
129 private:
130     String8 mResType;
131 
132     const sp<ResourceTypeSet> mSet;
133     size_t mSetPos;
134 
135     sp<AaptGroup> mGroup;
136     size_t mGroupPos;
137 
138     sp<AaptFile> mFile;
139     String8 mBaseName;
140     String8 mLeafName;
141     String8 mPath;
142     ResTable_config mParams;
143 };
144 
145 // ==========================================================================
146 // ==========================================================================
147 // ==========================================================================
148 
isValidResourceType(const String8 & type)149 bool isValidResourceType(const String8& type)
150 {
151     return type == "anim" || type == "drawable" || type == "layout"
152         || type == "values" || type == "xml" || type == "raw"
153         || type == "color" || type == "menu";
154 }
155 
getResourceFile(const sp<AaptAssets> & assets,bool makeIfNecessary=true)156 static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true)
157 {
158     sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc"));
159     sp<AaptFile> file;
160     if (group != NULL) {
161         file = group->getFiles().valueFor(AaptGroupEntry());
162         if (file != NULL) {
163             return file;
164         }
165     }
166 
167     if (!makeIfNecessary) {
168         return NULL;
169     }
170     return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(),
171                             NULL, String8());
172 }
173 
parsePackage(const sp<AaptAssets> & assets,const sp<AaptGroup> & grp)174 static status_t parsePackage(const sp<AaptAssets>& assets, const sp<AaptGroup>& grp)
175 {
176     if (grp->getFiles().size() != 1) {
177         fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
178                 grp->getFiles().valueAt(0)->getPrintableSource().string());
179     }
180 
181     sp<AaptFile> file = grp->getFiles().valueAt(0);
182 
183     ResXMLTree block;
184     status_t err = parseXMLResource(file, &block);
185     if (err != NO_ERROR) {
186         return err;
187     }
188     //printXMLBlock(&block);
189 
190     ResXMLTree::event_code_t code;
191     while ((code=block.next()) != ResXMLTree::START_TAG
192            && code != ResXMLTree::END_DOCUMENT
193            && code != ResXMLTree::BAD_DOCUMENT) {
194     }
195 
196     size_t len;
197     if (code != ResXMLTree::START_TAG) {
198         fprintf(stderr, "%s:%d: No start tag found\n",
199                 file->getPrintableSource().string(), block.getLineNumber());
200         return UNKNOWN_ERROR;
201     }
202     if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
203         fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
204                 file->getPrintableSource().string(), block.getLineNumber(),
205                 String8(block.getElementName(&len)).string());
206         return UNKNOWN_ERROR;
207     }
208 
209     ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
210     if (nameIndex < 0) {
211         fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
212                 file->getPrintableSource().string(), block.getLineNumber());
213         return UNKNOWN_ERROR;
214     }
215 
216     assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
217 
218     return NO_ERROR;
219 }
220 
221 // ==========================================================================
222 // ==========================================================================
223 // ==========================================================================
224 
makeFileResources(Bundle * bundle,const sp<AaptAssets> & assets,ResourceTable * table,const sp<ResourceTypeSet> & set,const char * resType)225 static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
226                                   ResourceTable* table,
227                                   const sp<ResourceTypeSet>& set,
228                                   const char* resType)
229 {
230     String8 type8(resType);
231     String16 type16(resType);
232 
233     bool hasErrors = false;
234 
235     ResourceDirIterator it(set, String8(resType));
236     ssize_t res;
237     while ((res=it.next()) == NO_ERROR) {
238         if (bundle->getVerbose()) {
239             printf("    (new resource id %s from %s)\n",
240                    it.getBaseName().string(), it.getFile()->getPrintableSource().string());
241         }
242         String16 baseName(it.getBaseName());
243         const char16_t* str = baseName.string();
244         const char16_t* const end = str + baseName.size();
245         while (str < end) {
246             if (!((*str >= 'a' && *str <= 'z')
247                     || (*str >= '0' && *str <= '9')
248                     || *str == '_' || *str == '.')) {
249                 fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
250                         it.getPath().string());
251                 hasErrors = true;
252             }
253             str++;
254         }
255         String8 resPath = it.getPath();
256         resPath.convertToResPath();
257         table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
258                         type16,
259                         baseName,
260                         String16(resPath),
261                         NULL,
262                         &it.getParams());
263         assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
264     }
265 
266     return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
267 }
268 
preProcessImages(Bundle * bundle,const sp<AaptAssets> & assets,const sp<ResourceTypeSet> & set)269 static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets,
270                           const sp<ResourceTypeSet>& set)
271 {
272     ResourceDirIterator it(set, String8("drawable"));
273     Vector<sp<AaptFile> > newNameFiles;
274     Vector<String8> newNamePaths;
275     bool hasErrors = false;
276     ssize_t res;
277     while ((res=it.next()) == NO_ERROR) {
278         res = preProcessImage(bundle, assets, it.getFile(), NULL);
279         if (res < NO_ERROR) {
280             hasErrors = true;
281         }
282     }
283 
284     return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
285 }
286 
postProcessImages(const sp<AaptAssets> & assets,ResourceTable * table,const sp<ResourceTypeSet> & set)287 status_t postProcessImages(const sp<AaptAssets>& assets,
288                            ResourceTable* table,
289                            const sp<ResourceTypeSet>& set)
290 {
291     ResourceDirIterator it(set, String8("drawable"));
292     bool hasErrors = false;
293     ssize_t res;
294     while ((res=it.next()) == NO_ERROR) {
295         res = postProcessImage(assets, table, it.getFile());
296         if (res < NO_ERROR) {
297             hasErrors = true;
298         }
299     }
300 
301     return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
302 }
303 
collect_files(const sp<AaptDir> & dir,KeyedVector<String8,sp<ResourceTypeSet>> * resources)304 static void collect_files(const sp<AaptDir>& dir,
305         KeyedVector<String8, sp<ResourceTypeSet> >* resources)
306 {
307     const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
308     int N = groups.size();
309     for (int i=0; i<N; i++) {
310         String8 leafName = groups.keyAt(i);
311         const sp<AaptGroup>& group = groups.valueAt(i);
312 
313         const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
314                 = group->getFiles();
315 
316         if (files.size() == 0) {
317             continue;
318         }
319 
320         String8 resType = files.valueAt(0)->getResourceType();
321 
322         ssize_t index = resources->indexOfKey(resType);
323 
324         if (index < 0) {
325             sp<ResourceTypeSet> set = new ResourceTypeSet();
326             set->add(leafName, group);
327             resources->add(resType, set);
328         } else {
329             sp<ResourceTypeSet> set = resources->valueAt(index);
330             index = set->indexOfKey(leafName);
331             if (index < 0) {
332                 set->add(leafName, group);
333             } else {
334                 sp<AaptGroup> existingGroup = set->valueAt(index);
335                 int M = files.size();
336                 for (int j=0; j<M; j++) {
337                     existingGroup->addFile(files.valueAt(j));
338                 }
339             }
340         }
341     }
342 }
343 
collect_files(const sp<AaptAssets> & ass,KeyedVector<String8,sp<ResourceTypeSet>> * resources)344 static void collect_files(const sp<AaptAssets>& ass,
345         KeyedVector<String8, sp<ResourceTypeSet> >* resources)
346 {
347     const Vector<sp<AaptDir> >& dirs = ass->resDirs();
348     int N = dirs.size();
349 
350     for (int i=0; i<N; i++) {
351         sp<AaptDir> d = dirs.itemAt(i);
352         collect_files(d, resources);
353 
354         // don't try to include the res dir
355         ass->removeDir(d->getLeaf());
356     }
357 }
358 
359 enum {
360     ATTR_OKAY = -1,
361     ATTR_NOT_FOUND = -2,
362     ATTR_LEADING_SPACES = -3,
363     ATTR_TRAILING_SPACES = -4
364 };
validateAttr(const String8 & path,const ResXMLParser & parser,const char * ns,const char * attr,const char * validChars,bool required)365 static int validateAttr(const String8& path, const ResXMLParser& parser,
366         const char* ns, const char* attr, const char* validChars, bool required)
367 {
368     size_t len;
369 
370     ssize_t index = parser.indexOfAttribute(ns, attr);
371     const uint16_t* str;
372     if (index >= 0 && (str=parser.getAttributeStringValue(index, &len)) != NULL) {
373         if (validChars) {
374             for (size_t i=0; i<len; i++) {
375                 uint16_t c = str[i];
376                 const char* p = validChars;
377                 bool okay = false;
378                 while (*p) {
379                     if (c == *p) {
380                         okay = true;
381                         break;
382                     }
383                     p++;
384                 }
385                 if (!okay) {
386                     fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid character '%c'.\n",
387                             path.string(), parser.getLineNumber(),
388                             String8(parser.getElementName(&len)).string(), attr, (char)str[i]);
389                     return (int)i;
390                 }
391             }
392         }
393         if (*str == ' ') {
394             fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not start with a space.\n",
395                     path.string(), parser.getLineNumber(),
396                     String8(parser.getElementName(&len)).string(), attr);
397             return ATTR_LEADING_SPACES;
398         }
399         if (str[len-1] == ' ') {
400             fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not end with a space.\n",
401                     path.string(), parser.getLineNumber(),
402                     String8(parser.getElementName(&len)).string(), attr);
403             return ATTR_TRAILING_SPACES;
404         }
405         return ATTR_OKAY;
406     }
407     if (required) {
408         fprintf(stderr, "%s:%d: Tag <%s> missing required attribute %s.\n",
409                 path.string(), parser.getLineNumber(),
410                 String8(parser.getElementName(&len)).string(), attr);
411         return ATTR_NOT_FOUND;
412     }
413     return ATTR_OKAY;
414 }
415 
checkForIds(const String8 & path,ResXMLParser & parser)416 static void checkForIds(const String8& path, ResXMLParser& parser)
417 {
418     ResXMLTree::event_code_t code;
419     while ((code=parser.next()) != ResXMLTree::END_DOCUMENT
420            && code > ResXMLTree::BAD_DOCUMENT) {
421         if (code == ResXMLTree::START_TAG) {
422             ssize_t index = parser.indexOfAttribute(NULL, "id");
423             if (index >= 0) {
424                 fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n",
425                         path.string(), parser.getLineNumber());
426             }
427         }
428     }
429 }
430 
applyFileOverlay(Bundle * bundle,const sp<AaptAssets> & assets,const sp<ResourceTypeSet> & baseSet,const char * resType)431 static bool applyFileOverlay(Bundle *bundle,
432                              const sp<AaptAssets>& assets,
433                              const sp<ResourceTypeSet>& baseSet,
434                              const char *resType)
435 {
436     if (bundle->getVerbose()) {
437         printf("applyFileOverlay for %s\n", resType);
438     }
439 
440     // Replace any base level files in this category with any found from the overlay
441     // Also add any found only in the overlay.
442     sp<AaptAssets> overlay = assets->getOverlay();
443     String8 resTypeString(resType);
444 
445     // work through the linked list of overlays
446     while (overlay.get()) {
447         KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources();
448 
449         // get the overlay resources of the requested type
450         ssize_t index = overlayRes->indexOfKey(resTypeString);
451         if (index >= 0) {
452             sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index);
453 
454             // for each of the resources, check for a match in the previously built
455             // non-overlay "baseset".
456             size_t overlayCount = overlaySet->size();
457             for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) {
458                 if (bundle->getVerbose()) {
459                     printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string());
460                 }
461                 size_t baseIndex = baseSet->indexOfKey(overlaySet->keyAt(overlayIndex));
462                 if (baseIndex < UNKNOWN_ERROR) {
463                     // look for same flavor.  For a given file (strings.xml, for example)
464                     // there may be a locale specific or other flavors - we want to match
465                     // the same flavor.
466                     sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
467                     sp<AaptGroup> baseGroup = baseSet->valueAt(baseIndex);
468 
469                     DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
470                             overlayGroup->getFiles();
471                     if (bundle->getVerbose()) {
472                         DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
473                                 baseGroup->getFiles();
474                         for (size_t i=0; i < baseFiles.size(); i++) {
475                             printf("baseFile %d has flavor %s\n", i,
476                                     baseFiles.keyAt(i).toString().string());
477                         }
478                         for (size_t i=0; i < overlayFiles.size(); i++) {
479                             printf("overlayFile %d has flavor %s\n", i,
480                                     overlayFiles.keyAt(i).toString().string());
481                         }
482                     }
483 
484                     size_t overlayGroupSize = overlayFiles.size();
485                     for (size_t overlayGroupIndex = 0;
486                             overlayGroupIndex<overlayGroupSize;
487                             overlayGroupIndex++) {
488                         size_t baseFileIndex =
489                                 baseGroup->getFiles().indexOfKey(overlayFiles.
490                                 keyAt(overlayGroupIndex));
491                         if(baseFileIndex < UNKNOWN_ERROR) {
492                             if (bundle->getVerbose()) {
493                                 printf("found a match (%d) for overlay file %s, for flavor %s\n",
494                                         baseFileIndex,
495                                         overlayGroup->getLeaf().string(),
496                                         overlayFiles.keyAt(overlayGroupIndex).toString().string());
497                             }
498                             baseGroup->removeFile(baseFileIndex);
499                         } else {
500                             // didn't find a match fall through and add it..
501                         }
502                         baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex));
503                         assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
504                     }
505                 } else {
506                     // this group doesn't exist (a file that's only in the overlay)
507                     baseSet->add(overlaySet->keyAt(overlayIndex),
508                             overlaySet->valueAt(overlayIndex));
509                     // make sure all flavors are defined in the resources.
510                     sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
511                     DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
512                             overlayGroup->getFiles();
513                     size_t overlayGroupSize = overlayFiles.size();
514                     for (size_t overlayGroupIndex = 0;
515                             overlayGroupIndex<overlayGroupSize;
516                             overlayGroupIndex++) {
517                         assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
518                     }
519                 }
520             }
521             // this overlay didn't have resources for this type
522         }
523         // try next overlay
524         overlay = overlay->getOverlay();
525     }
526     return true;
527 }
528 
addTagAttribute(const sp<XMLNode> & node,const char * ns8,const char * attr8,const char * value)529 void addTagAttribute(const sp<XMLNode>& node, const char* ns8,
530         const char* attr8, const char* value)
531 {
532     if (value == NULL) {
533         return;
534     }
535 
536     const String16 ns(ns8);
537     const String16 attr(attr8);
538 
539     if (node->getAttribute(ns, attr) != NULL) {
540         fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s)\n",
541                 String8(attr).string(), String8(ns).string());
542         return;
543     }
544 
545     node->addAttribute(ns, attr, String16(value));
546 }
547 
massageManifest(Bundle * bundle,sp<XMLNode> root)548 status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
549 {
550     root = root->searchElement(String16(), String16("manifest"));
551     if (root == NULL) {
552         fprintf(stderr, "No <manifest> tag.\n");
553         return UNKNOWN_ERROR;
554     }
555 
556     addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode",
557             bundle->getVersionCode());
558     addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName",
559             bundle->getVersionName());
560 
561     if (bundle->getMinSdkVersion() != NULL
562             || bundle->getTargetSdkVersion() != NULL
563             || bundle->getMaxSdkVersion() != NULL) {
564         sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk"));
565         if (vers == NULL) {
566             vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk"));
567             root->insertChildAt(vers, 0);
568         }
569 
570         addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion",
571                 bundle->getMinSdkVersion());
572         addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "targetSdkVersion",
573                 bundle->getTargetSdkVersion());
574         addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "maxSdkVersion",
575                 bundle->getMaxSdkVersion());
576     }
577 
578     return NO_ERROR;
579 }
580 
581 #define ASSIGN_IT(n) \
582         do { \
583             ssize_t index = resources->indexOfKey(String8(#n)); \
584             if (index >= 0) { \
585                 n ## s = resources->valueAt(index); \
586             } \
587         } while (0)
588 
buildResources(Bundle * bundle,const sp<AaptAssets> & assets)589 status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
590 {
591     // First, look for a package file to parse.  This is required to
592     // be able to generate the resource information.
593     sp<AaptGroup> androidManifestFile =
594             assets->getFiles().valueFor(String8("AndroidManifest.xml"));
595     if (androidManifestFile == NULL) {
596         fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
597         return UNKNOWN_ERROR;
598     }
599 
600     status_t err = parsePackage(assets, androidManifestFile);
601     if (err != NO_ERROR) {
602         return err;
603     }
604 
605     NOISY(printf("Creating resources for package %s\n",
606                  assets->getPackage().string()));
607 
608     ResourceTable table(bundle, String16(assets->getPackage()));
609     err = table.addIncludedResources(bundle, assets);
610     if (err != NO_ERROR) {
611         return err;
612     }
613 
614     NOISY(printf("Found %d included resource packages\n", (int)table.size()));
615 
616     // --------------------------------------------------------------
617     // First, gather all resource information.
618     // --------------------------------------------------------------
619 
620     // resType -> leafName -> group
621     KeyedVector<String8, sp<ResourceTypeSet> > *resources =
622             new KeyedVector<String8, sp<ResourceTypeSet> >;
623     collect_files(assets, resources);
624 
625     sp<ResourceTypeSet> drawables;
626     sp<ResourceTypeSet> layouts;
627     sp<ResourceTypeSet> anims;
628     sp<ResourceTypeSet> xmls;
629     sp<ResourceTypeSet> raws;
630     sp<ResourceTypeSet> colors;
631     sp<ResourceTypeSet> menus;
632 
633     ASSIGN_IT(drawable);
634     ASSIGN_IT(layout);
635     ASSIGN_IT(anim);
636     ASSIGN_IT(xml);
637     ASSIGN_IT(raw);
638     ASSIGN_IT(color);
639     ASSIGN_IT(menu);
640 
641     assets->setResources(resources);
642     // now go through any resource overlays and collect their files
643     sp<AaptAssets> current = assets->getOverlay();
644     while(current.get()) {
645         KeyedVector<String8, sp<ResourceTypeSet> > *resources =
646                 new KeyedVector<String8, sp<ResourceTypeSet> >;
647         current->setResources(resources);
648         collect_files(current, resources);
649         current = current->getOverlay();
650     }
651     // apply the overlay files to the base set
652     if (!applyFileOverlay(bundle, assets, drawables, "drawable") ||
653             !applyFileOverlay(bundle, assets, layouts, "layout") ||
654             !applyFileOverlay(bundle, assets, anims, "anim") ||
655             !applyFileOverlay(bundle, assets, xmls, "xml") ||
656             !applyFileOverlay(bundle, assets, raws, "raw") ||
657             !applyFileOverlay(bundle, assets, colors, "color") ||
658             !applyFileOverlay(bundle, assets, menus, "menu")) {
659         return UNKNOWN_ERROR;
660     }
661 
662     bool hasErrors = false;
663 
664     if (drawables != NULL) {
665         err = preProcessImages(bundle, assets, drawables);
666         if (err == NO_ERROR) {
667             err = makeFileResources(bundle, assets, &table, drawables, "drawable");
668             if (err != NO_ERROR) {
669                 hasErrors = true;
670             }
671         } else {
672             hasErrors = true;
673         }
674     }
675 
676     if (layouts != NULL) {
677         err = makeFileResources(bundle, assets, &table, layouts, "layout");
678         if (err != NO_ERROR) {
679             hasErrors = true;
680         }
681     }
682 
683     if (anims != NULL) {
684         err = makeFileResources(bundle, assets, &table, anims, "anim");
685         if (err != NO_ERROR) {
686             hasErrors = true;
687         }
688     }
689 
690     if (xmls != NULL) {
691         err = makeFileResources(bundle, assets, &table, xmls, "xml");
692         if (err != NO_ERROR) {
693             hasErrors = true;
694         }
695     }
696 
697     if (raws != NULL) {
698         err = makeFileResources(bundle, assets, &table, raws, "raw");
699         if (err != NO_ERROR) {
700             hasErrors = true;
701         }
702     }
703 
704     // compile resources
705     current = assets;
706     while(current.get()) {
707         KeyedVector<String8, sp<ResourceTypeSet> > *resources =
708                 current->getResources();
709 
710         ssize_t index = resources->indexOfKey(String8("values"));
711         if (index >= 0) {
712             ResourceDirIterator it(resources->valueAt(index), String8("values"));
713             ssize_t res;
714             while ((res=it.next()) == NO_ERROR) {
715                 sp<AaptFile> file = it.getFile();
716                 res = compileResourceFile(bundle, assets, file, it.getParams(),
717                                           (current!=assets), &table);
718                 if (res != NO_ERROR) {
719                     hasErrors = true;
720                 }
721             }
722         }
723         current = current->getOverlay();
724     }
725 
726     if (colors != NULL) {
727         err = makeFileResources(bundle, assets, &table, colors, "color");
728         if (err != NO_ERROR) {
729             hasErrors = true;
730         }
731     }
732 
733     if (menus != NULL) {
734         err = makeFileResources(bundle, assets, &table, menus, "menu");
735         if (err != NO_ERROR) {
736             hasErrors = true;
737         }
738     }
739 
740     // --------------------------------------------------------------------
741     // Assignment of resource IDs and initial generation of resource table.
742     // --------------------------------------------------------------------
743 
744     if (table.hasResources()) {
745         sp<AaptFile> resFile(getResourceFile(assets));
746         if (resFile == NULL) {
747             fprintf(stderr, "Error: unable to generate entry for resource data\n");
748             return UNKNOWN_ERROR;
749         }
750 
751         err = table.assignResourceIds();
752         if (err < NO_ERROR) {
753             return err;
754         }
755     }
756 
757     // --------------------------------------------------------------
758     // Finally, we can now we can compile XML files, which may reference
759     // resources.
760     // --------------------------------------------------------------
761 
762     if (layouts != NULL) {
763         ResourceDirIterator it(layouts, String8("layout"));
764         while ((err=it.next()) == NO_ERROR) {
765             String8 src = it.getFile()->getPrintableSource();
766             err = compileXmlFile(assets, it.getFile(), &table);
767             if (err == NO_ERROR) {
768                 ResXMLTree block;
769                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
770                 checkForIds(src, block);
771             } else {
772                 hasErrors = true;
773             }
774         }
775 
776         if (err < NO_ERROR) {
777             hasErrors = true;
778         }
779         err = NO_ERROR;
780     }
781 
782     if (anims != NULL) {
783         ResourceDirIterator it(anims, String8("anim"));
784         while ((err=it.next()) == NO_ERROR) {
785             err = compileXmlFile(assets, it.getFile(), &table);
786             if (err != NO_ERROR) {
787                 hasErrors = true;
788             }
789         }
790 
791         if (err < NO_ERROR) {
792             hasErrors = true;
793         }
794         err = NO_ERROR;
795     }
796 
797     if (xmls != NULL) {
798         ResourceDirIterator it(xmls, String8("xml"));
799         while ((err=it.next()) == NO_ERROR) {
800             err = compileXmlFile(assets, it.getFile(), &table);
801             if (err != NO_ERROR) {
802                 hasErrors = true;
803             }
804         }
805 
806         if (err < NO_ERROR) {
807             hasErrors = true;
808         }
809         err = NO_ERROR;
810     }
811 
812     if (drawables != NULL) {
813         err = postProcessImages(assets, &table, drawables);
814         if (err != NO_ERROR) {
815             hasErrors = true;
816         }
817     }
818 
819     if (colors != NULL) {
820         ResourceDirIterator it(colors, String8("color"));
821         while ((err=it.next()) == NO_ERROR) {
822           err = compileXmlFile(assets, it.getFile(), &table);
823             if (err != NO_ERROR) {
824                 hasErrors = true;
825             }
826         }
827 
828         if (err < NO_ERROR) {
829             hasErrors = true;
830         }
831         err = NO_ERROR;
832     }
833 
834     if (menus != NULL) {
835         ResourceDirIterator it(menus, String8("menu"));
836         while ((err=it.next()) == NO_ERROR) {
837             String8 src = it.getFile()->getPrintableSource();
838             err = compileXmlFile(assets, it.getFile(), &table);
839             if (err != NO_ERROR) {
840                 hasErrors = true;
841             }
842             ResXMLTree block;
843             block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
844             checkForIds(src, block);
845         }
846 
847         if (err < NO_ERROR) {
848             hasErrors = true;
849         }
850         err = NO_ERROR;
851     }
852 
853     const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
854     String8 manifestPath(manifestFile->getPrintableSource());
855 
856     // Perform a basic validation of the manifest file.  This time we
857     // parse it with the comments intact, so that we can use them to
858     // generate java docs...  so we are not going to write this one
859     // back out to the final manifest data.
860     err = compileXmlFile(assets, manifestFile, &table,
861             XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
862             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
863     if (err < NO_ERROR) {
864         return err;
865     }
866     ResXMLTree block;
867     block.setTo(manifestFile->getData(), manifestFile->getSize(), true);
868     String16 manifest16("manifest");
869     String16 permission16("permission");
870     String16 permission_group16("permission-group");
871     String16 uses_permission16("uses-permission");
872     String16 instrumentation16("instrumentation");
873     String16 application16("application");
874     String16 provider16("provider");
875     String16 service16("service");
876     String16 receiver16("receiver");
877     String16 activity16("activity");
878     String16 action16("action");
879     String16 category16("category");
880     String16 data16("scheme");
881     const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
882         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
883     const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
884         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
885     const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz"
886         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$";
887     const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz"
888         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:";
889     const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz"
890         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;";
891     const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz"
892         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+";
893     const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz"
894         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
895     ResXMLTree::event_code_t code;
896     sp<AaptSymbols> permissionSymbols;
897     sp<AaptSymbols> permissionGroupSymbols;
898     while ((code=block.next()) != ResXMLTree::END_DOCUMENT
899            && code > ResXMLTree::BAD_DOCUMENT) {
900         if (code == ResXMLTree::START_TAG) {
901             size_t len;
902             if (block.getElementNamespace(&len) != NULL) {
903                 continue;
904             }
905             if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
906                 if (validateAttr(manifestPath, block, NULL, "package",
907                                  packageIdentChars, true) != ATTR_OKAY) {
908                     hasErrors = true;
909                 }
910             } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0
911                     || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) {
912                 const bool isGroup = strcmp16(block.getElementName(&len),
913                         permission_group16.string()) == 0;
914                 if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
915                                  isGroup ? packageIdentCharsWithTheStupid
916                                  : packageIdentChars, true) != ATTR_OKAY) {
917                     hasErrors = true;
918                 }
919                 SourcePos srcPos(manifestPath, block.getLineNumber());
920                 sp<AaptSymbols> syms;
921                 if (!isGroup) {
922                     syms = permissionSymbols;
923                     if (syms == NULL) {
924                         sp<AaptSymbols> symbols =
925                                 assets->getSymbolsFor(String8("Manifest"));
926                         syms = permissionSymbols = symbols->addNestedSymbol(
927                                 String8("permission"), srcPos);
928                     }
929                 } else {
930                     syms = permissionGroupSymbols;
931                     if (syms == NULL) {
932                         sp<AaptSymbols> symbols =
933                                 assets->getSymbolsFor(String8("Manifest"));
934                         syms = permissionGroupSymbols = symbols->addNestedSymbol(
935                                 String8("permission_group"), srcPos);
936                     }
937                 }
938                 size_t len;
939                 ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name");
940                 const uint16_t* id = block.getAttributeStringValue(index, &len);
941                 if (id == NULL) {
942                     fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n",
943                             manifestPath.string(), block.getLineNumber(),
944                             String8(block.getElementName(&len)).string());
945                     hasErrors = true;
946                     break;
947                 }
948                 String8 idStr(id);
949                 char* p = idStr.lockBuffer(idStr.size());
950                 char* e = p + idStr.size();
951                 bool begins_with_digit = true;  // init to true so an empty string fails
952                 while (e > p) {
953                     e--;
954                     if (*e >= '0' && *e <= '9') {
955                       begins_with_digit = true;
956                       continue;
957                     }
958                     if ((*e >= 'a' && *e <= 'z') ||
959                         (*e >= 'A' && *e <= 'Z') ||
960                         (*e == '_')) {
961                       begins_with_digit = false;
962                       continue;
963                     }
964                     if (isGroup && (*e == '-')) {
965                         *e = '_';
966                         begins_with_digit = false;
967                         continue;
968                     }
969                     e++;
970                     break;
971                 }
972                 idStr.unlockBuffer();
973                 // verify that we stopped because we hit a period or
974                 // the beginning of the string, and that the
975                 // identifier didn't begin with a digit.
976                 if (begins_with_digit || (e != p && *(e-1) != '.')) {
977                   fprintf(stderr,
978                           "%s:%d: Permission name <%s> is not a valid Java symbol\n",
979                           manifestPath.string(), block.getLineNumber(), idStr.string());
980                   hasErrors = true;
981                 }
982                 syms->addStringSymbol(String8(e), idStr, srcPos);
983                 const uint16_t* cmt = block.getComment(&len);
984                 if (cmt != NULL && *cmt != 0) {
985                     //printf("Comment of %s: %s\n", String8(e).string(),
986                     //        String8(cmt).string());
987                     syms->appendComment(String8(e), String16(cmt), srcPos);
988                 } else {
989                     //printf("No comment for %s\n", String8(e).string());
990                 }
991                 syms->makeSymbolPublic(String8(e), srcPos);
992             } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
993                 if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
994                                  packageIdentChars, true) != ATTR_OKAY) {
995                     hasErrors = true;
996                 }
997             } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) {
998                 if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
999                                  classIdentChars, true) != ATTR_OKAY) {
1000                     hasErrors = true;
1001                 }
1002                 if (validateAttr(manifestPath, block,
1003                                  RESOURCES_ANDROID_NAMESPACE, "targetPackage",
1004                                  packageIdentChars, true) != ATTR_OKAY) {
1005                     hasErrors = true;
1006                 }
1007             } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) {
1008                 if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
1009                                  classIdentChars, false) != ATTR_OKAY) {
1010                     hasErrors = true;
1011                 }
1012                 if (validateAttr(manifestPath, block,
1013                                  RESOURCES_ANDROID_NAMESPACE, "permission",
1014                                  packageIdentChars, false) != ATTR_OKAY) {
1015                     hasErrors = true;
1016                 }
1017                 if (validateAttr(manifestPath, block,
1018                                  RESOURCES_ANDROID_NAMESPACE, "process",
1019                                  processIdentChars, false) != ATTR_OKAY) {
1020                     hasErrors = true;
1021                 }
1022                 if (validateAttr(manifestPath, block,
1023                                  RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
1024                                  processIdentChars, false) != ATTR_OKAY) {
1025                     hasErrors = true;
1026                 }
1027             } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) {
1028                 if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
1029                                  classIdentChars, true) != ATTR_OKAY) {
1030                     hasErrors = true;
1031                 }
1032                 if (validateAttr(manifestPath, block,
1033                                  RESOURCES_ANDROID_NAMESPACE, "authorities",
1034                                  authoritiesIdentChars, true) != ATTR_OKAY) {
1035                     hasErrors = true;
1036                 }
1037                 if (validateAttr(manifestPath, block,
1038                                  RESOURCES_ANDROID_NAMESPACE, "permission",
1039                                  packageIdentChars, false) != ATTR_OKAY) {
1040                     hasErrors = true;
1041                 }
1042                 if (validateAttr(manifestPath, block,
1043                                  RESOURCES_ANDROID_NAMESPACE, "process",
1044                                  processIdentChars, false) != ATTR_OKAY) {
1045                     hasErrors = true;
1046                 }
1047             } else if (strcmp16(block.getElementName(&len), service16.string()) == 0
1048                        || strcmp16(block.getElementName(&len), receiver16.string()) == 0
1049                        || strcmp16(block.getElementName(&len), activity16.string()) == 0) {
1050                 if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
1051                                  classIdentChars, true) != ATTR_OKAY) {
1052                     hasErrors = true;
1053                 }
1054                 if (validateAttr(manifestPath, block,
1055                                  RESOURCES_ANDROID_NAMESPACE, "permission",
1056                                  packageIdentChars, false) != ATTR_OKAY) {
1057                     hasErrors = true;
1058                 }
1059                 if (validateAttr(manifestPath, block,
1060                                  RESOURCES_ANDROID_NAMESPACE, "process",
1061                                  processIdentChars, false) != ATTR_OKAY) {
1062                     hasErrors = true;
1063                 }
1064                 if (validateAttr(manifestPath, block,
1065                                  RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
1066                                  processIdentChars, false) != ATTR_OKAY) {
1067                     hasErrors = true;
1068                 }
1069             } else if (strcmp16(block.getElementName(&len), action16.string()) == 0
1070                        || strcmp16(block.getElementName(&len), category16.string()) == 0) {
1071                 if (validateAttr(manifestPath, block,
1072                                  RESOURCES_ANDROID_NAMESPACE, "name",
1073                                  packageIdentChars, true) != ATTR_OKAY) {
1074                     hasErrors = true;
1075                 }
1076             } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) {
1077                 if (validateAttr(manifestPath, block,
1078                                  RESOURCES_ANDROID_NAMESPACE, "mimeType",
1079                                  typeIdentChars, true) != ATTR_OKAY) {
1080                     hasErrors = true;
1081                 }
1082                 if (validateAttr(manifestPath, block,
1083                                  RESOURCES_ANDROID_NAMESPACE, "scheme",
1084                                  schemeIdentChars, true) != ATTR_OKAY) {
1085                     hasErrors = true;
1086                 }
1087             }
1088         }
1089     }
1090 
1091     if (table.validateLocalizations()) {
1092         hasErrors = true;
1093     }
1094 
1095     if (hasErrors) {
1096         return UNKNOWN_ERROR;
1097     }
1098 
1099     // Generate final compiled manifest file.
1100     manifestFile->clearData();
1101     sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
1102     if (manifestTree == NULL) {
1103         return UNKNOWN_ERROR;
1104     }
1105     err = massageManifest(bundle, manifestTree);
1106     if (err < NO_ERROR) {
1107         return err;
1108     }
1109     err = compileXmlFile(assets, manifestTree, manifestFile, &table);
1110     if (err < NO_ERROR) {
1111         return err;
1112     }
1113 
1114     //block.restart();
1115     //printXMLBlock(&block);
1116 
1117     // --------------------------------------------------------------
1118     // Generate the final resource table.
1119     // Re-flatten because we may have added new resource IDs
1120     // --------------------------------------------------------------
1121 
1122     if (table.hasResources()) {
1123         sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
1124         err = table.addSymbols(symbols);
1125         if (err < NO_ERROR) {
1126             return err;
1127         }
1128 
1129         sp<AaptFile> resFile(getResourceFile(assets));
1130         if (resFile == NULL) {
1131             fprintf(stderr, "Error: unable to generate entry for resource data\n");
1132             return UNKNOWN_ERROR;
1133         }
1134 
1135         err = table.flatten(bundle, resFile);
1136         if (err < NO_ERROR) {
1137             return err;
1138         }
1139 
1140         if (bundle->getPublicOutputFile()) {
1141             FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
1142             if (fp == NULL) {
1143                 fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n",
1144                         (const char*)bundle->getPublicOutputFile(), strerror(errno));
1145                 return UNKNOWN_ERROR;
1146             }
1147             if (bundle->getVerbose()) {
1148                 printf("  Writing public definitions to %s.\n", bundle->getPublicOutputFile());
1149             }
1150             table.writePublicDefinitions(String16(assets->getPackage()), fp);
1151             fclose(fp);
1152         }
1153 
1154         NOISY(
1155               ResTable rt;
1156               rt.add(resFile->getData(), resFile->getSize(), NULL);
1157               printf("Generated resources:\n");
1158               rt.print();
1159         )
1160 
1161         // These resources are now considered to be a part of the included
1162         // resources, for others to reference.
1163         err = assets->addIncludedResources(resFile);
1164         if (err < NO_ERROR) {
1165             fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
1166             return err;
1167         }
1168     }
1169     return err;
1170 }
1171 
getIndentSpace(int indent)1172 static const char* getIndentSpace(int indent)
1173 {
1174 static const char whitespace[] =
1175 "                                                                                       ";
1176 
1177     return whitespace + sizeof(whitespace) - 1 - indent*4;
1178 }
1179 
fixupSymbol(String16 * inoutSymbol)1180 static status_t fixupSymbol(String16* inoutSymbol)
1181 {
1182     inoutSymbol->replaceAll('.', '_');
1183     inoutSymbol->replaceAll(':', '_');
1184     return NO_ERROR;
1185 }
1186 
getAttributeComment(const sp<AaptAssets> & assets,const String8 & name,String16 * outTypeComment=NULL)1187 static String16 getAttributeComment(const sp<AaptAssets>& assets,
1188                                     const String8& name,
1189                                     String16* outTypeComment = NULL)
1190 {
1191     sp<AaptSymbols> asym = assets->getSymbolsFor(String8("R"));
1192     if (asym != NULL) {
1193         //printf("Got R symbols!\n");
1194         asym = asym->getNestedSymbols().valueFor(String8("attr"));
1195         if (asym != NULL) {
1196             //printf("Got attrs symbols! comment %s=%s\n",
1197             //     name.string(), String8(asym->getComment(name)).string());
1198             if (outTypeComment != NULL) {
1199                 *outTypeComment = asym->getTypeComment(name);
1200             }
1201             return asym->getComment(name);
1202         }
1203     }
1204     return String16();
1205 }
1206 
writeLayoutClasses(FILE * fp,const sp<AaptAssets> & assets,const sp<AaptSymbols> & symbols,int indent,bool includePrivate)1207 static status_t writeLayoutClasses(
1208     FILE* fp, const sp<AaptAssets>& assets,
1209     const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
1210 {
1211     const char* indentStr = getIndentSpace(indent);
1212     if (!includePrivate) {
1213         fprintf(fp, "%s/** @doconly */\n", indentStr);
1214     }
1215     fprintf(fp, "%spublic static final class styleable {\n", indentStr);
1216     indent++;
1217 
1218     String16 attr16("attr");
1219     String16 package16(assets->getPackage());
1220 
1221     indentStr = getIndentSpace(indent);
1222     bool hasErrors = false;
1223 
1224     size_t i;
1225     size_t N = symbols->getNestedSymbols().size();
1226     for (i=0; i<N; i++) {
1227         sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
1228         String16 nclassName16(symbols->getNestedSymbols().keyAt(i));
1229         String8 realClassName(nclassName16);
1230         if (fixupSymbol(&nclassName16) != NO_ERROR) {
1231             hasErrors = true;
1232         }
1233         String8 nclassName(nclassName16);
1234 
1235         SortedVector<uint32_t> idents;
1236         Vector<uint32_t> origOrder;
1237         Vector<bool> publicFlags;
1238 
1239         size_t a;
1240         size_t NA = nsymbols->getSymbols().size();
1241         for (a=0; a<NA; a++) {
1242             const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
1243             int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
1244                     ? sym.int32Val : 0;
1245             bool isPublic = true;
1246             if (code == 0) {
1247                 String16 name16(sym.name);
1248                 uint32_t typeSpecFlags;
1249                 code = assets->getIncludedResources().identifierForName(
1250                     name16.string(), name16.size(),
1251                     attr16.string(), attr16.size(),
1252                     package16.string(), package16.size(), &typeSpecFlags);
1253                 if (code == 0) {
1254                     fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
1255                             nclassName.string(), sym.name.string());
1256                     hasErrors = true;
1257                 }
1258                 isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
1259             }
1260             idents.add(code);
1261             origOrder.add(code);
1262             publicFlags.add(isPublic);
1263         }
1264 
1265         NA = idents.size();
1266 
1267         bool deprecated = false;
1268 
1269         String16 comment = symbols->getComment(realClassName);
1270         fprintf(fp, "%s/** ", indentStr);
1271         if (comment.size() > 0) {
1272             String8 cmt(comment);
1273             fprintf(fp, "%s\n", cmt.string());
1274             if (strstr(cmt.string(), "@deprecated") != NULL) {
1275                 deprecated = true;
1276             }
1277         } else {
1278             fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string());
1279         }
1280         bool hasTable = false;
1281         for (a=0; a<NA; a++) {
1282             ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1283             if (pos >= 0) {
1284                 if (!hasTable) {
1285                     hasTable = true;
1286                     fprintf(fp,
1287                             "%s   <p>Includes the following attributes:</p>\n"
1288                             "%s   <table>\n"
1289                             "%s   <colgroup align=\"left\" />\n"
1290                             "%s   <colgroup align=\"left\" />\n"
1291                             "%s   <tr><th>Attribute</th><th>Description</th></tr>\n",
1292                             indentStr,
1293                             indentStr,
1294                             indentStr,
1295                             indentStr,
1296                             indentStr);
1297                 }
1298                 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1299                 if (!publicFlags.itemAt(a) && !includePrivate) {
1300                     continue;
1301                 }
1302                 String8 name8(sym.name);
1303                 String16 comment(sym.comment);
1304                 if (comment.size() <= 0) {
1305                     comment = getAttributeComment(assets, name8);
1306                 }
1307                 if (comment.size() > 0) {
1308                     const char16_t* p = comment.string();
1309                     while (*p != 0 && *p != '.') {
1310                         if (*p == '{') {
1311                             while (*p != 0 && *p != '}') {
1312                                 p++;
1313                             }
1314                         } else {
1315                             p++;
1316                         }
1317                     }
1318                     if (*p == '.') {
1319                         p++;
1320                     }
1321                     comment = String16(comment.string(), p-comment.string());
1322                 }
1323                 String16 name(name8);
1324                 fixupSymbol(&name);
1325                 fprintf(fp, "%s   <tr><td><code>{@link #%s_%s %s:%s}</code></td><td>%s</td></tr>\n",
1326                         indentStr, nclassName.string(),
1327                         String8(name).string(),
1328                         assets->getPackage().string(),
1329                         String8(name).string(),
1330                         String8(comment).string());
1331             }
1332         }
1333         if (hasTable) {
1334             fprintf(fp, "%s   </table>\n", indentStr);
1335         }
1336         for (a=0; a<NA; a++) {
1337             ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1338             if (pos >= 0) {
1339                 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1340                 if (!publicFlags.itemAt(a) && !includePrivate) {
1341                     continue;
1342                 }
1343                 String16 name(sym.name);
1344                 fixupSymbol(&name);
1345                 fprintf(fp, "%s   @see #%s_%s\n",
1346                         indentStr, nclassName.string(),
1347                         String8(name).string());
1348             }
1349         }
1350         fprintf(fp, "%s */\n", getIndentSpace(indent));
1351 
1352         if (deprecated) {
1353             fprintf(fp, "%s@Deprecated\n", indentStr);
1354         }
1355 
1356         fprintf(fp,
1357                 "%spublic static final int[] %s = {\n"
1358                 "%s",
1359                 indentStr, nclassName.string(),
1360                 getIndentSpace(indent+1));
1361 
1362         for (a=0; a<NA; a++) {
1363             if (a != 0) {
1364                 if ((a&3) == 0) {
1365                     fprintf(fp, ",\n%s", getIndentSpace(indent+1));
1366                 } else {
1367                     fprintf(fp, ", ");
1368                 }
1369             }
1370             fprintf(fp, "0x%08x", idents[a]);
1371         }
1372 
1373         fprintf(fp, "\n%s};\n", indentStr);
1374 
1375         for (a=0; a<NA; a++) {
1376             ssize_t pos = idents.indexOf(origOrder.itemAt(a));
1377             if (pos >= 0) {
1378                 const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
1379                 if (!publicFlags.itemAt(a) && !includePrivate) {
1380                     continue;
1381                 }
1382                 String8 name8(sym.name);
1383                 String16 comment(sym.comment);
1384                 String16 typeComment;
1385                 if (comment.size() <= 0) {
1386                     comment = getAttributeComment(assets, name8, &typeComment);
1387                 } else {
1388                     getAttributeComment(assets, name8, &typeComment);
1389                 }
1390                 String16 name(name8);
1391                 if (fixupSymbol(&name) != NO_ERROR) {
1392                     hasErrors = true;
1393                 }
1394 
1395                 uint32_t typeSpecFlags = 0;
1396                 String16 name16(sym.name);
1397                 assets->getIncludedResources().identifierForName(
1398                     name16.string(), name16.size(),
1399                     attr16.string(), attr16.size(),
1400                     package16.string(), package16.size(), &typeSpecFlags);
1401                 //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
1402                 //    String8(attr16).string(), String8(name16).string(), typeSpecFlags);
1403                 const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
1404 
1405                 bool deprecated = false;
1406 
1407                 fprintf(fp, "%s/**\n", indentStr);
1408                 if (comment.size() > 0) {
1409                     String8 cmt(comment);
1410                     fprintf(fp, "%s  <p>\n%s  @attr description\n", indentStr, indentStr);
1411                     fprintf(fp, "%s  %s\n", indentStr, cmt.string());
1412                     if (strstr(cmt.string(), "@deprecated") != NULL) {
1413                         deprecated = true;
1414                     }
1415                 } else {
1416                     fprintf(fp,
1417                             "%s  <p>This symbol is the offset where the {@link %s.R.attr#%s}\n"
1418                             "%s  attribute's value can be found in the {@link #%s} array.\n",
1419                             indentStr,
1420                             pub ? assets->getPackage().string()
1421                                 : assets->getSymbolsPrivatePackage().string(),
1422                             String8(name).string(),
1423                             indentStr, nclassName.string());
1424                 }
1425                 if (typeComment.size() > 0) {
1426                     String8 cmt(typeComment);
1427                     fprintf(fp, "\n\n%s  %s\n", indentStr, cmt.string());
1428                     if (strstr(cmt.string(), "@deprecated") != NULL) {
1429                         deprecated = true;
1430                     }
1431                 }
1432                 if (comment.size() > 0) {
1433                     if (pub) {
1434                         fprintf(fp,
1435                                 "%s  <p>This corresponds to the global attribute"
1436                                 "%s  resource symbol {@link %s.R.attr#%s}.\n",
1437                                 indentStr, indentStr,
1438                                 assets->getPackage().string(),
1439                                 String8(name).string());
1440                     } else {
1441                         fprintf(fp,
1442                                 "%s  <p>This is a private symbol.\n", indentStr);
1443                     }
1444                 }
1445                 fprintf(fp, "%s  @attr name %s:%s\n", indentStr,
1446                         "android", String8(name).string());
1447                 fprintf(fp, "%s*/\n", indentStr);
1448                 if (deprecated) {
1449                     fprintf(fp, "%s@Deprecated\n", indentStr);
1450                 }
1451                 fprintf(fp,
1452                         "%spublic static final int %s_%s = %d;\n",
1453                         indentStr, nclassName.string(),
1454                         String8(name).string(), (int)pos);
1455             }
1456         }
1457     }
1458 
1459     indent--;
1460     fprintf(fp, "%s};\n", getIndentSpace(indent));
1461     return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
1462 }
1463 
writeSymbolClass(FILE * fp,const sp<AaptAssets> & assets,bool includePrivate,const sp<AaptSymbols> & symbols,const String8 & className,int indent)1464 static status_t writeSymbolClass(
1465     FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
1466     const sp<AaptSymbols>& symbols, const String8& className, int indent)
1467 {
1468     fprintf(fp, "%spublic %sfinal class %s {\n",
1469             getIndentSpace(indent),
1470             indent != 0 ? "static " : "", className.string());
1471     indent++;
1472 
1473     size_t i;
1474     status_t err = NO_ERROR;
1475 
1476     size_t N = symbols->getSymbols().size();
1477     for (i=0; i<N; i++) {
1478         const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
1479         if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
1480             continue;
1481         }
1482         if (!includePrivate && !sym.isPublic) {
1483             continue;
1484         }
1485         String16 name(sym.name);
1486         String8 realName(name);
1487         if (fixupSymbol(&name) != NO_ERROR) {
1488             return UNKNOWN_ERROR;
1489         }
1490         String16 comment(sym.comment);
1491         bool haveComment = false;
1492         bool deprecated = false;
1493         if (comment.size() > 0) {
1494             haveComment = true;
1495             String8 cmt(comment);
1496             fprintf(fp,
1497                     "%s/** %s\n",
1498                     getIndentSpace(indent), cmt.string());
1499             if (strstr(cmt.string(), "@deprecated") != NULL) {
1500                 deprecated = true;
1501             }
1502         } else if (sym.isPublic && !includePrivate) {
1503             sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
1504                 assets->getPackage().string(), className.string(),
1505                 String8(sym.name).string());
1506         }
1507         String16 typeComment(sym.typeComment);
1508         if (typeComment.size() > 0) {
1509             String8 cmt(typeComment);
1510             if (!haveComment) {
1511                 haveComment = true;
1512                 fprintf(fp,
1513                         "%s/** %s\n", getIndentSpace(indent), cmt.string());
1514             } else {
1515                 fprintf(fp,
1516                         "%s %s\n", getIndentSpace(indent), cmt.string());
1517             }
1518             if (strstr(cmt.string(), "@deprecated") != NULL) {
1519                 deprecated = true;
1520             }
1521         }
1522         if (haveComment) {
1523             fprintf(fp,"%s */\n", getIndentSpace(indent));
1524         }
1525         if (deprecated) {
1526             fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
1527         }
1528         fprintf(fp, "%spublic static final int %s=0x%08x;\n",
1529                 getIndentSpace(indent),
1530                 String8(name).string(), (int)sym.int32Val);
1531     }
1532 
1533     for (i=0; i<N; i++) {
1534         const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
1535         if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) {
1536             continue;
1537         }
1538         if (!includePrivate && !sym.isPublic) {
1539             continue;
1540         }
1541         String16 name(sym.name);
1542         if (fixupSymbol(&name) != NO_ERROR) {
1543             return UNKNOWN_ERROR;
1544         }
1545         String16 comment(sym.comment);
1546         bool deprecated = false;
1547         if (comment.size() > 0) {
1548             String8 cmt(comment);
1549             fprintf(fp,
1550                     "%s/** %s\n"
1551                      "%s */\n",
1552                     getIndentSpace(indent), cmt.string(),
1553                     getIndentSpace(indent));
1554             if (strstr(cmt.string(), "@deprecated") != NULL) {
1555                 deprecated = true;
1556             }
1557         } else if (sym.isPublic && !includePrivate) {
1558             sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
1559                 assets->getPackage().string(), className.string(),
1560                 String8(sym.name).string());
1561         }
1562         if (deprecated) {
1563             fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
1564         }
1565         fprintf(fp, "%spublic static final String %s=\"%s\";\n",
1566                 getIndentSpace(indent),
1567                 String8(name).string(), sym.stringVal.string());
1568     }
1569 
1570     sp<AaptSymbols> styleableSymbols;
1571 
1572     N = symbols->getNestedSymbols().size();
1573     for (i=0; i<N; i++) {
1574         sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
1575         String8 nclassName(symbols->getNestedSymbols().keyAt(i));
1576         if (nclassName == "styleable") {
1577             styleableSymbols = nsymbols;
1578         } else {
1579             err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent);
1580         }
1581         if (err != NO_ERROR) {
1582             return err;
1583         }
1584     }
1585 
1586     if (styleableSymbols != NULL) {
1587         err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
1588         if (err != NO_ERROR) {
1589             return err;
1590         }
1591     }
1592 
1593     indent--;
1594     fprintf(fp, "%s}\n", getIndentSpace(indent));
1595     return NO_ERROR;
1596 }
1597 
writeResourceSymbols(Bundle * bundle,const sp<AaptAssets> & assets,const String8 & package,bool includePrivate)1598 status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
1599     const String8& package, bool includePrivate)
1600 {
1601     if (!bundle->getRClassDir()) {
1602         return NO_ERROR;
1603     }
1604 
1605     const size_t N = assets->getSymbols().size();
1606     for (size_t i=0; i<N; i++) {
1607         sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i);
1608         String8 className(assets->getSymbols().keyAt(i));
1609         String8 dest(bundle->getRClassDir());
1610         if (bundle->getMakePackageDirs()) {
1611             String8 pkg(package);
1612             const char* last = pkg.string();
1613             const char* s = last-1;
1614             do {
1615                 s++;
1616                 if (s > last && (*s == '.' || *s == 0)) {
1617                     String8 part(last, s-last);
1618                     dest.appendPath(part);
1619 #ifdef HAVE_MS_C_RUNTIME
1620                     _mkdir(dest.string());
1621 #else
1622                     mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
1623 #endif
1624                     last = s+1;
1625                 }
1626             } while (*s);
1627         }
1628         dest.appendPath(className);
1629         dest.append(".java");
1630         FILE* fp = fopen(dest.string(), "w+");
1631         if (fp == NULL) {
1632             fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
1633                     dest.string(), strerror(errno));
1634             return UNKNOWN_ERROR;
1635         }
1636         if (bundle->getVerbose()) {
1637             printf("  Writing symbols for class %s.\n", className.string());
1638         }
1639 
1640         fprintf(fp,
1641         "/* AUTO-GENERATED FILE.  DO NOT MODIFY.\n"
1642         " *\n"
1643         " * This class was automatically generated by the\n"
1644         " * aapt tool from the resource data it found.  It\n"
1645         " * should not be modified by hand.\n"
1646         " */\n"
1647         "\n"
1648         "package %s;\n\n", package.string());
1649 
1650         status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, className, 0);
1651         if (err != NO_ERROR) {
1652             return err;
1653         }
1654         fclose(fp);
1655     }
1656 
1657     return NO_ERROR;
1658 }
1659 
1660 
1661 
1662 class ProguardKeepSet
1663 {
1664 public:
1665     // { rule --> { file locations } }
1666     KeyedVector<String8, SortedVector<String8> > rules;
1667 
1668     void add(const String8& rule, const String8& where);
1669 };
1670 
add(const String8 & rule,const String8 & where)1671 void ProguardKeepSet::add(const String8& rule, const String8& where)
1672 {
1673     ssize_t index = rules.indexOfKey(rule);
1674     if (index < 0) {
1675         index = rules.add(rule, SortedVector<String8>());
1676     }
1677     rules.editValueAt(index).add(where);
1678 }
1679 
1680 status_t
writeProguardForAndroidManifest(ProguardKeepSet * keep,const sp<AaptAssets> & assets)1681 writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
1682 {
1683     status_t err;
1684     ResXMLTree tree;
1685     size_t len;
1686     ResXMLTree::event_code_t code;
1687     int depth = 0;
1688     bool inApplication = false;
1689     String8 error;
1690     sp<AaptGroup> assGroup;
1691     sp<AaptFile> assFile;
1692     String8 pkg;
1693 
1694     // First, look for a package file to parse.  This is required to
1695     // be able to generate the resource information.
1696     assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml"));
1697     if (assGroup == NULL) {
1698         fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
1699         return -1;
1700     }
1701 
1702     if (assGroup->getFiles().size() != 1) {
1703         fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
1704                 assGroup->getFiles().valueAt(0)->getPrintableSource().string());
1705     }
1706 
1707     assFile = assGroup->getFiles().valueAt(0);
1708 
1709     err = parseXMLResource(assFile, &tree);
1710     if (err != NO_ERROR) {
1711         return err;
1712     }
1713 
1714     tree.restart();
1715 
1716     while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1717         if (code == ResXMLTree::END_TAG) {
1718             if (/* name == "Application" && */ depth == 2) {
1719                 inApplication = false;
1720             }
1721             depth--;
1722             continue;
1723         }
1724         if (code != ResXMLTree::START_TAG) {
1725             continue;
1726         }
1727         depth++;
1728         String8 tag(tree.getElementName(&len));
1729         // printf("Depth %d tag %s\n", depth, tag.string());
1730         if (depth == 1) {
1731             if (tag != "manifest") {
1732                 fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
1733                 return -1;
1734             }
1735             pkg = getAttribute(tree, NULL, "package", NULL);
1736         } else if (depth == 2 && tag == "application") {
1737             inApplication = true;
1738         }
1739         if (inApplication) {
1740             if (tag == "application" || tag == "activity" || tag == "service" || tag == "receiver"
1741                     || tag == "provider") {
1742                 String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
1743                         "name", &error);
1744                 if (error != "") {
1745                     fprintf(stderr, "ERROR: %s\n", error.string());
1746                     return -1;
1747                 }
1748                 // asdf     --> package.asdf
1749                 // .asdf  .a.b  --> package.asdf package.a.b
1750                 // asdf.adsf --> asdf.asdf
1751                 String8 rule("-keep class ");
1752                 const char* p = name.string();
1753                 const char* q = strchr(p, '.');
1754                 if (p == q) {
1755                     rule += pkg;
1756                     rule += name;
1757                 } else if (q == NULL) {
1758                     rule += pkg;
1759                     rule += ".";
1760                     rule += name;
1761                 } else {
1762                     rule += name;
1763                 }
1764 
1765                 String8 location = tag;
1766                 location += " ";
1767                 location += assFile->getSourceFile();
1768                 char lineno[20];
1769                 sprintf(lineno, ":%d", tree.getLineNumber());
1770                 location += lineno;
1771 
1772                 keep->add(rule, location);
1773             }
1774         }
1775     }
1776 
1777     return NO_ERROR;
1778 }
1779 
1780 status_t
writeProguardForLayout(ProguardKeepSet * keep,const sp<AaptFile> & layoutFile)1781 writeProguardForLayout(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile)
1782 {
1783     status_t err;
1784     ResXMLTree tree;
1785     size_t len;
1786     ResXMLTree::event_code_t code;
1787 
1788     err = parseXMLResource(layoutFile, &tree);
1789     if (err != NO_ERROR) {
1790         return err;
1791     }
1792 
1793     tree.restart();
1794 
1795     while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
1796         if (code != ResXMLTree::START_TAG) {
1797             continue;
1798         }
1799         String8 tag(tree.getElementName(&len));
1800 
1801         // If there is no '.', we'll assume that it's one of the built in names.
1802         if (strchr(tag.string(), '.')) {
1803             String8 rule("-keep class ");
1804             rule += tag;
1805             rule += " { <init>(...); }";
1806 
1807             String8 location("view ");
1808             location += layoutFile->getSourceFile();
1809             char lineno[20];
1810             sprintf(lineno, ":%d", tree.getLineNumber());
1811             location += lineno;
1812 
1813             keep->add(rule, location);
1814         }
1815     }
1816 
1817     return NO_ERROR;
1818 }
1819 
1820 status_t
writeProguardForLayouts(ProguardKeepSet * keep,const sp<AaptAssets> & assets)1821 writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
1822 {
1823     status_t err;
1824     sp<AaptDir> layout = assets->resDir(String8("layout"));
1825 
1826     if (layout != NULL) {
1827         const KeyedVector<String8,sp<AaptGroup> > groups = layout->getFiles();
1828         const size_t N = groups.size();
1829         for (size_t i=0; i<N; i++) {
1830             const sp<AaptGroup>& group = groups.valueAt(i);
1831             const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles();
1832             const size_t M = files.size();
1833             for (size_t j=0; j<M; j++) {
1834                 err = writeProguardForLayout(keep, files.valueAt(j));
1835                 if (err < 0) {
1836                     return err;
1837                 }
1838             }
1839         }
1840     }
1841     return NO_ERROR;
1842 }
1843 
1844 status_t
writeProguardFile(Bundle * bundle,const sp<AaptAssets> & assets)1845 writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
1846 {
1847     status_t err = -1;
1848 
1849     if (!bundle->getProguardFile()) {
1850         return NO_ERROR;
1851     }
1852 
1853     ProguardKeepSet keep;
1854 
1855     err = writeProguardForAndroidManifest(&keep, assets);
1856     if (err < 0) {
1857         return err;
1858     }
1859 
1860     err = writeProguardForLayouts(&keep, assets);
1861     if (err < 0) {
1862         return err;
1863     }
1864 
1865     FILE* fp = fopen(bundle->getProguardFile(), "w+");
1866     if (fp == NULL) {
1867         fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
1868                 bundle->getProguardFile(), strerror(errno));
1869         return UNKNOWN_ERROR;
1870     }
1871 
1872     const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
1873     const size_t N = rules.size();
1874     for (size_t i=0; i<N; i++) {
1875         const SortedVector<String8>& locations = rules.valueAt(i);
1876         const size_t M = locations.size();
1877         for (size_t j=0; j<M; j++) {
1878             fprintf(fp, "# %s\n", locations.itemAt(j).string());
1879         }
1880         fprintf(fp, "%s\n\n", rules.keyAt(i).string());
1881     }
1882     fclose(fp);
1883 
1884     return err;
1885 }
1886