LOCAL_SRC_FILES := \
AaptAssets.cpp \
Command.cpp \
+ CrunchCache.cpp \
+ FileFinder.cpp \
Main.cpp \
Package.cpp \
StringPool.cpp \
kCommandAdd,
kCommandRemove,
kCommandPackage,
+ kCommandCrunch,
} Command;
/*
mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
mIsOverlayPackage(false),
mAutoAddOverlay(false), mGenDependencies(false),
- mAssetSourceDir(NULL), mProguardFile(NULL),
+ mAssetSourceDir(NULL),
+ mCrunchedOutputDir(NULL), mProguardFile(NULL),
mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL),
mVersionCode(NULL), mVersionName(NULL), mCustomPackage(NULL), mExtraPackages(NULL),
mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false), mProduct(NULL),
- mArgc(0), mArgv(NULL)
+ mUseCrunchCache(false), mArgc(0), mArgv(NULL)
{}
~Bundle(void) {}
*/
const char* getAssetSourceDir() const { return mAssetSourceDir; }
void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; }
+ const char* getCrunchedOutputDir() const { return mCrunchedOutputDir; }
+ void setCrunchedOutputDir(const char* dir) { mCrunchedOutputDir = dir; }
const char* getProguardFile() const { return mProguardFile; }
void setProguardFile(const char* file) { mProguardFile = file; }
const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; }
void setNonConstantId(bool val) { mNonConstantId = val; }
const char* getProduct() const { return mProduct; }
void setProduct(const char * val) { mProduct = val; }
+ void setUseCrunchCache(bool val) { mUseCrunchCache = val; }
+ bool getUseCrunchCache() { return mUseCrunchCache; }
/*
* Set and get the file specification.
bool mAutoAddOverlay;
bool mGenDependencies;
const char* mAssetSourceDir;
+ const char* mCrunchedOutputDir;
const char* mProguardFile;
const char* mAndroidManifestFile;
const char* mPublicOutputFile;
bool mDebugMode;
bool mNonConstantId;
const char* mProduct;
+ bool mUseCrunchCache;
/* file specification */
int mArgc;
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Abstraction of calls to system to make directories and delete files and
+// wrapper to image processing.
+
+#ifndef CACHE_UPDATER_H
+#define CACHE_UPDATER_H
+
+#include <utils/String8.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include "Images.h"
+
+using namespace android;
+
+/** CacheUpdater
+ * This is a pure virtual class that declares abstractions of functions useful
+ * for managing a cache files. This manager is set up to be used in a
+ * mirror cache where the source tree is duplicated and filled with processed
+ * images. This class is abstracted to allow for dependency injection during
+ * unit testing.
+ * Usage:
+ * To update/add a file to the cache, call processImage
+ * To remove a file from the cache, call deleteFile
+ */
+class CacheUpdater {
+public:
+ // Make sure all the directories along this path exist
+ virtual void ensureDirectoriesExist(String8 path) = 0;
+
+ // Delete a file
+ virtual void deleteFile(String8 path) = 0;
+
+ // Process an image from source out to dest
+ virtual void processImage(String8 source, String8 dest) = 0;
+private:
+};
+
+/** SystemCacheUpdater
+ * This is an implementation of the above virtual cache updater specification.
+ * This implementations hits the filesystem to manage a cache and calls out to
+ * the PNG crunching in images.h to process images out to its cache components.
+ */
+class SystemCacheUpdater : public CacheUpdater {
+public:
+ // Constructor to set bundle to pass to preProcessImage
+ SystemCacheUpdater (Bundle* b)
+ : bundle(b) { };
+
+ // Make sure all the directories along this path exist
+ virtual void ensureDirectoriesExist(String8 path)
+ {
+ // Check to see if we're dealing with a fully qualified path
+ String8 existsPath;
+ String8 toCreate;
+ String8 remains;
+ struct stat s;
+
+ // Check optomistically to see if all directories exist.
+ // If something in the path doesn't exist, then walk the path backwards
+ // and find the place to start creating directories forward.
+ if (stat(path.string(),&s) == -1) {
+ // Walk backwards to find place to start creating directories
+ existsPath = path;
+ do {
+ // As we remove the end of existsPath add it to
+ // the string of paths to create.
+ toCreate = existsPath.getPathLeaf().appendPath(toCreate);
+ existsPath = existsPath.getPathDir();
+ } while (stat(existsPath.string(),&s) == -1);
+
+ // Walk forwards and build directories as we go
+ do {
+ // Advance to the next segment of the path
+ existsPath.appendPath(toCreate.walkPath(&remains));
+ toCreate = remains;
+#ifdef HAVE_MS_C_RUNTIME
+ _mkdir(existsPath.string());
+#else
+ mkdir(existsPath.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+ } while (remains.length() > 0);
+ } //if
+ };
+
+ // Delete a file
+ virtual void deleteFile(String8 path)
+ {
+ if (remove(path.string()) != 0)
+ fprintf(stderr,"ERROR DELETING %s\n",path.string());
+ };
+
+ // Process an image from source out to dest
+ virtual void processImage(String8 source, String8 dest)
+ {
+ // Make sure we're trying to write to a directory that is extant
+ ensureDirectoriesExist(dest.getPathDir());
+
+ preProcessImageToCache(bundle, source, dest);
+ };
+private:
+ Bundle* bundle;
+};
+
+#endif // CACHE_UPDATER_H
\ No newline at end of file
}
return retVal;
}
+
+/*
+ * Do PNG Crunching
+ * PRECONDITIONS
+ * -S flag points to a source directory containing drawable* folders
+ * -C flag points to destination directory. The folder structure in the
+ * source directory will be mirrored to the destination (cache) directory
+ *
+ * POSTCONDITIONS
+ * Destination directory will be updated to match the PNG files in
+ * the source directory.
+ */
+int doCrunch(Bundle* bundle)
+{
+ fprintf(stdout, "Crunching PNG Files in ");
+ fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
+ fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
+
+ updatePreProcessedCache(bundle);
+
+ return NO_ERROR;
+}
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Implementation file for CrunchCache
+// This file defines functions laid out and documented in
+// CrunchCache.h
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+#include "CrunchCache.h"
+
+using namespace android;
+
+CrunchCache::CrunchCache(String8 sourcePath, String8 destPath, FileFinder* ff)
+ : mSourcePath(sourcePath), mDestPath(destPath), mSourceFiles(0), mDestFiles(0), mFileFinder(ff)
+{
+ // We initialize the default value to return to 0 so if a file doesn't exist
+ // then all files are automatically "newer" than it.
+
+ // Set file extensions to look for. Right now just pngs.
+ mExtensions.push(String8(".png"));
+
+ // Load files into our data members
+ loadFiles();
+}
+
+size_t CrunchCache::crunch(CacheUpdater* cu, bool forceOverwrite)
+{
+ size_t numFilesUpdated = 0;
+
+ // Iterate through the source files and compare to cache.
+ // After processing a file, remove it from the source files and
+ // from the dest files.
+ // We're done when we're out of files in source.
+ String8 relativePath;
+ while (mSourceFiles.size() > 0) {
+ // Get the full path to the source file, then convert to a c-string
+ // and offset our beginning pointer to the length of the sourcePath
+ // This efficiently strips the source directory prefix from our path.
+ // Also, String8 doesn't have a substring method so this is what we've
+ // got to work with.
+ const char* rPathPtr = mSourceFiles.keyAt(0).string()+mSourcePath.length();
+ // Strip leading slash if present
+ int offset = 0;
+ if (rPathPtr[0] == OS_PATH_SEPARATOR)
+ offset = 1;
+ relativePath = String8(rPathPtr + offset);
+
+ if (forceOverwrite || needsUpdating(relativePath)) {
+ cu->processImage(mSourcePath.appendPathCopy(relativePath),
+ mDestPath.appendPathCopy(relativePath));
+ numFilesUpdated++;
+ // crunchFile(relativePath);
+ }
+ // Delete this file from the source files and (if it exists) from the
+ // dest files.
+ mSourceFiles.removeItemsAt(0);
+ mDestFiles.removeItem(mDestPath.appendPathCopy(relativePath));
+ }
+
+ // Iterate through what's left of destFiles and delete leftovers
+ while (mDestFiles.size() > 0) {
+ cu->deleteFile(mDestFiles.keyAt(0));
+ mDestFiles.removeItemsAt(0);
+ }
+
+ // Update our knowledge of the files cache
+ // both source and dest should be empty by now.
+ loadFiles();
+
+ return numFilesUpdated;
+}
+
+void CrunchCache::loadFiles()
+{
+ // Clear out our data structures to avoid putting in duplicates
+ mSourceFiles.clear();
+ mDestFiles.clear();
+
+ // Make a directory walker that points to the system.
+ DirectoryWalker* dw = new SystemDirectoryWalker();
+
+ // Load files in the source directory
+ mFileFinder->findFiles(mSourcePath, mExtensions, mSourceFiles,dw);
+
+ // Load files in the destination directory
+ mFileFinder->findFiles(mDestPath,mExtensions,mDestFiles,dw);
+
+ delete dw;
+}
+
+bool CrunchCache::needsUpdating(String8 relativePath) const
+{
+ // Retrieve modification dates for this file entry under the source and
+ // cache directory trees. The vectors will return a modification date of 0
+ // if the file doesn't exist.
+ time_t sourceDate = mSourceFiles.valueFor(mSourcePath.appendPathCopy(relativePath));
+ time_t destDate = mDestFiles.valueFor(mDestPath.appendPathCopy(relativePath));
+ return sourceDate > destDate;
+}
\ No newline at end of file
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Cache manager for pre-processed PNG files.
+// Contains code for managing which PNG files get processed
+// at build time.
+//
+
+#ifndef CRUNCHCACHE_H
+#define CRUNCHCACHE_H
+
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+
+using namespace android;
+
+/** CrunchCache
+ * This class is a cache manager which can pre-process PNG files and store
+ * them in a mirror-cache. It's capable of doing incremental updates to its
+ * cache.
+ *
+ * Usage:
+ * Create an instance initialized with the root of the source tree, the
+ * root location to store the cache files, and an instance of a file finder.
+ * Then update the cache by calling crunch.
+ */
+class CrunchCache {
+public:
+ // Constructor
+ CrunchCache(String8 sourcePath, String8 destPath, FileFinder* ff);
+
+ // Nobody should be calling the default constructor
+ // So this space is intentionally left blank
+
+ // Default Copy Constructor and Destructor are fine
+
+ /** crunch is the workhorse of this class.
+ * It goes through all the files found in the sourcePath and compares
+ * them to the cached versions in the destPath. If the optional
+ * argument forceOverwrite is set to true, then all source files are
+ * re-crunched even if they have not been modified recently. Otherwise,
+ * source files are only crunched when they needUpdating. Afterwards,
+ * we delete any leftover files in the cache that are no longer present
+ * in source.
+ *
+ * PRECONDITIONS:
+ * No setup besides construction is needed
+ * POSTCONDITIONS:
+ * The cache is updated to fully reflect all changes in source.
+ * The function then returns the number of files changed in cache
+ * (counting deletions).
+ */
+ size_t crunch(CacheUpdater* cu, bool forceOverwrite=false);
+
+private:
+ /** loadFiles is a wrapper to the FileFinder that places matching
+ * files into mSourceFiles and mDestFiles.
+ *
+ * POSTCONDITIONS
+ * mDestFiles and mSourceFiles are refreshed to reflect the current
+ * state of the files in the source and dest directories.
+ * Any previous contents of mSourceFiles and mDestFiles are cleared.
+ */
+ void loadFiles();
+
+ /** needsUpdating takes a file path
+ * and returns true if the file represented by this path is newer in the
+ * sourceFiles than in the cache (mDestFiles).
+ *
+ * PRECONDITIONS:
+ * mSourceFiles and mDestFiles must be initialized and filled.
+ * POSTCONDITIONS:
+ * returns true if and only if source file's modification time
+ * is greater than the cached file's mod-time. Otherwise returns false.
+ *
+ * USAGE:
+ * Should be used something like the following:
+ * if (needsUpdating(filePath))
+ * // Recrunch sourceFile out to destFile.
+ *
+ */
+ bool needsUpdating(String8 relativePath) const;
+
+ // DATA MEMBERS ====================================================
+
+ String8 mSourcePath;
+ String8 mDestPath;
+
+ Vector<String8> mExtensions;
+
+ // Each vector of paths contains one entry per PNG file encountered.
+ // Each entry consists of a path pointing to that PNG.
+ DefaultKeyedVector<String8,time_t> mSourceFiles;
+ DefaultKeyedVector<String8,time_t> mDestFiles;
+
+ // Pointer to a FileFinder to use
+ FileFinder* mFileFinder;
+};
+
+#endif // CRUNCHCACHE_H
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Defines an abstraction for opening a directory on the filesystem and
+// iterating through it.
+
+#ifndef DIRECTORYWALKER_H
+#define DIRECTORYWALKER_H
+
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utils/String8.h>
+
+#include <stdio.h>
+
+using namespace android;
+
+// Directory Walker
+// This is an abstraction for walking through a directory and getting files
+// and descriptions.
+
+class DirectoryWalker {
+public:
+ virtual ~DirectoryWalker() {};
+ virtual bool openDir(String8 path) = 0;
+ virtual bool openDir(const char* path) = 0;
+ // Advance to next directory entry
+ virtual struct dirent* nextEntry() = 0;
+ // Get the stats for the current entry
+ virtual struct stat* entryStats() = 0;
+ // Clean Up
+ virtual void closeDir() = 0;
+ // This class is able to replicate itself on the heap
+ virtual DirectoryWalker* clone() = 0;
+
+ // DATA MEMBERS
+ // Current directory entry
+ struct dirent mEntry;
+ // Stats for that directory entry
+ struct stat mStats;
+ // Base path
+ String8 mBasePath;
+};
+
+// System Directory Walker
+// This is an implementation of the above abstraction that calls
+// real system calls and is fully functional.
+// functions are inlined since they're very short and simple
+
+class SystemDirectoryWalker : public DirectoryWalker {
+
+ // Default constructor, copy constructor, and destructor are fine
+public:
+ virtual bool openDir(String8 path) {
+ mBasePath = path;
+ dir = NULL;
+ dir = opendir(mBasePath.string() );
+
+ if (dir == NULL)
+ return false;
+
+ return true;
+ };
+ virtual bool openDir(const char* path) {
+ String8 p(path);
+ openDir(p);
+ return true;
+ };
+ // Advance to next directory entry
+ virtual struct dirent* nextEntry() {
+ struct dirent* entryPtr = readdir(dir);
+ if (entryPtr == NULL)
+ return NULL;
+
+ mEntry = *entryPtr;
+ // Get stats
+ String8 fullPath = mBasePath.appendPathCopy(mEntry.d_name);
+ stat(fullPath.string(),&mStats);
+ return &mEntry;
+ };
+ // Get the stats for the current entry
+ virtual struct stat* entryStats() {
+ return &mStats;
+ };
+ virtual void closeDir() {
+ closedir(dir);
+ };
+ virtual DirectoryWalker* clone() {
+ return new SystemDirectoryWalker(*this);
+ };
+private:
+ DIR* dir;
+};
+
+#endif // DIRECTORYWALKER_H
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+// File Finder implementation.
+// Implementation for the functions declared and documented in FileFinder.h
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+#include <utils/KeyedVector.h>
+
+
+#include <iostream>
+
+#include "DirectoryWalker.h"
+#include "FileFinder.h"
+
+//#define DEBUG
+
+using android::String8;
+using std::cout;
+using std::endl;
+
+bool SystemFileFinder::findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw)
+{
+ // Scan the directory pointed to by basePath
+ // check files and recurse into subdirectories.
+ if (!dw->openDir(basePath)) {
+ return false;
+ }
+#ifdef DEBUG
+ cout << "FileFinder looking in " << basePath << endl;
+#endif // DEBUG
+ /*
+ * Go through all directory entries. Check each file using checkAndAddFile
+ * and recurse into sub-directories.
+ */
+ struct dirent* entry;
+ while ((entry = dw->nextEntry()) != NULL) {
+ String8 entryName(entry->d_name);
+ if (entry->d_name[0] == '.') // Skip hidden files and directories
+ continue;
+
+ String8 fullPath = basePath.appendPathCopy(entryName);
+ // If this entry is a directory we'll recurse into it
+ if (entry->d_type == DT_DIR) {
+ DirectoryWalker* copy = dw->clone();
+ findFiles(fullPath, extensions, fileStore,copy);
+ delete copy;
+ }
+
+ // If this entry is a file, we'll pass it over to checkAndAddFile
+ if (entry->d_type == DT_REG) {
+ checkAndAddFile(fullPath,dw->entryStats(),extensions,fileStore);
+ }
+ }
+
+ // Clean up
+ dw->closeDir();
+
+ return true;
+}
+
+void SystemFileFinder::checkAndAddFile(String8 path, const struct stat* stats,
+ Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore)
+{
+#ifdef DEBUG
+ cout << "Checking file " << path << "...";
+#endif // DEBUG
+ // Loop over the extensions, checking for a match
+ bool done = false;
+ String8 ext(path.getPathExtension());
+ ext.toLower();
+ for (size_t i = 0; i < extensions.size() && !done; ++i) {
+ String8 ext2 = extensions[i].getPathExtension();
+ ext2.toLower();
+ // Compare the extensions. If a match is found, add to storage.
+ if (ext == ext2) {
+#ifdef DEBUG
+ cout << "Match";
+#endif // DEBUG
+ done = true;
+ fileStore.add(path,stats->st_mtime);
+ }
+ }
+#ifdef DEBUG
+ cout << endl;
+#endif //DEBUG
+}
\ No newline at end of file
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+// File Finder.
+// This is a collection of useful functions for finding paths and modification
+// times of files that match an extension pattern in a directory tree.
+// and finding files in it.
+
+#ifndef FILEFINDER_H
+#define FILEFINDER_H
+
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+
+using namespace android;
+
+// Abstraction to allow for dependency injection. See MockFileFinder.h
+// for the testing implementation.
+class FileFinder {
+public:
+ virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw) = 0;
+};
+
+class SystemFileFinder : public FileFinder {
+public:
+
+ /* findFiles takes a path, a Vector of extensions, and a destination KeyedVector
+ * and places path/modification date key/values pointing to
+ * all files with matching extensions found into the KeyedVector
+ * PRECONDITIONS
+ * path is a valid system path
+ * extensions should include leading "."
+ * This is not necessary, but the comparison directly
+ * compares the end of the path string so if the "."
+ * is excluded there is a small chance you could have
+ * a false positive match. (For example: extension "png"
+ * would match a file called "blahblahpng")
+ *
+ * POSTCONDITIONS
+ * fileStore contains (in no guaranteed order) paths to all
+ * matching files encountered in subdirectories of path
+ * as keys in the KeyedVector. Each key has the modification time
+ * of the file as its value.
+ *
+ * Calls checkAndAddFile on each file encountered in the directory tree
+ * Recursively descends into subdirectories.
+ */
+ virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw);
+
+private:
+ /**
+ * checkAndAddFile looks at a single file path and stat combo
+ * to determine whether it is a matching file (by looking at
+ * the extension)
+ *
+ * PRECONDITIONS
+ * no setup is needed
+ *
+ * POSTCONDITIONS
+ * If the given file has a matching extension then a new entry
+ * is added to the KeyedVector with the path as the key and the modification
+ * time as the value.
+ *
+ */
+ static void checkAndAddFile(String8 path, const struct stat* stats,
+ Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore);
+
+};
+#endif // FILEFINDER_H
return error;
}
+status_t preProcessImageToCache(Bundle* bundle, String8 source, String8 dest)
+{
+ png_structp read_ptr = NULL;
+ png_infop read_info = NULL;
+
+ FILE* fp;
+
+ image_info imageInfo;
+
+ png_structp write_ptr = NULL;
+ png_infop write_info = NULL;
+
+ status_t error = UNKNOWN_ERROR;
+
+ // Get a file handler to read from
+ fp = fopen(source.string(),"rb");
+ if (fp == NULL) {
+ fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
+ return error;
+ }
+
+ // Call libpng to get a struct to read image data into
+ read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!read_ptr) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Call libpng to get a struct to read image info into
+ read_info = png_create_info_struct(read_ptr);
+ if (!read_info) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Set a jump point for libpng to long jump back to on error
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Set up libpng to read from our file.
+ png_init_io(read_ptr,fp);
+
+ // Actually read data from the file
+ read_png(source.string(), read_ptr, read_info, &imageInfo);
+ // We're done reading so we can clean up
+ // Find old file size before releasing handle
+ fseek(fp, 0, SEEK_END);
+ size_t oldSize = (size_t)ftell(fp);
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+
+ // Check to see if we're dealing with a 9-patch
+ // If we are, process appropriately
+ if (source.getBasePath().getPathExtension() == ".9") {
+ if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
+ return error;
+ }
+ }
+
+ // Call libpng to create a structure to hold the processed image data
+ // that can be written to disk
+ write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!write_ptr) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Call libpng to create a structure to hold processed image info that can
+ // be written to disk
+ write_info = png_create_info_struct(write_ptr);
+ if (!write_info) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Open up our destination file for writing
+ fp = fopen(dest.string(), "wb");
+ if (!fp) {
+ fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Set up libpng to write to our file
+ png_init_io(write_ptr, fp);
+
+ // Set up a jump for libpng to long jump back on on errors
+ if (setjmp(png_jmpbuf(write_ptr))) {
+ fclose(fp);
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Actually write out to the new png
+ write_png(dest.string(), write_ptr, write_info, imageInfo,
+ bundle->getGrayscaleTolerance());
+
+ if (bundle->getVerbose()) {
+ // Find the size of our new file
+ FILE* reader = fopen(dest.string(), "rb");
+ fseek(reader, 0, SEEK_END);
+ size_t newSize = (size_t)ftell(reader);
+ fclose(reader);
+
+ float factor = ((float)newSize)/oldSize;
+ int percent = (int)(factor*100);
+ printf(" (processed image to cache entry %s: %d%% size of source)\n",
+ dest.string(), percent);
+ }
+
+ //Clean up
+ fclose(fp);
+ png_destroy_write_struct(&write_ptr, &write_info);
+
+ return NO_ERROR;
+}
status_t postProcessImage(const sp<AaptAssets>& assets,
ResourceTable* table, const sp<AaptFile>& file)
#define IMAGES_H
#include "ResourceTable.h"
+#include "Bundle.h"
+
+#include <utils/String8.h>
+#include <utils/RefBase.h>
+
+using android::String8;
status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
const sp<AaptFile>& file, String8* outNewLeafName);
+status_t preProcessImageToCache(Bundle* bundle, String8 source, String8 dest);
+
status_t postProcessImage(const sp<AaptAssets>& assets,
- ResourceTable* table, const sp<AaptFile>& file);
+ ResourceTable* table, const sp<AaptFile>& file);
#endif
fprintf(stderr,
" %s a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
" Add specified files to Zip-compatible archive.\n\n", gProgName);
+ fprintf(stderr,
+ " %s c[runch] [-v] -S resource-sources ... -C output-folder ...\n"
+ " Do PNG preprocessing and store the results in output folder.\n\n", gProgName);
fprintf(stderr,
" %s v[ersion]\n"
" Print program version.\n\n", gProgName);
case kCommandAdd: return doAdd(bundle);
case kCommandRemove: return doRemove(bundle);
case kCommandPackage: return doPackage(bundle);
+ case kCommandCrunch: return doCrunch(bundle);
default:
fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
return 1;
bundle.setCommand(kCommandRemove);
else if (argv[1][0] == 'p')
bundle.setCommand(kCommandPackage);
+ else if (argv[1][0] == 'c')
+ bundle.setCommand(kCommandCrunch);
else {
fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
wantUsage = true;
convertPath(argv[0]);
bundle.addResourceSourceDir(argv[0]);
break;
+ case 'C':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-C' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setCrunchedOutputDir(argv[0]);
+ break;
case '0':
argc--;
argv++;
bundle.setProduct(argv[0]);
} else if (strcmp(cp, "-non-constant-id") == 0) {
bundle.setNonConstantId(true);
- } else {
+ } else if (strcmp(cp, "-no-crunch") == 0) {
+ bundle.setUseCrunchCache(true);
+ }else {
fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
wantUsage = true;
goto bail;
#include "AaptAssets.h"
#include "ZipFile.h"
+
+/* Benchmarking Flag */
+//#define BENCHMARK 1
+
+#if BENCHMARK
+ #include <time.h>
+#endif /* BENCHMARK */
+
extern int doVersion(Bundle* bundle);
extern int doList(Bundle* bundle);
extern int doDump(Bundle* bundle);
extern int doAdd(Bundle* bundle);
extern int doRemove(Bundle* bundle);
extern int doPackage(Bundle* bundle);
+extern int doCrunch(Bundle* bundle);
extern int calcPercent(long uncompressedLen, long compressedLen);
const sp<AaptAssets>& assets,
const android::String8& outputFile);
+extern android::status_t updatePreProcessedCache(Bundle* bundle);
+
extern android::status_t buildResources(Bundle* bundle,
const sp<AaptAssets>& assets);
status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
const String8& outputFile)
{
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
+ long startAPKTime = clock();
+ #endif /* BENCHMARK */
+
status_t result = NO_ERROR;
ZipFile* zip = NULL;
int count;
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;
}
#include "ResourceTable.h"
#include "Images.h"
+#include "CrunchCache.h"
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+
#define NOISY(x) // x
// ==========================================================================
static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets,
const sp<ResourceTypeSet>& set, const char* type)
{
- ResourceDirIterator it(set, String8(type));
- Vector<sp<AaptFile> > newNameFiles;
- Vector<String8> newNamePaths;
bool hasErrors = false;
- ssize_t res;
- while ((res=it.next()) == NO_ERROR) {
- res = preProcessImage(bundle, assets, it.getFile(), NULL);
- if (res < NO_ERROR) {
- hasErrors = true;
+ ssize_t res = NO_ERROR;
+ if (bundle->getUseCrunchCache() == false) {
+ ResourceDirIterator it(set, String8(type));
+ Vector<sp<AaptFile> > newNameFiles;
+ Vector<String8> newNamePaths;
+ while ((res=it.next()) == NO_ERROR) {
+ res = preProcessImage(bundle, assets, it.getFile(), NULL);
+ if (res < NO_ERROR) {
+ hasErrors = true;
+ }
}
}
-
return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
}
} \
} 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<AaptAssets>& assets)
{
// First, look for a package file to parse. This is required to
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+#include <utils/String8.h>
+#include <iostream>
+#include <errno.h>
+
+#include "CrunchCache.h"
+#include "FileFinder.h"
+#include "MockFileFinder.h"
+#include "CacheUpdater.h"
+#include "MockCacheUpdater.h"
+
+using namespace android;
+using std::cout;
+using std::endl;
+
+void expectEqual(int got, int expected, const char* desc) {
+ cout << "Checking " << desc << ": ";
+ cout << "Got " << got << ", expected " << expected << "...";
+ cout << ( (got == expected) ? "PASSED" : "FAILED") << endl;
+ errno += ((got == expected) ? 0 : 1);
+}
+
+int main() {
+
+ errno = 0;
+
+ String8 source("res");
+ String8 dest("res2");
+
+ // Create data for MockFileFinder to feed to the cache
+ KeyedVector<String8, time_t> sourceData;
+ // This shouldn't be updated
+ sourceData.add(String8("res/drawable/hello.png"),3);
+ // This should be updated
+ sourceData.add(String8("res/drawable/world.png"),5);
+ // This should cause make directory to be called
+ sourceData.add(String8("res/drawable-cool/hello.png"),3);
+
+ KeyedVector<String8, time_t> destData;
+ destData.add(String8("res2/drawable/hello.png"),3);
+ destData.add(String8("res2/drawable/world.png"),3);
+ // this should call delete
+ destData.add(String8("res2/drawable/dead.png"),3);
+
+ // Package up data and create mock file finder
+ KeyedVector<String8, KeyedVector<String8,time_t> > data;
+ data.add(source,sourceData);
+ data.add(dest,destData);
+ FileFinder* ff = new MockFileFinder(data);
+ CrunchCache cc(source,dest,ff);
+
+ MockCacheUpdater* mcu = new MockCacheUpdater();
+ CacheUpdater* cu(mcu);
+
+ cout << "Running Crunch...";
+ int result = cc.crunch(cu);
+ cout << ((result > 0) ? "PASSED" : "FAILED") << endl;
+ errno += ((result > 0) ? 0 : 1);
+
+ const int EXPECTED_RESULT = 2;
+ expectEqual(result, EXPECTED_RESULT, "number of files touched");
+
+ cout << "Checking calls to deleteFile and processImage:" << endl;
+ const int EXPECTED_DELETES = 1;
+ const int EXPECTED_PROCESSED = 2;
+ // Deletes
+ expectEqual(mcu->deleteCount, EXPECTED_DELETES, "deleteFile");
+ // processImage
+ expectEqual(mcu->processCount, EXPECTED_PROCESSED, "processImage");
+
+ const int EXPECTED_OVERWRITES = 3;
+ result = cc.crunch(cu, true);
+ expectEqual(result, EXPECTED_OVERWRITES, "number of files touched with overwrite");
+ \
+
+ if (errno == 0)
+ cout << "ALL TESTS PASSED!" << endl;
+ else
+ cout << errno << " TESTS FAILED" << endl;
+
+ delete ff;
+ delete cu;
+
+ // TESTS BELOW WILL GO AWAY SOON
+
+ String8 source2("ApiDemos/res");
+ String8 dest2("ApiDemos/res2");
+
+ FileFinder* sff = new SystemFileFinder();
+ CacheUpdater* scu = new SystemCacheUpdater();
+
+ CrunchCache scc(source2,dest2,sff);
+
+ scc.crunch(scu);
+}
\ No newline at end of file
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <iostream>
+#include <cassert>
+#include <utils/String8.h>
+#include <utility>
+
+#include "DirectoryWalker.h"
+#include "MockDirectoryWalker.h"
+#include "FileFinder.h"
+
+using namespace android;
+
+using std::pair;
+using std::cout;
+using std::endl;
+
+
+
+int main()
+{
+
+ cout << "\n\n STARTING FILE FINDER TESTS" << endl;
+ String8 path("ApiDemos");
+
+ // Storage to pass to findFiles()
+ KeyedVector<String8,time_t> testStorage;
+
+ // Mock Directory Walker initialization. First data, then sdw
+ Vector< pair<String8,time_t> > data;
+ data.push( pair<String8,time_t>(String8("hello.png"),3) );
+ data.push( pair<String8,time_t>(String8("world.PNG"),3) );
+ data.push( pair<String8,time_t>(String8("foo.pNg"),3) );
+ // Neither of these should be found
+ data.push( pair<String8,time_t>(String8("hello.jpg"),3) );
+ data.push( pair<String8,time_t>(String8(".hidden.png"),3));
+
+ DirectoryWalker* sdw = new StringDirectoryWalker(path,data);
+
+ // Extensions to look for
+ Vector<String8> exts;
+ exts.push(String8(".png"));
+
+ errno = 0;
+
+ // Make sure we get a valid mock directory walker
+ // Make sure we finish without errors
+ cout << "Checking DirectoryWalker...";
+ assert(sdw != NULL);
+ cout << "PASSED" << endl;
+
+ // Make sure we finish without errors
+ cout << "Running findFiles()...";
+ bool findStatus = FileFinder::findFiles(path,exts, testStorage, sdw);
+ assert(findStatus);
+ cout << "PASSED" << endl;
+
+ const size_t SIZE_EXPECTED = 3;
+ // Check to make sure we have the right number of things in our storage
+ cout << "Running size comparison: Size is " << testStorage.size() << ", ";
+ cout << "Expected " << SIZE_EXPECTED << "...";
+ if(testStorage.size() == SIZE_EXPECTED)
+ cout << "PASSED" << endl;
+ else {
+ cout << "FAILED" << endl;
+ errno++;
+ }
+
+ // Check to make sure that each of our found items has the right extension
+ cout << "Checking Returned Extensions...";
+ bool extsOkay = true;
+ String8 wrongExts;
+ for (size_t i = 0; i < SIZE_EXPECTED; ++i) {
+ String8 testExt(testStorage.keyAt(i).getPathExtension());
+ testExt.toLower();
+ if (testExt != ".png") {
+ wrongExts += testStorage.keyAt(i);
+ wrongExts += "\n";
+ extsOkay = false;
+ }
+ }
+ if (extsOkay)
+ cout << "PASSED" << endl;
+ else {
+ cout << "FAILED" << endl;
+ cout << "The following extensions didn't check out" << endl << wrongExts;
+ }
+
+ // Clean up
+ delete sdw;
+
+ if(errno == 0) {
+ cout << "ALL TESTS PASSED" << endl;
+ } else {
+ cout << errno << " TESTS FAILED" << endl;
+ }
+ return errno;
+}
\ No newline at end of file
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+#ifndef MOCKCACHEUPDATER_H
+#define MOCKCACHEUPDATER_H
+
+#include <utils/String8.h>
+#include "CacheUpdater.h"
+
+using namespace android;
+
+class MockCacheUpdater : public CacheUpdater {
+public:
+
+ MockCacheUpdater()
+ : deleteCount(0), processCount(0) { };
+
+ // Make sure all the directories along this path exist
+ virtual void ensureDirectoriesExist(String8 path)
+ {
+ // Nothing to do
+ };
+
+ // Delete a file
+ virtual void deleteFile(String8 path) {
+ deleteCount++;
+ };
+
+ // Process an image from source out to dest
+ virtual void processImage(String8 source, String8 dest) {
+ processCount++;
+ };
+
+ // DATA MEMBERS
+ int deleteCount;
+ int processCount;
+private:
+};
+
+#endif // MOCKCACHEUPDATER_H
\ No newline at end of file
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+#ifndef MOCKDIRECTORYWALKER_H
+#define MOCKDIRECTORYWALKER_H
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+#include <utility>
+#include "DirectoryWalker.h"
+
+using namespace android;
+using std::pair;
+
+// String8 Directory Walker
+// This is an implementation of the Directory Walker abstraction that is built
+// for testing.
+// Instead of system calls it queries a private data structure for the directory
+// entries. It takes a path and a map of filenames and their modification times.
+// functions are inlined since they are short and simple
+
+class StringDirectoryWalker : public DirectoryWalker {
+public:
+ StringDirectoryWalker(String8& path, Vector< pair<String8,time_t> >& data)
+ : mPos(0), mBasePath(path), mData(data) {
+ //fprintf(stdout,"StringDW built to mimic %s with %d files\n",
+ // mBasePath.string());
+ };
+ // Default copy constructor, and destructor are fine
+
+ virtual bool openDir(String8 path) {
+ // If the user is trying to query the "directory" that this
+ // walker was initialized with, then return success. Else fail.
+ return path == mBasePath;
+ };
+ virtual bool openDir(const char* path) {
+ String8 p(path);
+ openDir(p);
+ return true;
+ };
+ // Advance to next entry in the Vector
+ virtual struct dirent* nextEntry() {
+ // Advance position and check to see if we're done
+ if (mPos >= mData.size())
+ return NULL;
+
+ // Place data in the entry descriptor. This class only returns files.
+ mEntry.d_type = DT_REG;
+ mEntry.d_ino = mPos;
+ // Copy chars from the string name to the entry name
+ size_t i = 0;
+ for (i; i < mData[mPos].first.size(); ++i)
+ mEntry.d_name[i] = mData[mPos].first[i];
+ mEntry.d_name[i] = '\0';
+
+ // Place data in stats
+ mStats.st_ino = mPos;
+ mStats.st_mtime = mData[mPos].second;
+
+ // Get ready to move to the next entry
+ mPos++;
+
+ return &mEntry;
+ };
+ // Get the stats for the current entry
+ virtual struct stat* entryStats() {
+ return &mStats;
+ };
+ // Nothing to do in clean up
+ virtual void closeDir() {
+ // Nothing to do
+ };
+ virtual DirectoryWalker* clone() {
+ return new StringDirectoryWalker(*this);
+ };
+private:
+ // Current position in the Vector
+ size_t mPos;
+ // Base path
+ String8 mBasePath;
+ // Data to simulate a directory full of files.
+ Vector< pair<String8,time_t> > mData;
+};
+
+#endif // MOCKDIRECTORYWALKER_H
\ No newline at end of file
--- /dev/null
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+#ifndef MOCKFILEFINDER_H
+#define MOCKFILEFINDER_H
+
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+
+using namespace android;
+
+class MockFileFinder : public FileFinder {
+public:
+ MockFileFinder (KeyedVector<String8, KeyedVector<String8,time_t> >& files)
+ : mFiles(files)
+ {
+ // Nothing left to do
+ };
+
+ /**
+ * findFiles implementation for the abstraction.
+ * PRECONDITIONS:
+ * No checking is done, so there MUST be an entry in mFiles with
+ * path matching basePath.
+ *
+ * POSTCONDITIONS:
+ * fileStore is filled with a copy of the data in mFiles corresponding
+ * to the basePath.
+ */
+
+ virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw)
+ {
+ const KeyedVector<String8,time_t>* payload(&mFiles.valueFor(basePath));
+ // Since KeyedVector doesn't implement swap
+ // (who doesn't use swap??) we loop and add one at a time.
+ for (size_t i = 0; i < payload->size(); ++i) {
+ fileStore.add(payload->keyAt(i),payload->valueAt(i));
+ }
+ return true;
+ }
+
+private:
+ // Virtual mapping between "directories" and the "files" contained
+ // in them
+ KeyedVector<String8, KeyedVector<String8,time_t> > mFiles;
+};
+
+
+#endif // MOCKFILEFINDER_H
\ No newline at end of file