X-Git-Url: https://git.saurik.com/android/aapt.git/blobdiff_plain/2c43f46a895c5dc43e70e9ade39bbb0d7ed87a27..180f1af5d8c7f1c1e80126ab59f8a5bc69bb51ca:/Resource.cpp diff --git a/Resource.cpp b/Resource.cpp index 2e4d0a4..a69adc1 100644 --- a/Resource.cpp +++ b/Resource.cpp @@ -10,8 +10,25 @@ #include "ResourceTable.h" #include "Images.h" +#include "CrunchCache.h" +#include "FileFinder.h" +#include "CacheUpdater.h" + +#include + +#if HAVE_PRINTF_ZD +# define ZD "%zd" +# define ZD_TYPE ssize_t +#else +# define ZD "%ld" +# define ZD_TYPE long +#endif + #define NOISY(x) // x +// Number of threads to use for preprocessing images. +static const size_t MAX_THREADS = 4; + // ========================================================================== // ========================================================================== // ========================================================================== @@ -51,6 +68,12 @@ ResourceTypeSet::ResourceTypeSet() { } +FilePathStore::FilePathStore() + :RefBase(), + Vector() +{ +} + class ResourceDirIterator { public: @@ -101,13 +124,13 @@ public: String8 leaf(group->getLeaf()); mLeafName = String8(leaf); mParams = file->getGroupEntry().toParams(); - 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", + NOISY(printf("Dir %s: mcc=%d mnc=%d lang=%c%c cnt=%c%c orient=%d ui=%d density=%d touch=%d key=%d inp=%d nav=%d\n", group->getPath().string(), mParams.mcc, mParams.mnc, mParams.language[0] ? mParams.language[0] : '-', mParams.language[1] ? mParams.language[1] : '-', mParams.country[0] ? mParams.country[0] : '-', mParams.country[1] ? mParams.country[1] : '-', - mParams.orientation, + mParams.orientation, mParams.uiMode, mParams.density, mParams.touchscreen, mParams.keyboard, mParams.inputFlags, mParams.navigation)); mPath = "res"; @@ -148,9 +171,10 @@ private: bool isValidResourceType(const String8& type) { - return type == "anim" || type == "drawable" || type == "layout" + return type == "anim" || type == "animator" || type == "interpolator" + || type == "drawable" || type == "layout" || type == "values" || type == "xml" || type == "raw" - || type == "color" || type == "menu"; + || type == "color" || type == "menu" || type == "mipmap"; } static sp getResourceFile(const sp& assets, bool makeIfNecessary=true) @@ -171,10 +195,11 @@ static sp getResourceFile(const sp& assets, bool makeIfNec NULL, String8()); } -static status_t parsePackage(const sp& assets, const sp& grp) +static status_t parsePackage(Bundle* bundle, const sp& assets, + const sp& grp) { if (grp->getFiles().size() != 1) { - fprintf(stderr, "WARNING: Multiple AndroidManifest.xml files found, using %s\n", + fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n", grp->getFiles().valueAt(0)->getPrintableSource().string()); } @@ -215,6 +240,22 @@ static status_t parsePackage(const sp& assets, const sp& assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len))); + String16 uses_sdk16("uses-sdk"); + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) { + ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, + "minSdkVersion"); + if (minSdkIndex >= 0) { + const uint16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len); + const char* minSdk8 = strdup(String8(minSdk16).string()); + bundle->setManifestMinSdkVersion(minSdk8); + } + } + } + } + return NO_ERROR; } @@ -266,21 +307,54 @@ static status_t makeFileResources(Bundle* bundle, const sp& assets, return hasErrors ? UNKNOWN_ERROR : NO_ERROR; } -static status_t preProcessImages(Bundle* bundle, const sp& assets, - const sp& set) -{ - ResourceDirIterator it(set, String8("drawable")); - Vector > newNameFiles; - Vector newNamePaths; - ssize_t res; - while ((res=it.next()) == NO_ERROR) { - res = preProcessImage(bundle, assets, it.getFile(), NULL); - if (res != NO_ERROR) { - return res; +class PreProcessImageWorkUnit : public WorkQueue::WorkUnit { +public: + PreProcessImageWorkUnit(const Bundle* bundle, const sp& assets, + const sp& file, volatile bool* hasErrors) : + mBundle(bundle), mAssets(assets), mFile(file), mHasErrors(hasErrors) { + } + + virtual bool run() { + status_t status = preProcessImage(mBundle, mAssets, mFile, NULL); + if (status) { + *mHasErrors = true; } + return true; // continue even if there are errors } - return NO_ERROR; +private: + const Bundle* mBundle; + sp mAssets; + sp mFile; + volatile bool* mHasErrors; +}; + +static status_t preProcessImages(const Bundle* bundle, const sp& assets, + const sp& set, const char* type) +{ + volatile bool hasErrors = false; + ssize_t res = NO_ERROR; + if (bundle->getUseCrunchCache() == false) { + WorkQueue wq(MAX_THREADS, false); + ResourceDirIterator it(set, String8(type)); + while ((res=it.next()) == NO_ERROR) { + PreProcessImageWorkUnit* w = new PreProcessImageWorkUnit( + bundle, assets, it.getFile(), &hasErrors); + status_t status = wq.schedule(w); + if (status) { + fprintf(stderr, "preProcessImages failed: schedule() returned %d\n", status); + hasErrors = true; + delete w; + break; + } + } + status_t status = wq.finish(); + if (status) { + fprintf(stderr, "preProcessImages failed: finish() returned %d\n", status); + hasErrors = true; + } + } + return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; } status_t postProcessImages(const sp& assets, @@ -288,15 +362,16 @@ status_t postProcessImages(const sp& assets, const sp& set) { ResourceDirIterator it(set, String8("drawable")); + bool hasErrors = false; ssize_t res; while ((res=it.next()) == NO_ERROR) { res = postProcessImage(assets, table, it.getFile()); - if (res != NO_ERROR) { - return res; + if (res < NO_ERROR) { + hasErrors = true; } } - return res < NO_ERROR ? res : (status_t)NO_ERROR; + return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; } static void collect_files(const sp& dir, @@ -321,18 +396,27 @@ static void collect_files(const sp& dir, if (index < 0) { sp set = new ResourceTypeSet(); + NOISY(printf("Creating new resource type set for leaf %s with group %s (%p)\n", + leafName.string(), group->getPath().string(), group.get())); set->add(leafName, group); resources->add(resType, set); } else { sp set = resources->valueAt(index); index = set->indexOfKey(leafName); if (index < 0) { + NOISY(printf("Adding to resource type set for leaf %s group %s (%p)\n", + leafName.string(), group->getPath().string(), group.get())); set->add(leafName, group); } else { sp existingGroup = set->valueAt(index); - int M = files.size(); - for (int j=0; jaddFile(files.valueAt(j)); + NOISY(printf("Extending to resource type set for leaf %s group %s (%p)\n", + leafName.string(), group->getPath().string(), group.get())); + for (size_t j=0; jgetSourceFile().string(), + files.keyAt(j).toDirName(String8()).string(), + resType.string())); + status_t err = existingGroup->addFile(files.valueAt(j)); } } } @@ -347,9 +431,12 @@ static void collect_files(const sp& ass, for (int i=0; i d = dirs.itemAt(i); + NOISY(printf("Collecting dir #%d %p: %s, leaf %s\n", i, d.get(), d->getPath().string(), + d->getLeaf().string())); collect_files(d, resources); // don't try to include the res dir + NOISY(printf("Removing dir leaf %s\n", d->getLeaf().string())); ass->removeDir(d->getLeaf()); } } @@ -360,14 +447,64 @@ enum { ATTR_LEADING_SPACES = -3, ATTR_TRAILING_SPACES = -4 }; -static int validateAttr(const String8& path, const ResXMLParser& parser, +static int validateAttr(const String8& path, const ResTable& table, + const ResXMLParser& parser, const char* ns, const char* attr, const char* validChars, bool required) { size_t len; ssize_t index = parser.indexOfAttribute(ns, attr); const uint16_t* str; - if (index >= 0 && (str=parser.getAttributeStringValue(index, &len)) != NULL) { + Res_value value; + if (index >= 0 && parser.getAttributeValue(index, &value) >= 0) { + const ResStringPool* pool = &parser.getStrings(); + if (value.dataType == Res_value::TYPE_REFERENCE) { + uint32_t specFlags = 0; + int strIdx; + if ((strIdx=table.resolveReference(&value, 0x10000000, NULL, &specFlags)) < 0) { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s references unknown resid 0x%08x.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr, + value.data); + return ATTR_NOT_FOUND; + } + + pool = table.getTableStringBlock(strIdx); + #if 0 + if (pool != NULL) { + str = pool->stringAt(value.data, &len); + } + printf("***** RES ATTR: %s specFlags=0x%x strIdx=%d: %s\n", attr, + specFlags, strIdx, str != NULL ? String8(str).string() : "???"); + #endif + if ((specFlags&~ResTable_typeSpec::SPEC_PUBLIC) != 0 && false) { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s varies by configurations 0x%x.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr, + specFlags); + return ATTR_NOT_FOUND; + } + } + if (value.dataType == Res_value::TYPE_STRING) { + if (pool == NULL) { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s has no string block.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr); + return ATTR_NOT_FOUND; + } + if ((str=pool->stringAt(value.data, &len)) == NULL) { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s has corrupt string value.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr); + return ATTR_NOT_FOUND; + } + } else { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid type %d.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr, + value.dataType); + return ATTR_NOT_FOUND; + } if (validChars) { for (size_t i=0; i= 0) { - fprintf(stderr, "%s:%d: WARNING: found plain 'id' attribute; did you mean the new 'android:id' name?\n", + fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n", path.string(), parser.getLineNumber()); } } } } -static void applyFileOverlay(const sp& assets, - const sp& baseSet, +static bool applyFileOverlay(Bundle *bundle, + const sp& assets, + sp *baseSet, const char *resType) { + if (bundle->getVerbose()) { + printf("applyFileOverlay for %s\n", resType); + } + // Replace any base level files in this category with any found from the overlay // Also add any found only in the overlay. sp overlay = assets->getOverlay(); String8 resTypeString(resType); - + // work through the linked list of overlays while (overlay.get()) { KeyedVector >* overlayRes = overlay->getResources(); @@ -448,36 +590,79 @@ static void applyFileOverlay(const sp& assets, // non-overlay "baseset". size_t overlayCount = overlaySet->size(); for (size_t overlayIndex=0; overlayIndexindexOfKey(overlaySet->keyAt(overlayIndex)); - if (baseIndex != UNKNOWN_ERROR) { + if (bundle->getVerbose()) { + printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string()); + } + size_t baseIndex = UNKNOWN_ERROR; + if (baseSet->get() != NULL) { + baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex)); + } + if (baseIndex < UNKNOWN_ERROR) { // look for same flavor. For a given file (strings.xml, for example) // there may be a locale specific or other flavors - we want to match // the same flavor. sp overlayGroup = overlaySet->valueAt(overlayIndex); - sp baseGroup = baseSet->valueAt(baseIndex); - - DefaultKeyedVector > baseFiles = - baseGroup->getFiles(); - DefaultKeyedVector > overlayFiles = + sp baseGroup = (*baseSet)->valueAt(baseIndex); + + DefaultKeyedVector > overlayFiles = overlayGroup->getFiles(); + if (bundle->getVerbose()) { + DefaultKeyedVector > baseFiles = + baseGroup->getFiles(); + for (size_t i=0; i < baseFiles.size(); i++) { + printf("baseFile " ZD " has flavor %s\n", (ZD_TYPE) i, + baseFiles.keyAt(i).toString().string()); + } + for (size_t i=0; i < overlayFiles.size(); i++) { + printf("overlayFile " ZD " has flavor %s\n", (ZD_TYPE) i, + overlayFiles.keyAt(i).toString().string()); + } + } + size_t overlayGroupSize = overlayFiles.size(); - for (size_t overlayGroupIndex = 0; - overlayGroupIndexgetFiles().indexOfKey(overlayFiles. + keyAt(overlayGroupIndex)); + if (baseFileIndex < UNKNOWN_ERROR) { + if (bundle->getVerbose()) { + printf("found a match (" ZD ") for overlay file %s, for flavor %s\n", + (ZD_TYPE) baseFileIndex, + overlayGroup->getLeaf().string(), + overlayFiles.keyAt(overlayGroupIndex).toString().string()); + } baseGroup->removeFile(baseFileIndex); } else { // didn't find a match fall through and add it.. + if (true || bundle->getVerbose()) { + printf("nothing matches overlay file %s, for flavor %s\n", + overlayGroup->getLeaf().string(), + overlayFiles.keyAt(overlayGroupIndex).toString().string()); + } } baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex)); + assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex)); } } else { + if (baseSet->get() == NULL) { + *baseSet = new ResourceTypeSet(); + assets->getResources()->add(String8(resType), *baseSet); + } // this group doesn't exist (a file that's only in the overlay) - // add it - baseSet->add(overlaySet->keyAt(overlayIndex), - overlaySet->valueAt(overlayIndex)); + (*baseSet)->add(overlaySet->keyAt(overlayIndex), + overlaySet->valueAt(overlayIndex)); + // make sure all flavors are defined in the resources. + sp overlayGroup = overlaySet->valueAt(overlayIndex); + DefaultKeyedVector > overlayFiles = + overlayGroup->getFiles(); + size_t overlayGroupSize = overlayFiles.size(); + for (size_t overlayGroupIndex = 0; + overlayGroupIndexaddGroupEntry(overlayFiles.keyAt(overlayGroupIndex)); + } } } // this overlay didn't have resources for this type @@ -485,7 +670,146 @@ static void applyFileOverlay(const sp& assets, // try next overlay overlay = overlay->getOverlay(); } - return; + return true; +} + +void addTagAttribute(const sp& node, const char* ns8, + const char* attr8, const char* value) +{ + if (value == NULL) { + return; + } + + const String16 ns(ns8); + const String16 attr(attr8); + + if (node->getAttribute(ns, attr) != NULL) { + fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s);" + " using existing value in manifest.\n", + String8(attr).string(), String8(ns).string()); + return; + } + + node->addAttribute(ns, attr, String16(value)); +} + +static void fullyQualifyClassName(const String8& package, sp node, + const String16& attrName) { + XMLNode::attribute_entry* attr = node->editAttribute( + String16("http://schemas.android.com/apk/res/android"), attrName); + if (attr != NULL) { + String8 name(attr->string); + + // asdf --> package.asdf + // .asdf .a.b --> package.asdf package.a.b + // asdf.adsf --> asdf.asdf + String8 className; + const char* p = name.string(); + const char* q = strchr(p, '.'); + if (p == q) { + className += package; + className += name; + } else if (q == NULL) { + className += package; + className += "."; + className += name; + } else { + className += name; + } + NOISY(printf("Qualifying class '%s' to '%s'", name.string(), className.string())); + attr->string.setTo(String16(className)); + } +} + +status_t massageManifest(Bundle* bundle, sp root) +{ + root = root->searchElement(String16(), String16("manifest")); + if (root == NULL) { + fprintf(stderr, "No tag.\n"); + return UNKNOWN_ERROR; + } + + addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode", + bundle->getVersionCode()); + addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName", + bundle->getVersionName()); + + if (bundle->getMinSdkVersion() != NULL + || bundle->getTargetSdkVersion() != NULL + || bundle->getMaxSdkVersion() != NULL) { + sp vers = root->getChildElement(String16(), String16("uses-sdk")); + if (vers == NULL) { + vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk")); + root->insertChildAt(vers, 0); + } + + addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion", + bundle->getMinSdkVersion()); + addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "targetSdkVersion", + bundle->getTargetSdkVersion()); + addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "maxSdkVersion", + bundle->getMaxSdkVersion()); + } + + if (bundle->getDebugMode()) { + sp application = root->getChildElement(String16(), String16("application")); + if (application != NULL) { + addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable", "true"); + } + } + + // Deal with manifest package name overrides + const char* manifestPackageNameOverride = bundle->getManifestPackageNameOverride(); + if (manifestPackageNameOverride != NULL) { + // Update the actual package name + XMLNode::attribute_entry* attr = root->editAttribute(String16(), String16("package")); + if (attr == NULL) { + fprintf(stderr, "package name is required with --rename-manifest-package.\n"); + return UNKNOWN_ERROR; + } + String8 origPackage(attr->string); + attr->string.setTo(String16(manifestPackageNameOverride)); + NOISY(printf("Overriding package '%s' to be '%s'\n", origPackage.string(), manifestPackageNameOverride)); + + // Make class names fully qualified + sp application = root->getChildElement(String16(), String16("application")); + if (application != NULL) { + fullyQualifyClassName(origPackage, application, String16("name")); + fullyQualifyClassName(origPackage, application, String16("backupAgent")); + + Vector >& children = const_cast >&>(application->getChildren()); + for (size_t i = 0; i < children.size(); i++) { + sp child = children.editItemAt(i); + String8 tag(child->getElementName()); + if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") { + fullyQualifyClassName(origPackage, child, String16("name")); + } else if (tag == "activity-alias") { + fullyQualifyClassName(origPackage, child, String16("name")); + fullyQualifyClassName(origPackage, child, String16("targetActivity")); + } + } + } + } + + // Deal with manifest package name overrides + const char* instrumentationPackageNameOverride = bundle->getInstrumentationPackageNameOverride(); + if (instrumentationPackageNameOverride != NULL) { + // Fix up instrumentation targets. + Vector >& children = const_cast >&>(root->getChildren()); + for (size_t i = 0; i < children.size(); i++) { + sp child = children.editItemAt(i); + String8 tag(child->getElementName()); + if (tag == "instrumentation") { + XMLNode::attribute_entry* attr = child->editAttribute( + String16("http://schemas.android.com/apk/res/android"), String16("targetPackage")); + if (attr != NULL) { + attr->string.setTo(String16(instrumentationPackageNameOverride)); + } + } + } + } + + return NO_ERROR; } #define ASSIGN_IT(n) \ @@ -496,6 +820,35 @@ static void applyFileOverlay(const sp& assets, } \ } while (0) +status_t updatePreProcessedCache(Bundle* bundle) +{ + #if BENCHMARK + fprintf(stdout, "BENCHMARK: Starting PNG PreProcessing \n"); + long startPNGTime = clock(); + #endif /* BENCHMARK */ + + String8 source(bundle->getResourceSourceDirs()[0]); + String8 dest(bundle->getCrunchedOutputDir()); + + FileFinder* ff = new SystemFileFinder(); + CrunchCache cc(source,dest,ff); + + CacheUpdater* cu = new SystemCacheUpdater(bundle); + size_t numFiles = cc.crunch(cu); + + if (bundle->getVerbose()) + fprintf(stdout, "Crunched %d PNG files to update cache\n", (int)numFiles); + + delete ff; + delete cu; + + #if BENCHMARK + fprintf(stdout, "BENCHMARK: End PNG PreProcessing. Time Elapsed: %f ms \n" + ,(clock() - startPNGTime)/1000.0); + #endif /* BENCHMARK */ + return 0; +} + status_t buildResources(Bundle* bundle, const sp& assets) { // First, look for a package file to parse. This is required to @@ -507,7 +860,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) return UNKNOWN_ERROR; } - status_t err = parsePackage(assets, androidManifestFile); + status_t err = parsePackage(bundle, assets, androidManifestFile); if (err != NO_ERROR) { return err; } @@ -523,6 +876,17 @@ status_t buildResources(Bundle* bundle, const sp& assets) NOISY(printf("Found %d included resource packages\n", (int)table.size())); + // Standard flags for compiled XML and optional UTF-8 encoding + int xmlFlags = XML_COMPILE_STANDARD_RESOURCE; + + /* Only enable UTF-8 if the caller of aapt didn't specifically + * request UTF-16 encoding and the parameters of this package + * allow UTF-8 to be used. + */ + if (!bundle->getUTF16StringsOption()) { + xmlFlags |= XML_COMPILE_UTF8; + } + // -------------------------------------------------------------- // First, gather all resource information. // -------------------------------------------------------------- @@ -535,18 +899,24 @@ status_t buildResources(Bundle* bundle, const sp& assets) sp drawables; sp layouts; sp anims; + sp animators; + sp interpolators; sp xmls; sp raws; sp colors; sp menus; + sp mipmaps; ASSIGN_IT(drawable); ASSIGN_IT(layout); ASSIGN_IT(anim); + ASSIGN_IT(animator); + ASSIGN_IT(interpolator); ASSIGN_IT(xml); ASSIGN_IT(raw); ASSIGN_IT(color); ASSIGN_IT(menu); + ASSIGN_IT(mipmap); assets->setResources(resources); // now go through any resource overlays and collect their files @@ -559,18 +929,25 @@ status_t buildResources(Bundle* bundle, const sp& assets) current = current->getOverlay(); } // apply the overlay files to the base set - applyFileOverlay(assets, drawables, "drawable"); - applyFileOverlay(assets, layouts, "layout"); - applyFileOverlay(assets, anims, "anim"); - applyFileOverlay(assets, xmls, "xml"); - applyFileOverlay(assets, raws, "raw"); - applyFileOverlay(assets, colors, "color"); - applyFileOverlay(assets, menus, "menu"); + if (!applyFileOverlay(bundle, assets, &drawables, "drawable") || + !applyFileOverlay(bundle, assets, &layouts, "layout") || + !applyFileOverlay(bundle, assets, &anims, "anim") || + !applyFileOverlay(bundle, assets, &animators, "animator") || + !applyFileOverlay(bundle, assets, &interpolators, "interpolator") || + !applyFileOverlay(bundle, assets, &xmls, "xml") || + !applyFileOverlay(bundle, assets, &raws, "raw") || + !applyFileOverlay(bundle, assets, &colors, "color") || + !applyFileOverlay(bundle, assets, &menus, "menu") || + !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) { + return UNKNOWN_ERROR; + } bool hasErrors = false; if (drawables != NULL) { - err = preProcessImages(bundle, assets, drawables); + if (bundle->getOutputAPKFile() != NULL) { + err = preProcessImages(bundle, assets, drawables, "drawable"); + } if (err == NO_ERROR) { err = makeFileResources(bundle, assets, &table, drawables, "drawable"); if (err != NO_ERROR) { @@ -581,6 +958,20 @@ status_t buildResources(Bundle* bundle, const sp& assets) } } + if (mipmaps != NULL) { + if (bundle->getOutputAPKFile() != NULL) { + err = preProcessImages(bundle, assets, mipmaps, "mipmap"); + } + if (err == NO_ERROR) { + err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap"); + if (err != NO_ERROR) { + hasErrors = true; + } + } else { + hasErrors = true; + } + } + if (layouts != NULL) { err = makeFileResources(bundle, assets, &table, layouts, "layout"); if (err != NO_ERROR) { @@ -595,6 +986,20 @@ status_t buildResources(Bundle* bundle, const sp& assets) } } + if (animators != NULL) { + err = makeFileResources(bundle, assets, &table, animators, "animator"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (interpolators != NULL) { + err = makeFileResources(bundle, assets, &table, interpolators, "interpolator"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + if (xmls != NULL) { err = makeFileResources(bundle, assets, &table, xmls, "xml"); if (err != NO_ERROR) { @@ -671,7 +1076,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) ResourceDirIterator it(layouts, String8("layout")); while ((err=it.next()) == NO_ERROR) { String8 src = it.getFile()->getPrintableSource(); - err = compileXmlFile(assets, it.getFile(), &table); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err == NO_ERROR) { ResXMLTree block; block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); @@ -690,7 +1095,37 @@ status_t buildResources(Bundle* bundle, const sp& assets) if (anims != NULL) { ResourceDirIterator it(anims, String8("anim")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (err < NO_ERROR) { + hasErrors = true; + } + err = NO_ERROR; + } + + if (animators != NULL) { + ResourceDirIterator it(animators, String8("animator")); + while ((err=it.next()) == NO_ERROR) { + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (err < NO_ERROR) { + hasErrors = true; + } + err = NO_ERROR; + } + + if (interpolators != NULL) { + ResourceDirIterator it(interpolators, String8("interpolator")); + while ((err=it.next()) == NO_ERROR) { + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -705,7 +1140,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) if (xmls != NULL) { ResourceDirIterator it(xmls, String8("xml")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -727,7 +1162,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) if (colors != NULL) { ResourceDirIterator it(colors, String8("color")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -743,7 +1178,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) ResourceDirIterator it(menus, String8("menu")); while ((err=it.next()) == NO_ERROR) { String8 src = it.getFile()->getPrintableSource(); - err = compileXmlFile(assets, it.getFile(), &table); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -758,21 +1193,102 @@ status_t buildResources(Bundle* bundle, const sp& assets) err = NO_ERROR; } + if (table.validateLocalizations()) { + hasErrors = true; + } + + if (hasErrors) { + return UNKNOWN_ERROR; + } + const sp manifestFile(androidManifestFile->getFiles().valueAt(0)); String8 manifestPath(manifestFile->getPrintableSource()); + // Generate final compiled manifest file. + manifestFile->clearData(); + sp manifestTree = XMLNode::parse(manifestFile); + if (manifestTree == NULL) { + return UNKNOWN_ERROR; + } + err = massageManifest(bundle, manifestTree); + if (err < NO_ERROR) { + return err; + } + err = compileXmlFile(assets, manifestTree, manifestFile, &table); + if (err < NO_ERROR) { + return err; + } + + //block.restart(); + //printXMLBlock(&block); + + // -------------------------------------------------------------- + // Generate the final resource table. + // Re-flatten because we may have added new resource IDs + // -------------------------------------------------------------- + + ResTable finalResTable; + sp resFile; + + if (table.hasResources()) { + sp symbols = assets->getSymbolsFor(String8("R")); + err = table.addSymbols(symbols); + if (err < NO_ERROR) { + return err; + } + + resFile = getResourceFile(assets); + if (resFile == NULL) { + fprintf(stderr, "Error: unable to generate entry for resource data\n"); + return UNKNOWN_ERROR; + } + + err = table.flatten(bundle, resFile); + if (err < NO_ERROR) { + return err; + } + + if (bundle->getPublicOutputFile()) { + FILE* fp = fopen(bundle->getPublicOutputFile(), "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n", + (const char*)bundle->getPublicOutputFile(), strerror(errno)); + return UNKNOWN_ERROR; + } + if (bundle->getVerbose()) { + printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile()); + } + table.writePublicDefinitions(String16(assets->getPackage()), fp); + fclose(fp); + } + + // Read resources back in, + finalResTable.add(resFile->getData(), resFile->getSize(), NULL); + +#if 0 + NOISY( + printf("Generated resources:\n"); + finalResTable.print(); + ) +#endif + } + // Perform a basic validation of the manifest file. This time we // parse it with the comments intact, so that we can use them to // generate java docs... so we are not going to write this one // back out to the final manifest data. - err = compileXmlFile(assets, manifestFile, &table, + sp outManifestFile = new AaptFile(manifestFile->getSourceFile(), + manifestFile->getGroupEntry(), + manifestFile->getResourceType()); + err = compileXmlFile(assets, manifestFile, + outManifestFile, &table, XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES); if (err < NO_ERROR) { return err; } ResXMLTree block; - block.setTo(manifestFile->getData(), manifestFile->getSize(), true); + block.setTo(outManifestFile->getData(), outManifestFile->getSize(), true); String16 manifest16("manifest"); String16 permission16("permission"); String16 permission_group16("permission-group"); @@ -811,16 +1327,20 @@ status_t buildResources(Bundle* bundle, const sp& assets) continue; } if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) { - if (validateAttr(manifestPath, block, NULL, "package", + if (validateAttr(manifestPath, finalResTable, block, NULL, "package", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "sharedUserId", packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0 || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) { const bool isGroup = strcmp16(block.getElementName(&len), permission_group16.string()) == 0; - if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", - isGroup ? packageIdentCharsWithTheStupid + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", isGroup ? packageIdentCharsWithTheStupid : packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } @@ -898,56 +1418,56 @@ status_t buildResources(Bundle* bundle, const sp& assets) } syms->makeSymbolPublic(String8(e), srcPos); } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) { - if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", - packageIdentChars, true) != ATTR_OKAY) { + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) { - if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", - classIdentChars, true) != ATTR_OKAY) { + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, true) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "targetPackage", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) { - if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", - classIdentChars, false) != ATTR_OKAY) { + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, false) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "permission", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "process", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "taskAffinity", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) { - if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", - classIdentChars, true) != ATTR_OKAY) { + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, true) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "authorities", authoritiesIdentChars, true) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "permission", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "process", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; @@ -955,39 +1475,39 @@ status_t buildResources(Bundle* bundle, const sp& assets) } else if (strcmp16(block.getElementName(&len), service16.string()) == 0 || strcmp16(block.getElementName(&len), receiver16.string()) == 0 || strcmp16(block.getElementName(&len), activity16.string()) == 0) { - if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", - classIdentChars, true) != ATTR_OKAY) { + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, true) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "permission", packageIdentChars, false) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "process", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "taskAffinity", processIdentChars, false) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), action16.string()) == 0 || strcmp16(block.getElementName(&len), category16.string()) == 0) { - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "name", packageIdentChars, true) != ATTR_OKAY) { hasErrors = true; } } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) { - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "mimeType", typeIdentChars, true) != ATTR_OKAY) { hasErrors = true; } - if (validateAttr(manifestPath, block, + if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, "scheme", schemeIdentChars, true) != ATTR_OKAY) { hasErrors = true; @@ -996,68 +1516,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) } } - if (table.validateLocalizations()) { - hasErrors = true; - } - - if (hasErrors) { - return UNKNOWN_ERROR; - } - - // Generate final compiled manifest file. - manifestFile->clearData(); - err = compileXmlFile(assets, manifestFile, &table); - if (err < NO_ERROR) { - return err; - } - - //block.restart(); - //printXMLBlock(&block); - - // -------------------------------------------------------------- - // Generate the final resource table. - // Re-flatten because we may have added new resource IDs - // -------------------------------------------------------------- - - if (table.hasResources()) { - sp symbols = assets->getSymbolsFor(String8("R")); - err = table.addSymbols(symbols); - if (err < NO_ERROR) { - return err; - } - - sp resFile(getResourceFile(assets)); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } - - err = table.flatten(bundle, resFile); - if (err < NO_ERROR) { - return err; - } - - if (bundle->getPublicOutputFile()) { - FILE* fp = fopen(bundle->getPublicOutputFile(), "w+"); - if (fp == NULL) { - fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n", - (const char*)bundle->getPublicOutputFile(), strerror(errno)); - return UNKNOWN_ERROR; - } - if (bundle->getVerbose()) { - printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile()); - } - table.writePublicDefinitions(String16(assets->getPackage()), fp); - fclose(fp); - } - - NOISY( - ResTable rt; - rt.add(resFile->getData(), resFile->getSize(), NULL); - printf("Generated resources:\n"); - rt.print(); - ) - + if (resFile != NULL) { // These resources are now considered to be a part of the included // resources, for others to reference. err = assets->addIncludedResources(resFile); @@ -1066,6 +1525,7 @@ status_t buildResources(Bundle* bundle, const sp& assets) return err; } } + return err; } @@ -1164,10 +1624,16 @@ static status_t writeLayoutClasses( NA = idents.size(); + bool deprecated = false; + String16 comment = symbols->getComment(realClassName); fprintf(fp, "%s/** ", indentStr); if (comment.size() > 0) { - fprintf(fp, "%s\n", String8(comment).string()); + String8 cmt(comment); + fprintf(fp, "%s\n", cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else { fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string()); } @@ -1179,10 +1645,10 @@ static status_t writeLayoutClasses( hasTable = true; fprintf(fp, "%s

