diff options
Diffstat (limited to 'tools/aapt')
-rw-r--r-- | tools/aapt/AaptAssets.cpp | 151 | ||||
-rw-r--r-- | tools/aapt/AaptAssets.h | 21 | ||||
-rw-r--r-- | tools/aapt/AaptConfig.cpp | 11 | ||||
-rw-r--r-- | tools/aapt/Bundle.h | 17 | ||||
-rw-r--r-- | tools/aapt/Command.cpp | 33 | ||||
-rw-r--r-- | tools/aapt/Images.cpp | 95 | ||||
-rw-r--r-- | tools/aapt/Images.h | 16 | ||||
-rw-r--r-- | tools/aapt/Main.cpp | 47 | ||||
-rw-r--r-- | tools/aapt/Main.h | 9 | ||||
-rw-r--r-- | tools/aapt/Package.cpp | 306 | ||||
-rw-r--r-- | tools/aapt/Resource.cpp | 24 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.cpp | 7 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.h | 3 | ||||
-rw-r--r-- | tools/aapt/XMLNode.cpp | 43 | ||||
-rw-r--r-- | tools/aapt/XMLNode.h | 3 | ||||
-rw-r--r-- | tools/aapt/ZipFile.cpp | 65 | ||||
-rw-r--r-- | tools/aapt/ZipFile.h | 1 | ||||
-rw-r--r-- | tools/aapt/tests/ZipReading_test.cpp | 156 | ||||
-rw-r--r-- | tools/aapt/tests/mocks/MockZipEntry.h | 29 | ||||
-rw-r--r-- | tools/aapt/tests/mocks/MockZipFile.h | 29 |
20 files changed, 908 insertions, 158 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index d346731..1a5d512 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1048,8 +1048,23 @@ ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) goto bail; } totalCount += count; - } - else { + } else if (type == kFileTypeRegular) { + ZipFile* zip = new ZipFile; + status_t err = zip->open(String8(res), ZipFile::kOpenReadOnly); + if (err != NO_ERROR) { + fprintf(stderr, "error opening zip file %s\n", res); + delete zip; + totalCount = -1; + goto bail; + } + + count = current->slurpResourceZip(bundle, zip, res); + delete zip; + if (count < 0) { + totalCount = count; + goto bail; + } + } else { fprintf(stderr, "ERROR: '%s' is not a directory\n", res); return UNKNOWN_ERROR; } @@ -1214,96 +1229,90 @@ bail: } ssize_t -AaptAssets::slurpResourceZip(Bundle* /* bundle */, const char* filename) +AaptAssets::slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath) { + status_t err = NO_ERROR; int count = 0; SortedVector<AaptGroupEntry> entries; - ZipFile* zip = new ZipFile; - status_t err = zip->open(filename, ZipFile::kOpenReadOnly); - if (err != NO_ERROR) { - fprintf(stderr, "error opening zip file %s\n", filename); - count = err; - delete zip; - return -1; - } - const int N = zip->getNumEntries(); for (int i=0; i<N; i++) { ZipEntry* entry = zip->getEntryByIndex(i); - if (entry->getDeleted()) { + + if (!isEntryValid(bundle, entry)) { continue; } - String8 entryName(entry->getFileName()); + String8 entryName(entry->getFileName()); //ex: /res/drawable/foo.png + String8 entryLeaf = entryName.getPathLeaf(); //ex: foo.png + String8 entryDirFull = entryName.getPathDir(); //ex: res/drawable + String8 entryDir = entryDirFull.getPathLeaf(); //ex: drawable - String8 dirName = entryName.getPathDir(); - sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName); + err = addEntry(entryName, entryLeaf, entryDirFull, entryDir, String8(fullZipPath), 0); + if (err) continue; - String8 resType; - AaptGroupEntry kind; + count++; + } - String8 remain; - if (entryName.walkPath(&remain) == kResourceDir) { - // these are the resources, pull their type out of the directory name - kind.initFromDirName(remain.walkPath().string(), &resType); - } else { - // these are untyped and don't have an AaptGroupEntry - } - if (entries.indexOf(kind) < 0) { - entries.add(kind); - mGroupEntries.add(kind); - } + return count; +} - // use the one from the zip file if they both exist. - dir->removeFile(entryName.getPathLeaf()); +status_t +AaptAssets::addEntry(const String8& entryName, const String8& entryLeaf, + const String8& /* entryDirFull */, const String8& entryDir, + const String8& zipFile, int compressionMethod) +{ + AaptGroupEntry group; + String8 resType; + bool b = group.initFromDirName(entryDir, &resType); + if (!b) { + fprintf(stderr, "invalid resource directory name: %s\n", entryDir.string()); + return -1; + } - sp<AaptFile> file = new AaptFile(entryName, kind, resType); - status_t err = dir->addLeafFile(entryName.getPathLeaf(), file); - if (err != NO_ERROR) { - fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string()); - count = err; - goto bail; - } - file->setCompressionMethod(entry->getCompressionMethod()); + //This will do a cached lookup as well + sp<AaptDir> dir = makeDir(resType); //Does lookup as well on mdirs + sp<AaptFile> file = new AaptFile(entryName, group, resType, zipFile); + file->setCompressionMethod(compressionMethod); -#if 0 - if (entryName == "AndroidManifest.xml") { - printf("AndroidManifest.xml\n"); - } - printf("\n\nfile: %s\n", entryName.string()); -#endif - - size_t len = entry->getUncompressedLen(); - void* data = zip->uncompress(entry); - void* buf = file->editData(len); - memcpy(buf, data, len); - -#if 0 - const int OFF = 0; - const unsigned char* p = (unsigned char*)data; - const unsigned char* end = p+len; - p += OFF; - for (int i=0; i<32 && p < end; i++) { - printf("0x%03x ", i*0x10 + OFF); - for (int j=0; j<0x10 && p < end; j++) { - printf(" %02x", *p); - p++; - } - printf("\n"); - } -#endif + dir->addLeafFile(entryLeaf, file); - free(data); + sp<AaptDir> rdir = resDir(resType); + if (rdir == NULL) { + mResDirs.add(dir); + } - count++; + return NO_ERROR; +} + +bool AaptAssets::isEntryValid(Bundle* bundle, ZipEntry* entry) { + if (entry == NULL) { + return false; } -bail: - delete zip; - return count; + if (entry->getDeleted()) { + return false; + } + + // Entries that are not inside the internal zip path can be ignored + if (bundle->getInternalZipPath()) { + bool prefixed = (strncmp(entry->getFileName(), + bundle->getInternalZipPath(), + strlen(bundle->getInternalZipPath())) == 0); + if (!prefixed) { + return false; + } + } + + //Do not process directories + if (String8(entry->getFileName()).size() == 0) { + return false; + } + + return true; } + status_t AaptAssets::filter(Bundle* bundle) { WeakResourceFilter reqFilter; @@ -1530,7 +1539,7 @@ status_t AaptAssets::buildIncludedResources(Bundle* bundle) printf("Including resources from package: %s\n", includes[i].string()); } - if (!mIncludedAssets.addAssetPath(includes[i], NULL)) { + if (!mIncludedAssets.addAssetPath(includes[i], 0)) { fprintf(stderr, "ERROR: Asset package include '%s' not found.\n", includes[i].string()); return UNKNOWN_ERROR; diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 4fdc964..5b66e4e 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -27,6 +27,8 @@ using namespace android; extern const char * const gDefaultIgnoreAssets; extern const char * gUserIgnoreAssets; +extern bool endsWith(const char* haystack, const char* needle); + bool valid_symbol_name(const String8& str); class AaptAssets; @@ -146,7 +148,7 @@ class AaptFile : public RefBase { public: AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry, - const String8& resType) + const String8& resType, const String8& zipFile=String8("")) : mGroupEntry(groupEntry) , mResourceType(resType) , mSourceFile(sourceFile) @@ -154,9 +156,11 @@ public: , mDataSize(0) , mBufferSize(0) , mCompression(ZipEntry::kCompressStored) + , mZipFile(zipFile) { //printf("new AaptFile created %s\n", (const char*)sourceFile); } + virtual ~AaptFile() { free(mData); } @@ -188,6 +192,12 @@ public: // no compression is ZipEntry::kCompressStored. int getCompressionMethod() const { return mCompression; } void setCompressionMethod(int c) { mCompression = c; } + + // ZIP support. In this case the sourceFile is the zip entry name + // and zipFile is the path to the zip File. + // example: sourceFile = drawable-hdpi/foo.png, zipFile = res.zip + const String8& getZipFile() const { return mZipFile; } + private: friend class AaptGroup; @@ -199,6 +209,7 @@ private: size_t mDataSize; size_t mBufferSize; int mCompression; + String8 mZipFile; }; /** @@ -540,6 +551,8 @@ public: void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); } ssize_t slurpFromArgs(Bundle* bundle); + ssize_t slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath); + bool isEntryValid(Bundle* bundle, ZipEntry* entry); sp<AaptSymbols> getSymbolsFor(const String8& name); @@ -593,7 +606,11 @@ private: const bool overwrite=false); ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir); - ssize_t slurpResourceZip(Bundle* bundle, const char* filename); + + + status_t addEntry(const String8& entryName, const String8& entryLeaf, + const String8& entryDirFull, const String8& entryDir, + const String8& zipFile, int compressionMethod); status_t filter(Bundle* bundle); diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp index b12867a..fc42b8c 100644 --- a/tools/aapt/AaptConfig.cpp +++ b/tools/aapt/AaptConfig.cpp @@ -224,9 +224,20 @@ bool parse(const String8& str, ConfigDescription* out) { success: if (out != NULL) { +#ifndef HAVE_ANDROID_OS applyVersionForCompatibility(&config); +#else + // Calling applyVersionForCompatibility when compiling a theme can cause + // the path to be changed by AAPT which results in the themed assets not being + // loaded. The only time (as of right now) that aapt is run on an android device + // is when it is being used for themes, so this should be the correct behavior + // in this case. If AAPT is ever used on an android device for some other reason, + // we will need to change this. + printf("AAPT is running on Android, skipping applyVersionForCompatibility"); +#endif *out = config; } + return true; } diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index cbe7c5d..145eb64 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // State bundle. Used to pass around stuff like command-line args. // @@ -49,7 +50,7 @@ public: Bundle(void) : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false), mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), - mUpdate(false), mExtending(false), + mUpdate(false), mExtending(false), mExtendedPackageId(0), mRequireLocalization(false), mPseudolocalize(NO_PSEUDOLOCALIZATION), mWantUTF16(false), mValues(false), mIncludeMetaData(false), mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), @@ -65,6 +66,8 @@ public: mProduct(NULL), mUseCrunchCache(false), mErrorOnFailedInsert(false), mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL), mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), + mOutputResourcesApkFile(NULL), + mInternalZipPath(NULL), mInputAPKFile(NULL), mBuildSharedLibrary(false), mArgc(0), mArgv(NULL) {} @@ -94,6 +97,8 @@ public: void setUpdate(bool val) { mUpdate = val; } bool getExtending(void) const { return mExtending; } void setExtending(bool val) { mExtending = val; } + int getExtendedPackageId(void) const { return mExtendedPackageId; } + void setExtendedPackageId(int val) { mExtendedPackageId = val; } bool getRequireLocalization(void) const { return mRequireLocalization; } void setRequireLocalization(bool val) { mRequireLocalization = val; } short getPseudolocalize(void) const { return mPseudolocalize; } @@ -109,6 +114,10 @@ public: void setJunkPath(bool val) { mJunkPath = val; } const char* getOutputAPKFile() const { return mOutputAPKFile; } void setOutputAPKFile(const char* val) { mOutputAPKFile = val; } + const char* getOutputResApk() { return mOutputResourcesApkFile; } + const char* getInputAPKFile() { return mInputAPKFile; } + void setInputAPKFile(const char* val) { mInputAPKFile = val; } + void setOutputResApk(const char* val) { mOutputResourcesApkFile = val; } const char* getManifestPackageNameOverride() const { return mManifestPackageNameOverride; } void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; } const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; } @@ -204,6 +213,8 @@ public: void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; } const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; } void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } + void setInternalZipPath(const char* val) { mInternalZipPath = val; } + const char* getInternalZipPath() const { return mInternalZipPath; } bool getBuildSharedLibrary() const { return mBuildSharedLibrary; } void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; } void setNoVersionVectors(bool val) { mNoVersionVectors = val; } @@ -275,6 +286,7 @@ private: bool mMakePackageDirs; bool mUpdate; bool mExtending; + int mExtendedPackageId; bool mRequireLocalization; short mPseudolocalize; bool mWantUTF16; @@ -326,6 +338,9 @@ private: const char* mOutputTextSymbols; const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; + const char* mOutputResourcesApkFile; + const char* mInternalZipPath; + const char* mInputAPKFile; bool mBuildSharedLibrary; android::String8 mPlatformVersionCode; android::String8 mPlatformVersionName; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 8a0a39c..4c10868 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -2439,21 +2439,48 @@ int doPackage(Bundle* bundle) goto bail; } - // Write the apk - if (outputAPKFile) { + if (outputAPKFile || bundle->getOutputResApk()) { // Gather all resources and add them to the APK Builder. The builder will then // figure out which Split they belong in. err = addResourcesToBuilder(assets, builder); if (err != NO_ERROR) { goto bail; } + } + + //Write the res apk + if (bundle->getOutputResApk()) { + const char* resPath = bundle->getOutputResApk(); + char *endptr; + int resApk_fd = strtol(resPath, &endptr, 10); + + if (*endptr == '\0') { + //OutputResDir was a file descriptor + //Assume there is only one set of assets, when we deal with actual split apks this may have to change + err = writeAPK(bundle, resApk_fd, builder->getBaseSplit(), true); + } else { + //Assume there is only one set of assets, when we deal with actual split apks this may have to change + err = writeAPK(bundle, String8(bundle->getOutputResApk()), builder->getBaseSplit(), true); + } + + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: writing '%s' failed\n", resPath); + goto bail; + } + } + + // Write the apk + if (outputAPKFile) { + if (err != NO_ERROR) { + goto bail; + } const Vector<sp<ApkSplit> >& splits = builder->getSplits(); const size_t numSplits = splits.size(); for (size_t i = 0; i < numSplits; i++) { const sp<ApkSplit>& split = splits[i]; String8 outputPath = buildApkName(String8(outputAPKFile), split); - err = writeAPK(bundle, outputPath, split); + err = writeAPK(bundle, outputPath, split, false); if (err != NO_ERROR) { fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string()); goto bail; diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index e4738f5..528a960 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -27,6 +27,15 @@ png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length) } } +static void +png_read_mem_file(png_structp png_ptr, png_bytep data, png_size_t length) +{ + PngMemoryFile* pngFile = (PngMemoryFile*) png_get_io_ptr(png_ptr); + status_t err = pngFile->read(data, length); + if (err != NO_ERROR) { + png_error(png_ptr, "Read Error"); + } +} static void png_flush_aapt_file(png_structp /* png_ptr */) @@ -1269,29 +1278,39 @@ static bool write_png_protected(png_structp write_ptr, String8& printableName, p return true; } -status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets */, +status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets */, //non-theme path const sp<AaptFile>& file, String8* /* outNewLeafName */) { String8 ext(file->getPath().getPathExtension()); + bool isImageInZip = !file->getZipFile().isEmpty(); // We currently only process PNG images. if (strcmp(ext.string(), ".png") != 0) { return NO_ERROR; } + String8 printableName(file->getPrintableSource()); + + // We currently only process nine patch PNG images when building a theme apk. + Bundle* b = const_cast<Bundle*>(bundle); + if (!endsWith(printableName.string(), ".9.png") && b->getOutputResApk() != NULL) { + if (bundle->getVerbose()) { + printf("Skipping image: %s\n", file->getPrintableSource().string()); + } + return NO_ERROR; + } + // Example of renaming a file: //*outNewLeafName = file->getPath().getBasePath().getFileName(); //outNewLeafName->append(".nupng"); - String8 printableName(file->getPrintableSource()); - if (bundle->getVerbose()) { printf("Processing image: %s\n", printableName.string()); } png_structp read_ptr = NULL; png_infop read_info = NULL; - FILE* fp; + FILE* fp = NULL; image_info imageInfo; @@ -1300,12 +1319,7 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets * status_t error = UNKNOWN_ERROR; - fp = fopen(file->getSourceFile().string(), "rb"); - if (fp == NULL) { - fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string()); - goto bail; - } - + const size_t nameLen = file->getPath().length(); read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL, (png_error_ptr)NULL); if (!read_ptr) { @@ -1317,8 +1331,47 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets * goto bail; } - if (!read_png_protected(read_ptr, printableName, read_info, file, fp, &imageInfo)) { - goto bail; + if (isImageInZip) { + PngMemoryFile* pmf = new PngMemoryFile(); + + ZipFile* zip = new ZipFile; + status_t err = zip->open(file->getZipFile(), ZipFile::kOpenReadOnly); + if (NO_ERROR != err) { + fprintf(stderr, "ERROR: Unable to open %s\n", file->getZipFile().string()); + return err; + } + + ZipEntry* entry = zip->getEntryByName(file->getSourceFile().string()); + size_t len = entry->getUncompressedLen(); + void* data = zip->uncompress(entry); + void* buf = file->editData(len); + memcpy(buf, data, len); + free(data); + + pmf->setDataSource((const char*)file->getData(), file->getSize()); + png_set_read_fn(read_ptr, pmf, png_read_mem_file); + read_png(printableName.string(), read_ptr, read_info, &imageInfo); + if (nameLen > 6) { + const char* name = file->getPath().string(); + if (name[nameLen-5] == '9' && name[nameLen-6] == '.') { + if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) { + goto bail; + } + } + } + } else { + fp = fopen(file->getSourceFile().string(), "rb"); + if (fp == NULL) { + fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string()); + goto bail; + } + if (!read_png_protected(read_ptr, printableName, read_info, file, fp, &imageInfo)) { + goto bail; + } + } + + if (isImageInZip) { + file->clearData(); } write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL, @@ -1343,13 +1396,15 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets * error = NO_ERROR; - if (bundle->getVerbose()) { + if (bundle->getVerbose() && !isImageInZip) { fseek(fp, 0, SEEK_END); size_t oldSize = (size_t)ftell(fp); size_t newSize = file->getSize(); float factor = ((float)newSize)/oldSize; int percent = (int)(factor*100); printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent); + } else if (bundle->getVerbose() && isImageInZip) { + printf(" (processed image %s)\n", printableName.string()); } bail: @@ -1511,3 +1566,17 @@ status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, return NO_ERROR; } + +status_t PngMemoryFile::read(png_bytep data, png_size_t length) { + if (data == NULL) + return -1; + + if ((mIndex + length) >= mDataSize) { + length = mDataSize - mIndex; + } + + memcpy(data, mData + mIndex, length); + mIndex += length; + + return NO_ERROR; +} diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h index a0a94f8..3230ddc 100644 --- a/tools/aapt/Images.h +++ b/tools/aapt/Images.h @@ -10,6 +10,8 @@ #include "ResourceTable.h" #include "Bundle.h" +#include <png.h> + #include <utils/String8.h> #include <utils/RefBase.h> @@ -23,4 +25,18 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<AaptFile>& file); +class PngMemoryFile { +public: + PngMemoryFile(void) + : mData(NULL), mDataSize(0), mIndex(0) + {} + void setDataSource(const char* data, uint32_t size) { mData = data; mDataSize = size; mIndex = 0; } + status_t read(png_bytep data, png_size_t length); + +private: + const char* mData; + uint32_t mDataSize; + uint32_t mIndex; +}; + #endif diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index f832c60..aa67480 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // Android Asset Packaging Tool main entry point. // @@ -14,6 +15,7 @@ #include <cstdlib> #include <getopt.h> #include <cassert> +#include <ctype.h> using namespace android; @@ -56,7 +58,7 @@ void usage(void) " xmltree Print the compiled xmls in the given assets.\n" " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName); fprintf(stderr, - " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n" + " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n" " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n" " [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n" " [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n" @@ -114,7 +116,7 @@ void usage(void) " -m make package directories under location specified by -J\n" " -u update existing packages (add new, replace older, remove deleted files)\n" " -v verbose output\n" - " -x create extending (non-application) resource IDs\n" + " -x either create or assign (if specified) extending (non-application) resource IDs\n" " -z require localization of resource attributes marked with\n" " localization=\"suggested\"\n" " -A additional directory in which to find raw asset files\n" @@ -347,6 +349,14 @@ int main(int argc, char* const argv[]) break; case 'x': bundle.setExtending(true); + argc--; + argv++; + if (!argc || !isdigit(argv[0][0])) { + argc++; + argv--; + } else { + bundle.setExtendedPackageId(atoi(argv[0])); + } break; case 'z': bundle.setRequireLocalization(true); @@ -428,6 +438,17 @@ int main(int argc, char* const argv[]) convertPath(argv[0]); bundle.setAndroidManifestFile(argv[0]); break; + case 'X': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-X' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setInternalZipPath(argv[0]); + break; case 'P': argc--; argv++; @@ -497,6 +518,28 @@ int main(int argc, char* const argv[]) bundle.setCompressionMethod(ZipEntry::kCompressStored); } break; + case 'Z': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-Z' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setInputAPKFile(argv[0]); + break; + case 'r': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-r' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setOutputResApk(argv[0]); + break; case '-': if (strcmp(cp, "-debug-mode") == 0) { bundle.setDebugMode(true); diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index e84c4c5..0b8adbe 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -42,7 +42,14 @@ extern int calcPercent(long uncompressedLen, long compressedLen); extern android::status_t writeAPK(Bundle* bundle, const android::String8& outputFile, - const android::sp<OutputSet>& outputSet); + const android::sp<OutputSet>& outputSet, + bool isOverlay); +extern android::status_t writeAPK(Bundle* bundle, + int fd, + const android::sp<OutputSet>& outputSet, + bool isOverlay); +extern android::status_t writeResFile(FILE* fp, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder); +extern sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true); extern android::status_t updatePreProcessedCache(Bundle* bundle); diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index cb244ec..3daf644 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -37,8 +37,10 @@ static const char* kNoCompressExt[] = { }; /* fwd decls, so I can write this downward */ -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet); bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet, bool isOverlay); +bool processOverlayFile(Bundle* bundle, ZipFile* zip, + String8 storageName, const sp<const AaptFile>& file); bool okayToCompress(Bundle* bundle, const String8& pathName); ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); @@ -49,7 +51,81 @@ ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ -status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet) +status_t writeAPK(Bundle* bundle, ZipFile* zip, const char* outputFileName, + const sp<OutputSet>& outputSet, bool isOverlay) +{ + status_t result = NO_ERROR; + int count; + + if (bundle->getVerbose()) { + printf("Writing all files...\n"); + } + + count = processAssets(bundle, zip, outputSet, isOverlay); + if (count < 0) { + fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", + outputFileName); + result = count; + goto bail; + } + + if (bundle->getVerbose()) { + printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); + } + + if (!isOverlay) { + count = processJarFiles(bundle, zip); + if (count < 0) { + fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", + outputFileName); + result = count; + goto bail; + } + + if (bundle->getVerbose()) + printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); + } + + result = NO_ERROR; + + /* + * Check for cruft. We set the "marked" flag on all entries we created + * or decided not to update. If the entry isn't already slated for + * deletion, remove it now. + */ + { + if (bundle->getVerbose()) + printf("Checking for deleted files\n"); + int i, removed = 0; + for (i = 0; i < zip->getNumEntries(); i++) { + ZipEntry* entry = zip->getEntryByIndex(i); + + if (!entry->getMarked() && entry->getDeleted()) { + if (bundle->getVerbose()) { + printf(" (removing crufty '%s')\n", + entry->getFileName()); + } + zip->remove(entry); + removed++; + } + } + if (bundle->getVerbose() && removed > 0) + printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); + } + + /* tell Zip lib to process deletions and other pending changes */ + result = zip->flush(); + if (result != NO_ERROR) { + fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); + goto bail; + } + +bail: + return result; +} + +status_t writeAPK(Bundle* bundle, const String8& outputFile, + const sp<OutputSet>& outputSet, bool isOverlay) { #if BENCHMARK fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); @@ -58,7 +134,6 @@ status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet> status_t result = NO_ERROR; ZipFile* zip = NULL; - int count; //bundle->setPackageCount(0); @@ -105,64 +180,10 @@ status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet> goto bail; } - if (bundle->getVerbose()) { - printf("Writing all files...\n"); - } - - count = processAssets(bundle, zip, outputSet); - if (count < 0) { - fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", - outputFile.string()); - result = count; - goto bail; - } - - if (bundle->getVerbose()) { - printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); - } - - count = processJarFiles(bundle, zip); - if (count < 0) { - fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", - outputFile.string()); - result = count; - goto bail; - } - - if (bundle->getVerbose()) - printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); - - result = NO_ERROR; + result = writeAPK(bundle, zip, outputFile.string(), outputSet, isOverlay); - /* - * Check for cruft. We set the "marked" flag on all entries we created - * or decided not to update. If the entry isn't already slated for - * deletion, remove it now. - */ - { - if (bundle->getVerbose()) - printf("Checking for deleted files\n"); - int i, removed = 0; - for (i = 0; i < zip->getNumEntries(); i++) { - ZipEntry* entry = zip->getEntryByIndex(i); - - if (!entry->getMarked() && entry->getDeleted()) { - if (bundle->getVerbose()) { - printf(" (removing crufty '%s')\n", - entry->getFileName()); - } - zip->remove(entry); - removed++; - } - } - if (bundle->getVerbose() && removed > 0) - printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); - } - - /* tell Zip lib to process deletions and other pending changes */ - result = zip->flush(); if (result != NO_ERROR) { - fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); + fprintf(stderr, "ERROR: Writing apk failed\n"); goto bail; } @@ -215,7 +236,98 @@ bail: return result; } -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet) +/* + * The directory hierarchy looks like this: + * "outputDir" and "assetRoot" are existing directories. + * + * On success, "bundle->numPackages" will be the number of Zip packages + * we created. + */ +status_t writeAPK(Bundle* bundle, int fd, const sp<OutputSet>& outputSet, bool isOverlay) +{ + #if BENCHMARK + fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); + long startAPKTime = clock(); + #endif /* BENCHMARK */ + + status_t result = NO_ERROR; + ZipFile* zip = NULL; + + status_t status; + zip = new ZipFile; + status = zip->openfd(fd, ZipFile::kOpenReadWrite); + if (status != NO_ERROR) { + fprintf(stderr, "ERROR: unable to open file as Zip file for writing\n"); + result = PERMISSION_DENIED; + goto bail; + } + + result = writeAPK(bundle, zip, "file_descriptor", outputSet, isOverlay); + + if (result != NO_ERROR) { + fprintf(stderr, "ERROR: Writing apk failed\n"); + goto bail; + } + + /* anything here? */ + if (zip->getNumEntries() == 0) { + if (bundle->getVerbose()) { + printf("Archive is empty -- removing\n"); + } + delete zip; // close the file so we can remove it in Win32 + zip = NULL; + close(fd); + } + + assert(result == NO_ERROR); + +bail: + delete zip; // must close before remove in Win32 + close(fd); + if (result != NO_ERROR) { + if (bundle->getVerbose()) { + printf("Removing archive due to earlier failures\n"); + } + } + + if (result == NO_ERROR && bundle->getVerbose()) + printf("Done!\n"); + + #if BENCHMARK + fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0); + #endif /* BENCHMARK */ + return result; +} + +status_t writeResFile(FILE* fp, const sp<AaptAssets>& /* assets */, sp<ApkBuilder>& builder) { + if (fp == NULL) { + fprintf(stderr, "Unable to open resFile for writing resTable\n"); + return PERMISSION_DENIED; + } + + sp<ApkSplit> split = builder->getBaseSplit(); + const std::set<OutputEntry>& entries = split->getEntries(); + std::set<OutputEntry>::const_iterator iter = entries.begin(); + for (; iter != entries.end(); iter++) { + const OutputEntry& entry = *iter; + + if (entry.getPath() == String8("resources.arsc")) { + sp<const AaptFile> resFile = entry.getFile(); + + int count = 0; + count = fwrite(resFile->getData(), 1, resFile->getSize(), fp); + + if (count == 0) { + fprintf(stderr, "Nothing written to resFile\n"); + } + } + } + + return NO_ERROR; +} + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet, + bool isOverlay) { ssize_t count = 0; const std::set<OutputEntry>& entries = outputSet->getEntries(); @@ -227,7 +339,9 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& o } else { String8 storagePath(entry.getPath()); storagePath.convertToResPath(); - if (!processFile(bundle, zip, storagePath, entry.getFile())) { + bool ret = isOverlay ? processOverlayFile(bundle, zip, storagePath, entry.getFile()) + : processFile(bundle, zip, storagePath, entry.getFile()); + if (!ret) { return UNKNOWN_ERROR; } count++; @@ -360,6 +474,76 @@ bool processFile(Bundle* bundle, ZipFile* zip, } /* + * Process a regular file, adding it to the archive if appropriate. + * + * This function is intended for use when creating a cached overlay package. + * Only xml and .9.png files are processed and added to the package. + * + * If we're in "update" mode, and the file already exists in the archive, + * delete the existing entry before adding the new one. + */ +bool processOverlayFile(Bundle* bundle, ZipFile* zip, + String8 storageName, const sp<const AaptFile>& file) +{ + const bool hasData = file->hasData(); + + storageName.convertToResPath(); + ZipEntry* entry; + bool fromGzip = false; + status_t result; + + if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) { + fromGzip = true; + storageName = storageName.getBasePath(); + } + + if (bundle->getUpdate()) { + entry = zip->getEntryByName(storageName.string()); + if (entry != NULL) { + /* file already exists in archive; there can be only one */ + if (entry->getMarked()) { + fprintf(stderr, + "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", + file->getPrintableSource().string()); + return false; + } + zip->remove(entry); + } + } + + if (hasData) { + const char* name = storageName.string(); + if (endsWith(name, ".9.png") || endsWith(name, ".xml") || endsWith(name, ".arsc")) { + result = zip->add(file->getData(), file->getSize(), storageName.string(), + file->getCompressionMethod(), &entry); + if (result == NO_ERROR) { + if (bundle->getVerbose()) { + printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : ""); + if (entry->getCompressionMethod() == ZipEntry::kCompressStored) { + printf(" (not compressed)\n"); + } else { + printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(), + entry->getCompressedLen())); + } + } + entry->setMarked(true); + } else { + if (result == ALREADY_EXISTS) { + fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", + file->getPrintableSource().string()); + } else { + fprintf(stderr, " Unable to add '%s': Zip add failed\n", + file->getPrintableSource().string()); + } + return false; + } + } + } + + return true; +} + +/* * Determine whether or not we want to try to compress this file based * on the file extension. */ diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 5d20815..c636c28 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -221,6 +221,24 @@ bool isValidResourceType(const String8& type) || type == "color" || type == "menu" || type == "mipmap"; } +sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary) +{ + sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc")); + sp<AaptFile> file; + if (group != NULL) { + file = group->getFiles().valueFor(AaptGroupEntry()); + if (file != NULL) { + return file; + } + } + + if (!makeIfNecessary) { + return NULL; + } + return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(), + NULL, String8()); +} + static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, const sp<AaptGroup>& grp) { @@ -1170,7 +1188,9 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil packageType = ResourceTable::AppFeature; } - ResourceTable table(bundle, String16(assets->getPackage()), packageType); + int extendedPackageId = bundle->getExtendedPackageId(); + + ResourceTable table(bundle, String16(assets->getPackage()), packageType, extendedPackageId); err = table.addIncludedResources(bundle, assets); if (err != NO_ERROR) { return err; @@ -1252,7 +1272,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil bool hasErrors = false; if (drawables != NULL) { - if (bundle->getOutputAPKFile() != NULL) { + if (bundle->getOutputAPKFile() != NULL || bundle->getOutputResApk()) { err = preProcessImages(bundle, assets, drawables, "drawable"); } if (err == NO_ERROR) { diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 1f736d0..889883a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1753,7 +1753,7 @@ status_t compileResourceFile(Bundle* bundle, return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } -ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type) +ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type, ssize_t pkgIdOverride) : mAssetsPackage(assetsPackage) , mPackageType(type) , mTypeIdOffset(0) @@ -1779,6 +1779,11 @@ ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, Reso assert(0); break; } + + if (pkgIdOverride != 0) { + packageId = pkgIdOverride; + } + sp<Package> package = new Package(mAssetsPackage, packageId); mPackages.add(assetsPackage, package); mOrderedPackages.add(package); diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index c4bdf09..2c2df19 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -109,7 +109,8 @@ public: const ConfigDescription& sourceConfig, const int sdkVersionToGenerate); - ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type); + ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type, + ssize_t pkgIdOverride); const String16& getAssetsPackage() const { return mAssetsPackage; diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index ca3f687..6ab55d5 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -11,6 +11,7 @@ #include <utils/ByteOrder.h> #include <errno.h> #include <string.h> +#include <androidfw/AssetManager.h> #ifndef HAVE_MS_C_RUNTIME #define O_BINARY 0 @@ -583,9 +584,51 @@ status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree, return NO_ERROR; } +sp<XMLNode> XMLNode::parseFromZip(const sp<AaptFile>& file) { + AssetManager assets; + int32_t cookie; + + if (!assets.addAssetPath(file->getZipFile(), &cookie)) { + fprintf(stderr, "Error: Could not open path %s\n", file->getZipFile().string()); + return NULL; + } + + Asset* asset = assets.openNonAsset(cookie, file->getSourceFile(), Asset::ACCESS_BUFFER); + ssize_t len = asset->getLength(); + const void* buf = asset->getBuffer(false); + + XML_Parser parser = XML_ParserCreateNS(NULL, 1); + ParseState state; + state.filename = file->getPrintableSource(); + state.parser = parser; + XML_SetUserData(parser, &state); + XML_SetElementHandler(parser, startElement, endElement); + XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace); + XML_SetCharacterDataHandler(parser, characterData); + XML_SetCommentHandler(parser, commentData); + + bool done = true; + if (XML_Parse(parser, (char*) buf, len, done) == XML_STATUS_ERROR) { + SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error( + "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser))); + return NULL; + } + XML_ParserFree(parser); + if (state.root == NULL) { + SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing"); + } + return state.root; +} + sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file) { char buf[16384]; + + //Check for zip first + if (file->getZipFile().length() > 0) { + return parseFromZip(file); + } + int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY); if (fd < 0) { SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s", diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h index 3161f65..905c6fd 100644 --- a/tools/aapt/XMLNode.h +++ b/tools/aapt/XMLNode.h @@ -188,6 +188,9 @@ private: status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest, bool stripComments, bool stripRawValues) const; + static sp<XMLNode> parseFromZip(const sp<AaptFile>& file); + static sp<XMLNode> parseFromAsset(const Asset& asset); + String16 mNamespacePrefix; String16 mNamespaceUri; String16 mElementName; diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp index 36f4e73..8a4eef5 100644 --- a/tools/aapt/ZipFile.cpp +++ b/tools/aapt/ZipFile.cpp @@ -130,6 +130,71 @@ status_t ZipFile::open(const char* zipFileName, int flags) } /* + * Open a file and parse its guts. + */ +status_t ZipFile::openfd(int fd, int flags) +{ + bool newArchive = true; + + assert(mZipFp == NULL); // no reopen + + if ((flags & kOpenTruncate)) + flags |= kOpenCreate; // trunc implies create + + if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) + return INVALID_OPERATION; // not both + if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) + return INVALID_OPERATION; // not neither + if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) + return INVALID_OPERATION; // create requires write + + /* open the file */ + const char* openflags; + if (flags & kOpenReadWrite) { + if (newArchive) + openflags = FILE_OPEN_RW_CREATE; + else + openflags = FILE_OPEN_RW; + } else { + openflags = FILE_OPEN_RO; + } + mZipFp = fdopen(fd, openflags); + if (mZipFp == NULL) { + int err = errno; + ALOGD("fdopen failed: %s\n", strerror(err)); + return errnoToStatus(err); + } + + status_t result; + if (!newArchive) { + /* + * Load the central directory. If that fails, then this probably + * isn't a Zip archive. + */ + result = readCentralDir(); + } else { + /* + * Newly-created. The EndOfCentralDir constructor actually + * sets everything to be the way we want it (all zeroes). We + * set mNeedCDRewrite so that we create *something* if the + * caller doesn't add any files. (We could also just unlink + * the file if it's brand new and nothing was added, but that's + * probably doing more than we really should -- the user might + * have a need for empty zip files.) + */ + mNeedCDRewrite = true; + result = NO_ERROR; + } + + if (flags & kOpenReadOnly) + mReadOnly = true; + else + assert(!mReadOnly); + + return result; +} + +/* * Return the Nth entry in the archive. */ ZipEntry* ZipFile::getEntryByIndex(int idx) const diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h index 7877550..d5abbf1 100644 --- a/tools/aapt/ZipFile.h +++ b/tools/aapt/ZipFile.h @@ -64,6 +64,7 @@ public: kOpenTruncate = 0x08, // if it exists, empty it }; status_t open(const char* zipFileName, int flags); + status_t openfd(int fd, int flags); /* * Add a file to the end of the archive. Specify whether you want the diff --git a/tools/aapt/tests/ZipReading_test.cpp b/tools/aapt/tests/ZipReading_test.cpp new file mode 100644 index 0000000..4b4f2da --- /dev/null +++ b/tools/aapt/tests/ZipReading_test.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#include <utils/String8.h> +#include <gtest/gtest.h> +#include <gmock/gmock.h> +#include <utils/KeyedVector.h> + +#include "mocks/MockZipFile.h" +#include "mocks/MockZipEntry.h" + +#include "AaptConfig.h" +#include "ConfigDescription.h" +#include "TestHelper.h" + +#include "AaptAssets.h" + +using android::String8; +using namespace testing; + +// A path to an apk that would be considered valid +#define VALID_APK_FILE "/valid/valid.apk" + +// Internal zip path to a dir that aapt is being asked to compile +#define COMPILING_OVERLAY_DIR "/assets/overlays/com.interesting.app" + +// Internal zip path to a valid resource. aapt is expected to compile this resource. +#define COMPILING_OVERLAY_FILE COMPILING_OVERLAY_DIR "/res/drawable-xxhdpi/foo.png"; + +// Internal zip path to another overlay dir that is NOT being compiled +#define NOT_COMPILING_OVERLAY_DIR "/assets/overlays/com.boring.app" + +// Internal zip path to a resource for an overlay that is NOT compiling. aapt is expected to ignore +#define NOT_COMPILING_OVERLAY_FILE COMPILING_OVERLAY_DIR "/assets/overlays/com.boring.app" + +static ::testing::AssertionResult TestParse(const String8& input, ConfigDescription* config=NULL) { + if (AaptConfig::parse(String8(input), config)) { + return ::testing::AssertionSuccess() << input << " was successfully parsed"; + } + return ::testing::AssertionFailure() << input << " could not be parsed"; +} + +static ::testing::AssertionResult TestParse(const char* input, ConfigDescription* config=NULL) { + return TestParse(String8(input), config); +} + +TEST(ZipReadingTest, TestValidZipEntryIsAdded) { + MockZipFile zip; + MockZipEntry entry1; + const char* zipFile = VALID_APK_FILE; + const char* validFilename = COMPILING_OVERLAY_FILE; + + EXPECT_CALL(entry1, getFileName()) + .WillRepeatedly(Return(validFilename)); + + EXPECT_CALL(zip, getNumEntries()) + .Times(1) + .WillRepeatedly(Return(1)); + + EXPECT_CALL(zip, getEntryByIndex(_)) + .Times(1) + .WillOnce(Return(&entry1)); + + sp<AaptAssets> assets = new AaptAssets(); + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + ssize_t count = assets->slurpResourceZip(&bundle, &zip, zipFile); + + Vector<sp<AaptDir> > dirs = assets->resDirs(); + EXPECT_EQ(1, dirs.size()); + EXPECT_EQ(1, count); +} + +TEST(ZipReadingTest, TestDifferentThemeEntryNotAdded) { + MockZipFile zip; + MockZipEntry entry1; + const char* zipFile = VALID_APK_FILE; + const char* invalidFile = NOT_COMPILING_OVERLAY_FILE; + + EXPECT_CALL(entry1, getFileName()) + .WillRepeatedly(Return(invalidFile)); + + EXPECT_CALL(zip, getNumEntries()) + .WillRepeatedly(Return(1)); + + EXPECT_CALL(zip, getEntryByIndex(_)) + .Times(1) + .WillOnce(Return(&entry1)); + + sp<AaptAssets> assets = new AaptAssets(); + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + ssize_t count = assets->slurpResourceZip(&bundle, &zip, zipFile); + + Vector<sp<AaptDir> > dirs = assets->resDirs(); + EXPECT_EQ(0, dirs.size()); + EXPECT_EQ(0, count); +} + +TEST(ZipReadingTest, TestOutsideEntryMarkedInvalid) { + Bundle bundle; + bundle.setInternalZipPath("VALID_OVERLAY_DIR"); + MockZipEntry invalidEntry; + const char* invalidFile = NOT_COMPILING_OVERLAY_FILE; + + EXPECT_CALL(invalidEntry, getFileName()) + .WillRepeatedly(Return(invalidFile)); + + sp<AaptAssets> assets = new AaptAssets(); + bool result = assets->isEntryValid(&bundle, &invalidEntry); + + EXPECT_FALSE(result); +} + +TEST(ZipReadingTest, TestNullEntryIsInvalid) { + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + MockZipEntry invalidEntry; + const char* invalidFile = NOT_COMPILING_OVERLAY_FILE; + + EXPECT_CALL(invalidEntry, getFileName()) + .WillRepeatedly(Return(invalidFile)); + + sp<AaptAssets> assets = new AaptAssets(); + bool result = assets->isEntryValid(&bundle, NULL); + + EXPECT_FALSE(result); +} + +TEST(ZipReadingTest, TestDirectoryEntryMarkedInvalid) { + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + MockZipEntry invalidEntry2; + // Add a "/" signifying this is a dir entry not a file entry. + const char* dir2 = COMPILING_OVERLAY_DIR"/"; + EXPECT_CALL(invalidEntry2, getFileName()) + .WillRepeatedly(Return(dir2)); + + sp<AaptAssets> assets = new AaptAssets(); + bool result2 = assets->isEntryValid(&bundle, &invalidEntry2); + + EXPECT_FALSE(result2); +} diff --git a/tools/aapt/tests/mocks/MockZipEntry.h b/tools/aapt/tests/mocks/MockZipEntry.h new file mode 100644 index 0000000..eef07f9 --- /dev/null +++ b/tools/aapt/tests/mocks/MockZipEntry.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#ifndef ANDROID_MOCK_ZIP_ENTRY +#define ANDROID_MOCK_ZIP_ENTRY + +#include "ZipFile.h" +#include "gmock/gmock.h" + +class MockZipEntry : public android::ZipEntry { +public: + MOCK_CONST_METHOD0(getCompressionMethod, int()); + MOCK_CONST_METHOD0(getFileName, const char* ()); +}; + +#endif // ANDROID_MOCK_ZIP_ENTRY diff --git a/tools/aapt/tests/mocks/MockZipFile.h b/tools/aapt/tests/mocks/MockZipFile.h new file mode 100644 index 0000000..0394ce7 --- /dev/null +++ b/tools/aapt/tests/mocks/MockZipFile.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#ifndef ANDROID_MOCK_ZIP_FILE +#define ANDROID_MOCK_ZIP_FILE + +#include "ZipFile.h" +#include "gmock/gmock.h" + +class MockZipFile : public android::ZipFile { +public: + MOCK_CONST_METHOD0(getNumEntries, int()); + MOCK_CONST_METHOD1(getEntryByIndex, android::ZipEntry* (int idx)); +}; + +#endif // ANDROID_MOCK_ZIP_FILE |