Includes the following attributes:

\n" - "%s \n" + "%s
\n" "%s \n" "%s \n" - "%s \n", + "%s \n", indentStr, indentStr, indentStr, @@ -1216,7 +1682,7 @@ static status_t writeLayoutClasses( } String16 name(name8); fixupSymbol(&name); - fprintf(fp, "%s \n", + fprintf(fp, "%s \n", indentStr, nclassName.string(), String8(name).string(), assets->getPackage().string(), @@ -1243,6 +1709,10 @@ static status_t writeLayoutClasses( } fprintf(fp, "%s */\n", getIndentSpace(indent)); + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", indentStr); + } + fprintf(fp, "%spublic static final int[] %s = {\n" "%s", @@ -1291,11 +1761,17 @@ static status_t writeLayoutClasses( //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(), // String8(attr16).string(), String8(name16).string(), typeSpecFlags); const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0; - + + bool deprecated = false; + fprintf(fp, "%s/**\n", indentStr); if (comment.size() > 0) { + String8 cmt(comment); fprintf(fp, "%s

\n%s @attr description\n", indentStr, indentStr); - fprintf(fp, "%s %s\n", indentStr, String8(comment).string()); + fprintf(fp, "%s %s\n", indentStr, cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else { fprintf(fp, "%s

This symbol is the offset where the {@link %s.R.attr#%s}\n" @@ -1307,7 +1783,11 @@ static status_t writeLayoutClasses( indentStr, nclassName.string()); } if (typeComment.size() > 0) { - fprintf(fp, "\n\n%s %s\n", indentStr, String8(typeComment).string()); + String8 cmt(typeComment); + fprintf(fp, "\n\n%s %s\n", indentStr, cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } if (comment.size() > 0) { if (pub) { @@ -1325,6 +1805,9 @@ static status_t writeLayoutClasses( fprintf(fp, "%s @attr name %s:%s\n", indentStr, "android", String8(name).string()); fprintf(fp, "%s*/\n", indentStr); + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", indentStr); + } fprintf(fp, "%spublic static final int %s_%s = %d;\n", indentStr, nclassName.string(), @@ -1340,7 +1823,8 @@ static status_t writeLayoutClasses( static status_t writeSymbolClass( FILE* fp, const sp& assets, bool includePrivate, - const sp& symbols, const String8& className, int indent) + const sp& symbols, const String8& className, int indent, + bool nonConstantId) { fprintf(fp, "%spublic %sfinal class %s {\n", getIndentSpace(indent), @@ -1350,13 +1834,17 @@ static status_t writeSymbolClass( size_t i; status_t err = NO_ERROR; + const char * id_format = nonConstantId ? + "%spublic static int %s=0x%08x;\n" : + "%spublic static final int %s=0x%08x;\n"; + size_t N = symbols->getSymbols().size(); for (i=0; igetSymbols().valueAt(i); if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) { continue; } - if (!includePrivate && !sym.isPublic) { + if (!assets->isJavaSymbol(sym, includePrivate)) { continue; } String16 name(sym.name); @@ -1366,11 +1854,16 @@ static status_t writeSymbolClass( } String16 comment(sym.comment); bool haveComment = false; + bool deprecated = false; if (comment.size() > 0) { haveComment = true; + String8 cmt(comment); fprintf(fp, "%s/** %s\n", - getIndentSpace(indent), String8(comment).string()); + getIndentSpace(indent), cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else if (sym.isPublic && !includePrivate) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), @@ -1378,21 +1871,26 @@ static status_t writeSymbolClass( } String16 typeComment(sym.typeComment); if (typeComment.size() > 0) { + String8 cmt(typeComment); if (!haveComment) { haveComment = true; fprintf(fp, - "%s/** %s\n", - getIndentSpace(indent), String8(typeComment).string()); + "%s/** %s\n", getIndentSpace(indent), cmt.string()); } else { fprintf(fp, - "%s %s\n", - getIndentSpace(indent), String8(typeComment).string()); + "%s %s\n", getIndentSpace(indent), cmt.string()); + } + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; } } if (haveComment) { fprintf(fp,"%s */\n", getIndentSpace(indent)); } - fprintf(fp, "%spublic static final int %s=0x%08x;\n", + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); + } + fprintf(fp, id_format, getIndentSpace(indent), String8(name).string(), (int)sym.int32Val); } @@ -1402,7 +1900,7 @@ static status_t writeSymbolClass( if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) { continue; } - if (!includePrivate && !sym.isPublic) { + if (!assets->isJavaSymbol(sym, includePrivate)) { continue; } String16 name(sym.name); @@ -1410,17 +1908,25 @@ static status_t writeSymbolClass( return UNKNOWN_ERROR; } String16 comment(sym.comment); + bool deprecated = false; if (comment.size() > 0) { + String8 cmt(comment); fprintf(fp, "%s/** %s\n" "%s */\n", - getIndentSpace(indent), String8(comment).string(), + getIndentSpace(indent), cmt.string(), getIndentSpace(indent)); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else if (sym.isPublic && !includePrivate) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), String8(sym.name).string()); } + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); + } fprintf(fp, "%spublic static final String %s=\"%s\";\n", getIndentSpace(indent), String8(name).string(), sym.stringVal.string()); @@ -1435,7 +1941,7 @@ static status_t writeSymbolClass( if (nclassName == "styleable") { styleableSymbols = nsymbols; } else { - err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent); + err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId); } if (err != NO_ERROR) { return err; @@ -1506,12 +2012,399 @@ status_t writeResourceSymbols(Bundle* bundle, const sp& assets, "\n" "package %s;\n\n", package.string()); - status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, className, 0); + status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, + className, 0, bundle->getNonConstantId()); if (err != NO_ERROR) { return err; } fclose(fp); + + // If we were asked to generate a dependency file, we'll go ahead and add this R.java + // as a target in the dependency file right next to it. + if (bundle->getGenDependencies()) { + // Add this R.java to the dependency file + String8 dependencyFile(bundle->getRClassDir()); + dependencyFile.appendPath("R.java.d"); + + fp = fopen(dependencyFile.string(), "a"); + fprintf(fp,"%s \\\n", dest.string()); + fclose(fp); + } } return NO_ERROR; } + + + +class ProguardKeepSet +{ +public: + // { rule --> { file locations } } + KeyedVector > rules; + + void add(const String8& rule, const String8& where); +}; + +void ProguardKeepSet::add(const String8& rule, const String8& where) +{ + ssize_t index = rules.indexOfKey(rule); + if (index < 0) { + index = rules.add(rule, SortedVector()); + } + rules.editValueAt(index).add(where); +} + +void +addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName, + const char* pkg, const String8& srcName, int line) +{ + String8 className(inClassName); + if (pkg != NULL) { + // asdf --> package.asdf + // .asdf .a.b --> package.asdf package.a.b + // asdf.adsf --> asdf.asdf + const char* p = className.string(); + const char* q = strchr(p, '.'); + if (p == q) { + className = pkg; + className.append(inClassName); + } else if (q == NULL) { + className = pkg; + className.append("."); + className.append(inClassName); + } + } + + String8 rule("-keep class "); + rule += className; + rule += " { (...); }"; + + String8 location("view "); + location += srcName; + char lineno[20]; + sprintf(lineno, ":%d", line); + location += lineno; + + keep->add(rule, location); +} + +void +addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName, + const char* pkg, const String8& srcName, int line) +{ + String8 rule("-keepclassmembers class * { *** "); + rule += memberName; + rule += "(...); }"; + + String8 location("onClick "); + location += srcName; + char lineno[20]; + sprintf(lineno, ":%d", line); + location += lineno; + + keep->add(rule, location); +} + +status_t +writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp& assets) +{ + status_t err; + ResXMLTree tree; + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + bool inApplication = false; + String8 error; + sp assGroup; + sp assFile; + String8 pkg; + + // First, look for a package file to parse. This is required to + // be able to generate the resource information. + assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml")); + if (assGroup == NULL) { + fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n"); + return -1; + } + + if (assGroup->getFiles().size() != 1) { + fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n", + assGroup->getFiles().valueAt(0)->getPrintableSource().string()); + } + + assFile = assGroup->getFiles().valueAt(0); + + err = parseXMLResource(assFile, &tree); + if (err != NO_ERROR) { + return err; + } + + tree.restart(); + + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (/* name == "Application" && */ depth == 2) { + inApplication = false; + } + depth--; + continue; + } + if (code != ResXMLTree::START_TAG) { + continue; + } + depth++; + String8 tag(tree.getElementName(&len)); + // printf("Depth %d tag %s\n", depth, tag.string()); + bool keepTag = false; + if (depth == 1) { + if (tag != "manifest") { + fprintf(stderr, "ERROR: manifest does not start with tag\n"); + return -1; + } + pkg = getAttribute(tree, NULL, "package", NULL); + } else if (depth == 2) { + if (tag == "application") { + inApplication = true; + keepTag = true; + + String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android", + "backupAgent", &error); + if (agent.length() > 0) { + addProguardKeepRule(keep, agent, pkg.string(), + assFile->getPrintableSource(), tree.getLineNumber()); + } + } else if (tag == "instrumentation") { + keepTag = true; + } + } + if (!keepTag && inApplication && depth == 3) { + if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") { + keepTag = true; + } + } + if (keepTag) { + String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android", + "name", &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + return -1; + } + if (name.length() > 0) { + addProguardKeepRule(keep, name, pkg.string(), + assFile->getPrintableSource(), tree.getLineNumber()); + } + } + } + + return NO_ERROR; +} + +struct NamespaceAttributePair { + const char* ns; + const char* attr; + + NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {} + NamespaceAttributePair() : ns(NULL), attr(NULL) {} +}; + +status_t +writeProguardForXml(ProguardKeepSet* keep, const sp& layoutFile, + const char* startTag, const KeyedVector* tagAttrPairs) +{ + status_t err; + ResXMLTree tree; + size_t len; + ResXMLTree::event_code_t code; + + err = parseXMLResource(layoutFile, &tree); + if (err != NO_ERROR) { + return err; + } + + tree.restart(); + + if (startTag != NULL) { + bool haveStart = false; + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code != ResXMLTree::START_TAG) { + continue; + } + String8 tag(tree.getElementName(&len)); + if (tag == startTag) { + haveStart = true; + } + break; + } + if (!haveStart) { + return NO_ERROR; + } + } + + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code != ResXMLTree::START_TAG) { + continue; + } + String8 tag(tree.getElementName(&len)); + + // If there is no '.', we'll assume that it's one of the built in names. + if (strchr(tag.string(), '.')) { + addProguardKeepRule(keep, tag, NULL, + layoutFile->getPrintableSource(), tree.getLineNumber()); + } else if (tagAttrPairs != NULL) { + ssize_t tagIndex = tagAttrPairs->indexOfKey(tag); + if (tagIndex >= 0) { + const NamespaceAttributePair& nsAttr = tagAttrPairs->valueAt(tagIndex); + ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr); + if (attrIndex < 0) { + // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n", + // layoutFile->getPrintableSource().string(), tree.getLineNumber(), + // tag.string(), nsAttr.ns, nsAttr.attr); + } else { + size_t len; + addProguardKeepRule(keep, + String8(tree.getAttributeStringValue(attrIndex, &len)), NULL, + layoutFile->getPrintableSource(), tree.getLineNumber()); + } + } + } + ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick"); + if (attrIndex >= 0) { + size_t len; + addProguardKeepMethodRule(keep, + String8(tree.getAttributeStringValue(attrIndex, &len)), NULL, + layoutFile->getPrintableSource(), tree.getLineNumber()); + } + } + + return NO_ERROR; +} + +static void addTagAttrPair(KeyedVector* dest, + const char* tag, const char* ns, const char* attr) { + dest->add(String8(tag), NamespaceAttributePair(ns, attr)); +} + +status_t +writeProguardForLayouts(ProguardKeepSet* keep, const sp& assets) +{ + status_t err; + + // tag:attribute pairs that should be checked in layout files. + KeyedVector kLayoutTagAttrPairs; + addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class"); + addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class"); + addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name"); + + // tag:attribute pairs that should be checked in xml files. + KeyedVector kXmlTagAttrPairs; + addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment"); + addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment"); + + const Vector >& dirs = assets->resDirs(); + const size_t K = dirs.size(); + for (size_t k=0; k& d = dirs.itemAt(k); + const String8& dirName = d->getLeaf(); + const char* startTag = NULL; + const KeyedVector* tagAttrPairs = NULL; + if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) { + tagAttrPairs = &kLayoutTagAttrPairs; + } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) { + startTag = "PreferenceScreen"; + tagAttrPairs = &kXmlTagAttrPairs; + } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) { + startTag = "menu"; + tagAttrPairs = NULL; + } else { + continue; + } + + const KeyedVector > groups = d->getFiles(); + const size_t N = groups.size(); + for (size_t i=0; i& group = groups.valueAt(i); + const DefaultKeyedVector >& files = group->getFiles(); + const size_t M = files.size(); + for (size_t j=0; j overlay = assets->getOverlay(); + if (overlay.get()) { + return writeProguardForLayouts(keep, overlay); + } + return NO_ERROR; +} + +status_t +writeProguardFile(Bundle* bundle, const sp& assets) +{ + status_t err = -1; + + if (!bundle->getProguardFile()) { + return NO_ERROR; + } + + ProguardKeepSet keep; + + err = writeProguardForAndroidManifest(&keep, assets); + if (err < 0) { + return err; + } + + err = writeProguardForLayouts(&keep, assets); + if (err < 0) { + return err; + } + + FILE* fp = fopen(bundle->getProguardFile(), "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open class file %s: %s\n", + bundle->getProguardFile(), strerror(errno)); + return UNKNOWN_ERROR; + } + + const KeyedVector >& rules = keep.rules; + const size_t N = rules.size(); + for (size_t i=0; i& locations = rules.valueAt(i); + const size_t M = locations.size(); + for (size_t j=0; j& files, FILE* fp) +{ + status_t deps = -1; + for (size_t file_i = 0; file_i < files->size(); ++file_i) { + // Add the full file path to the dependency file + fprintf(fp, "%s \\\n", files->itemAt(file_i).string()); + deps++; + } + return deps; +} + +status_t +writeDependencyPreReqs(Bundle* bundle, const sp& assets, FILE* fp, bool includeRaw) +{ + status_t deps = -1; + deps += writePathsToFile(assets->getFullResPaths(), fp); + if (includeRaw) { + deps += writePathsToFile(assets->getFullAssetPaths(), fp); + } + return deps; +}

AttributeSummary
AttributeDescription
{@link #%s_%s %s:%s}%s
{@link #%s_%s %s:%s}%s