diff options
Diffstat (limited to 'tools/aapt')
49 files changed, 23046 insertions, 0 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp new file mode 100644 index 0000000..3797b49 --- /dev/null +++ b/tools/aapt/AaptAssets.cpp @@ -0,0 +1,2689 @@ +// +// Copyright 2006 The Android Open Source Project +// + +#include "AaptAssets.h" +#include "ResourceFilter.h" +#include "Main.h" + +#include <utils/misc.h> +#include <utils/SortedVector.h> + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> + +static const char* kDefaultLocale = "default"; +static const char* kWildcardName = "any"; +static const char* kAssetDir = "assets"; +static const char* kResourceDir = "res"; +static const char* kValuesDir = "values"; +static const char* kMipmapDir = "mipmap"; +static const char* kInvalidChars = "/\\:"; +static const size_t kMaxAssetFileName = 100; + +static const String8 kResString(kResourceDir); + +/* + * Names of asset files must meet the following criteria: + * + * - the filename length must be less than kMaxAssetFileName bytes long + * (and can't be empty) + * - all characters must be 7-bit printable ASCII + * - none of { '/' '\\' ':' } + * + * Pass in just the filename, not the full path. + */ +static bool validateFileName(const char* fileName) +{ + const char* cp = fileName; + size_t len = 0; + + while (*cp != '\0') { + if ((*cp & 0x80) != 0) + return false; // reject high ASCII + if (*cp < 0x20 || *cp >= 0x7f) + return false; // reject control chars and 0x7f + if (strchr(kInvalidChars, *cp) != NULL) + return false; // reject path sep chars + cp++; + len++; + } + + if (len < 1 || len > kMaxAssetFileName) + return false; // reject empty or too long + + return true; +} + +// The default to use if no other ignore pattern is defined. +const char * const gDefaultIgnoreAssets = + "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"; +// The ignore pattern that can be passed via --ignore-assets in Main.cpp +const char * gUserIgnoreAssets = NULL; + +static bool isHidden(const char *root, const char *path) +{ + // Patterns syntax: + // - Delimiter is : + // - Entry can start with the flag ! to avoid printing a warning + // about the file being ignored. + // - Entry can have the flag "<dir>" to match only directories + // or <file> to match only files. Default is to match both. + // - Entry can be a simplified glob "<prefix>*" or "*<suffix>" + // where prefix/suffix must have at least 1 character (so that + // we don't match a '*' catch-all pattern.) + // - The special filenames "." and ".." are always ignored. + // - Otherwise the full string is matched. + // - match is not case-sensitive. + + if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) { + return true; + } + + const char *delim = ":"; + const char *p = gUserIgnoreAssets; + if (!p || !p[0]) { + p = getenv("ANDROID_AAPT_IGNORE"); + } + if (!p || !p[0]) { + p = gDefaultIgnoreAssets; + } + char *patterns = strdup(p); + + bool ignore = false; + bool chatty = true; + char *matchedPattern = NULL; + + String8 fullPath(root); + fullPath.appendPath(path); + FileType type = getFileType(fullPath); + + int plen = strlen(path); + + // Note: we don't have strtok_r under mingw. + for(char *token = strtok(patterns, delim); + !ignore && token != NULL; + token = strtok(NULL, delim)) { + chatty = token[0] != '!'; + if (!chatty) token++; // skip ! + if (strncasecmp(token, "<dir>" , 5) == 0) { + if (type != kFileTypeDirectory) continue; + token += 5; + } + if (strncasecmp(token, "<file>", 6) == 0) { + if (type != kFileTypeRegular) continue; + token += 6; + } + + matchedPattern = token; + int n = strlen(token); + + if (token[0] == '*') { + // Match *suffix + token++; + n--; + if (n <= plen) { + ignore = strncasecmp(token, path + plen - n, n) == 0; + } + } else if (n > 1 && token[n - 1] == '*') { + // Match prefix* + ignore = strncasecmp(token, path, n - 1) == 0; + } else { + ignore = strcasecmp(token, path) == 0; + } + } + + if (ignore && chatty) { + fprintf(stderr, " (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n", + type == kFileTypeDirectory ? "dir" : "file", + path, + matchedPattern ? matchedPattern : ""); + } + + free(patterns); + return ignore; +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +status_t +AaptGroupEntry::parseNamePart(const String8& part, int* axis, uint32_t* value) +{ + ResTable_config config; + + // IMSI - MCC + if (getMccName(part.string(), &config)) { + *axis = AXIS_MCC; + *value = config.mcc; + return 0; + } + + // IMSI - MNC + if (getMncName(part.string(), &config)) { + *axis = AXIS_MNC; + *value = config.mnc; + return 0; + } + + // locale - language + if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) { + *axis = AXIS_LANGUAGE; + *value = part[1] << 8 | part[0]; + return 0; + } + + // locale - language_REGION + if (part.length() == 5 && isalpha(part[0]) && isalpha(part[1]) + && part[2] == '_' && isalpha(part[3]) && isalpha(part[4])) { + *axis = AXIS_LANGUAGE; + *value = (part[4] << 24) | (part[3] << 16) | (part[1] << 8) | (part[0]); + return 0; + } + + // layout direction + if (getLayoutDirectionName(part.string(), &config)) { + *axis = AXIS_LAYOUTDIR; + *value = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR); + return 0; + } + + // smallest screen dp width + if (getSmallestScreenWidthDpName(part.string(), &config)) { + *axis = AXIS_SMALLESTSCREENWIDTHDP; + *value = config.smallestScreenWidthDp; + return 0; + } + + // screen dp width + if (getScreenWidthDpName(part.string(), &config)) { + *axis = AXIS_SCREENWIDTHDP; + *value = config.screenWidthDp; + return 0; + } + + // screen dp height + if (getScreenHeightDpName(part.string(), &config)) { + *axis = AXIS_SCREENHEIGHTDP; + *value = config.screenHeightDp; + return 0; + } + + // screen layout size + if (getScreenLayoutSizeName(part.string(), &config)) { + *axis = AXIS_SCREENLAYOUTSIZE; + *value = (config.screenLayout&ResTable_config::MASK_SCREENSIZE); + return 0; + } + + // screen layout long + if (getScreenLayoutLongName(part.string(), &config)) { + *axis = AXIS_SCREENLAYOUTLONG; + *value = (config.screenLayout&ResTable_config::MASK_SCREENLONG); + return 0; + } + + // orientation + if (getOrientationName(part.string(), &config)) { + *axis = AXIS_ORIENTATION; + *value = config.orientation; + return 0; + } + + // ui mode type + if (getUiModeTypeName(part.string(), &config)) { + *axis = AXIS_UIMODETYPE; + *value = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); + return 0; + } + + // ui mode night + if (getUiModeNightName(part.string(), &config)) { + *axis = AXIS_UIMODENIGHT; + *value = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); + return 0; + } + + // density + if (getDensityName(part.string(), &config)) { + *axis = AXIS_DENSITY; + *value = config.density; + return 0; + } + + // touchscreen + if (getTouchscreenName(part.string(), &config)) { + *axis = AXIS_TOUCHSCREEN; + *value = config.touchscreen; + return 0; + } + + // keyboard hidden + if (getKeysHiddenName(part.string(), &config)) { + *axis = AXIS_KEYSHIDDEN; + *value = config.inputFlags; + return 0; + } + + // keyboard + if (getKeyboardName(part.string(), &config)) { + *axis = AXIS_KEYBOARD; + *value = config.keyboard; + return 0; + } + + // navigation hidden + if (getNavHiddenName(part.string(), &config)) { + *axis = AXIS_NAVHIDDEN; + *value = config.inputFlags; + return 0; + } + + // navigation + if (getNavigationName(part.string(), &config)) { + *axis = AXIS_NAVIGATION; + *value = config.navigation; + return 0; + } + + // screen size + if (getScreenSizeName(part.string(), &config)) { + *axis = AXIS_SCREENSIZE; + *value = config.screenSize; + return 0; + } + + // version + if (getVersionName(part.string(), &config)) { + *axis = AXIS_VERSION; + *value = config.version; + return 0; + } + + return 1; +} + +uint32_t +AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis) +{ + switch (axis) { + case AXIS_MCC: + return config.mcc; + case AXIS_MNC: + return config.mnc; + case AXIS_LANGUAGE: + return (((uint32_t)config.country[1]) << 24) | (((uint32_t)config.country[0]) << 16) + | (((uint32_t)config.language[1]) << 8) | (config.language[0]); + case AXIS_LAYOUTDIR: + return config.screenLayout&ResTable_config::MASK_LAYOUTDIR; + case AXIS_SCREENLAYOUTSIZE: + return config.screenLayout&ResTable_config::MASK_SCREENSIZE; + case AXIS_ORIENTATION: + return config.orientation; + case AXIS_UIMODETYPE: + return (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); + case AXIS_UIMODENIGHT: + return (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); + case AXIS_DENSITY: + return config.density; + case AXIS_TOUCHSCREEN: + return config.touchscreen; + case AXIS_KEYSHIDDEN: + return config.inputFlags; + case AXIS_KEYBOARD: + return config.keyboard; + case AXIS_NAVIGATION: + return config.navigation; + case AXIS_SCREENSIZE: + return config.screenSize; + case AXIS_SMALLESTSCREENWIDTHDP: + return config.smallestScreenWidthDp; + case AXIS_SCREENWIDTHDP: + return config.screenWidthDp; + case AXIS_SCREENHEIGHTDP: + return config.screenHeightDp; + case AXIS_VERSION: + return config.version; + } + return 0; +} + +bool +AaptGroupEntry::configSameExcept(const ResTable_config& config, + const ResTable_config& otherConfig, int axis) +{ + for (int i=AXIS_START; i<=AXIS_END; i++) { + if (i == axis) { + continue; + } + if (getConfigValueForAxis(config, i) != getConfigValueForAxis(otherConfig, i)) { + return false; + } + } + return true; +} + +bool +AaptGroupEntry::initFromDirName(const char* dir, String8* resType) +{ + mParamsChanged = true; + + Vector<String8> parts; + + String8 mcc, mnc, loc, layoutsize, layoutlong, orient, den; + String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers; + String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp; + + const char *p = dir; + const char *q; + while (NULL != (q = strchr(p, '-'))) { + String8 val(p, q-p); + val.toLower(); + parts.add(val); + //printf("part: %s\n", parts[parts.size()-1].string()); + p = q+1; + } + String8 val(p); + val.toLower(); + parts.add(val); + //printf("part: %s\n", parts[parts.size()-1].string()); + + const int N = parts.size(); + int index = 0; + String8 part = parts[index]; + + // resource type + if (!isValidResourceType(part)) { + return false; + } + *resType = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + + // imsi - mcc + if (getMccName(part.string())) { + mcc = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not mcc: %s\n", part.string()); + } + + // imsi - mnc + if (getMncName(part.string())) { + mnc = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not mcc: %s\n", part.string()); + } + + // locale - language + if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) { + loc = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not language: %s\n", part.string()); + } + + // locale - region + if (loc.length() > 0 + && part.length() == 3 && part[0] == 'r' && part[0] && part[1]) { + loc += "-"; + part.toUpper(); + loc += part.string() + 1; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not region: %s\n", part.string()); + } + + if (getLayoutDirectionName(part.string())) { + layoutDir = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not layout direction: %s\n", part.string()); + } + + if (getSmallestScreenWidthDpName(part.string())) { + smallestwidthdp = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not smallest screen width dp: %s\n", part.string()); + } + + if (getScreenWidthDpName(part.string())) { + widthdp = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not screen width dp: %s\n", part.string()); + } + + if (getScreenHeightDpName(part.string())) { + heightdp = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not screen height dp: %s\n", part.string()); + } + + if (getScreenLayoutSizeName(part.string())) { + layoutsize = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not screen layout size: %s\n", part.string()); + } + + if (getScreenLayoutLongName(part.string())) { + layoutlong = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not screen layout long: %s\n", part.string()); + } + + // orientation + if (getOrientationName(part.string())) { + orient = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not orientation: %s\n", part.string()); + } + + // ui mode type + if (getUiModeTypeName(part.string())) { + uiModeType = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not ui mode type: %s\n", part.string()); + } + + // ui mode night + if (getUiModeNightName(part.string())) { + uiModeNight = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not ui mode night: %s\n", part.string()); + } + + // density + if (getDensityName(part.string())) { + den = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not density: %s\n", part.string()); + } + + // touchscreen + if (getTouchscreenName(part.string())) { + touch = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not touchscreen: %s\n", part.string()); + } + + // keyboard hidden + if (getKeysHiddenName(part.string())) { + keysHidden = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not keysHidden: %s\n", part.string()); + } + + // keyboard + if (getKeyboardName(part.string())) { + key = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not keyboard: %s\n", part.string()); + } + + // navigation hidden + if (getNavHiddenName(part.string())) { + navHidden = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not navHidden: %s\n", part.string()); + } + + if (getNavigationName(part.string())) { + nav = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not navigation: %s\n", part.string()); + } + + if (getScreenSizeName(part.string())) { + size = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not screen size: %s\n", part.string()); + } + + if (getVersionName(part.string())) { + vers = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not version: %s\n", part.string()); + } + + // if there are extra parts, it doesn't match + return false; + +success: + this->mcc = mcc; + this->mnc = mnc; + this->locale = loc; + this->screenLayoutSize = layoutsize; + this->screenLayoutLong = layoutlong; + this->smallestScreenWidthDp = smallestwidthdp; + this->screenWidthDp = widthdp; + this->screenHeightDp = heightdp; + this->orientation = orient; + this->uiModeType = uiModeType; + this->uiModeNight = uiModeNight; + this->density = den; + this->touchscreen = touch; + this->keysHidden = keysHidden; + this->keyboard = key; + this->navHidden = navHidden; + this->navigation = nav; + this->screenSize = size; + this->layoutDirection = layoutDir; + this->version = vers; + + // what is this anyway? + this->vendor = ""; + + return true; +} + +String8 +AaptGroupEntry::toString() const +{ + String8 s = this->mcc; + s += ","; + s += this->mnc; + s += ","; + s += this->locale; + s += ","; + s += layoutDirection; + s += ","; + s += smallestScreenWidthDp; + s += ","; + s += screenWidthDp; + s += ","; + s += screenHeightDp; + s += ","; + s += screenLayoutSize; + s += ","; + s += screenLayoutLong; + s += ","; + s += this->orientation; + s += ","; + s += uiModeType; + s += ","; + s += uiModeNight; + s += ","; + s += density; + s += ","; + s += touchscreen; + s += ","; + s += keysHidden; + s += ","; + s += keyboard; + s += ","; + s += navHidden; + s += ","; + s += navigation; + s += ","; + s += screenSize; + s += ","; + s += version; + return s; +} + +String8 +AaptGroupEntry::toDirName(const String8& resType) const +{ + String8 s = resType; + if (this->mcc != "") { + if (s.length() > 0) { + s += "-"; + } + s += mcc; + } + if (this->mnc != "") { + if (s.length() > 0) { + s += "-"; + } + s += mnc; + } + if (this->locale != "") { + if (s.length() > 0) { + s += "-"; + } + s += locale; + } + if (this->layoutDirection != "") { + if (s.length() > 0) { + s += "-"; + } + s += layoutDirection; + } + if (this->smallestScreenWidthDp != "") { + if (s.length() > 0) { + s += "-"; + } + s += smallestScreenWidthDp; + } + if (this->screenWidthDp != "") { + if (s.length() > 0) { + s += "-"; + } + s += screenWidthDp; + } + if (this->screenHeightDp != "") { + if (s.length() > 0) { + s += "-"; + } + s += screenHeightDp; + } + if (this->screenLayoutSize != "") { + if (s.length() > 0) { + s += "-"; + } + s += screenLayoutSize; + } + if (this->screenLayoutLong != "") { + if (s.length() > 0) { + s += "-"; + } + s += screenLayoutLong; + } + if (this->orientation != "") { + if (s.length() > 0) { + s += "-"; + } + s += orientation; + } + if (this->uiModeType != "") { + if (s.length() > 0) { + s += "-"; + } + s += uiModeType; + } + if (this->uiModeNight != "") { + if (s.length() > 0) { + s += "-"; + } + s += uiModeNight; + } + if (this->density != "") { + if (s.length() > 0) { + s += "-"; + } + s += density; + } + if (this->touchscreen != "") { + if (s.length() > 0) { + s += "-"; + } + s += touchscreen; + } + if (this->keysHidden != "") { + if (s.length() > 0) { + s += "-"; + } + s += keysHidden; + } + if (this->keyboard != "") { + if (s.length() > 0) { + s += "-"; + } + s += keyboard; + } + if (this->navHidden != "") { + if (s.length() > 0) { + s += "-"; + } + s += navHidden; + } + if (this->navigation != "") { + if (s.length() > 0) { + s += "-"; + } + s += navigation; + } + if (this->screenSize != "") { + if (s.length() > 0) { + s += "-"; + } + s += screenSize; + } + if (this->version != "") { + if (s.length() > 0) { + s += "-"; + } + s += version; + } + + return s; +} + +bool AaptGroupEntry::getMccName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { + c++; + } + if (*c != 0) return false; + if (c-val != 3) return false; + + int d = atoi(val); + if (d != 0) { + if (out) out->mcc = d; + return true; + } + + return false; +} + +bool AaptGroupEntry::getMncName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'n') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { + c++; + } + if (*c != 0) return false; + if (c-val == 0 || c-val > 3) return false; + + if (out) { + out->mnc = atoi(val); + if (out->mnc == 0) { + out->mnc = ACONFIGURATION_MNC_ZERO; + } + } + + return true; +} + +/* + * Does this directory name fit the pattern of a locale dir ("en-rUS" or + * "default")? + * + * TODO: Should insist that the first two letters are lower case, and the + * second two are upper. + */ +bool AaptGroupEntry::getLocaleName(const char* fileName, + ResTable_config* out) +{ + if (strcmp(fileName, kWildcardName) == 0 + || strcmp(fileName, kDefaultLocale) == 0) { + if (out) { + out->language[0] = 0; + out->language[1] = 0; + out->country[0] = 0; + out->country[1] = 0; + } + return true; + } + + if (strlen(fileName) == 2 && isalpha(fileName[0]) && isalpha(fileName[1])) { + if (out) { + out->language[0] = fileName[0]; + out->language[1] = fileName[1]; + out->country[0] = 0; + out->country[1] = 0; + } + return true; + } + + if (strlen(fileName) == 5 && + isalpha(fileName[0]) && + isalpha(fileName[1]) && + fileName[2] == '-' && + isalpha(fileName[3]) && + isalpha(fileName[4])) { + if (out) { + out->language[0] = fileName[0]; + out->language[1] = fileName[1]; + out->country[0] = fileName[3]; + out->country[1] = fileName[4]; + } + return true; + } + + return false; +} + +bool AaptGroupEntry::getLayoutDirectionName(const char* name, ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_ANY; + return true; + } else if (strcmp(name, "ldltr") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_LTR; + return true; + } else if (strcmp(name, "ldrtl") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_RTL; + return true; + } + + return false; +} + +bool AaptGroupEntry::getScreenLayoutSizeName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_ANY; + return true; + } else if (strcmp(name, "small") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_SMALL; + return true; + } else if (strcmp(name, "normal") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_NORMAL; + return true; + } else if (strcmp(name, "large") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_LARGE; + return true; + } else if (strcmp(name, "xlarge") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_XLARGE; + return true; + } + + return false; +} + +bool AaptGroupEntry::getScreenLayoutLongName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_ANY; + return true; + } else if (strcmp(name, "long") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_YES; + return true; + } else if (strcmp(name, "notlong") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_NO; + return true; + } + + return false; +} + +bool AaptGroupEntry::getOrientationName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->orientation = out->ORIENTATION_ANY; + return true; + } else if (strcmp(name, "port") == 0) { + if (out) out->orientation = out->ORIENTATION_PORT; + return true; + } else if (strcmp(name, "land") == 0) { + if (out) out->orientation = out->ORIENTATION_LAND; + return true; + } else if (strcmp(name, "square") == 0) { + if (out) out->orientation = out->ORIENTATION_SQUARE; + return true; + } + + return false; +} + +bool AaptGroupEntry::getUiModeTypeName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_ANY; + return true; + } else if (strcmp(name, "desk") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_DESK; + return true; + } else if (strcmp(name, "car") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_CAR; + return true; + } else if (strcmp(name, "television") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_TELEVISION; + return true; + } else if (strcmp(name, "appliance") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_APPLIANCE; + return true; + } + + return false; +} + +bool AaptGroupEntry::getUiModeNightName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_ANY; + return true; + } else if (strcmp(name, "night") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_YES; + return true; + } else if (strcmp(name, "notnight") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_NO; + return true; + } + + return false; +} + +bool AaptGroupEntry::getDensityName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->density = ResTable_config::DENSITY_DEFAULT; + return true; + } + + if (strcmp(name, "nodpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_NONE; + return true; + } + + if (strcmp(name, "ldpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_LOW; + return true; + } + + if (strcmp(name, "mdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_MEDIUM; + return true; + } + + if (strcmp(name, "tvdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_TV; + return true; + } + + if (strcmp(name, "hdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_HIGH; + return true; + } + + if (strcmp(name, "xhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XHIGH; + return true; + } + + if (strcmp(name, "xxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXHIGH; + return true; + } + + if (strcmp(name, "xxxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXXHIGH; + return true; + } + + char* c = (char*)name; + while (*c >= '0' && *c <= '9') { + c++; + } + + // check that we have 'dpi' after the last digit. + if (toupper(c[0]) != 'D' || + toupper(c[1]) != 'P' || + toupper(c[2]) != 'I' || + c[3] != 0) { + return false; + } + + // temporarily replace the first letter with \0 to + // use atoi. + char tmp = c[0]; + c[0] = '\0'; + + int d = atoi(name); + c[0] = tmp; + + if (d != 0) { + if (out) out->density = d; + return true; + } + + return false; +} + +bool AaptGroupEntry::getTouchscreenName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_ANY; + return true; + } else if (strcmp(name, "notouch") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; + return true; + } else if (strcmp(name, "stylus") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; + return true; + } else if (strcmp(name, "finger") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; + return true; + } + + return false; +} + +bool AaptGroupEntry::getKeysHiddenName(const char* name, + ResTable_config* out) +{ + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_ANY; + } else if (strcmp(name, "keysexposed") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_NO; + } else if (strcmp(name, "keyshidden") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_YES; + } else if (strcmp(name, "keyssoft") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_SOFT; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + +bool AaptGroupEntry::getKeyboardName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->keyboard = out->KEYBOARD_ANY; + return true; + } else if (strcmp(name, "nokeys") == 0) { + if (out) out->keyboard = out->KEYBOARD_NOKEYS; + return true; + } else if (strcmp(name, "qwerty") == 0) { + if (out) out->keyboard = out->KEYBOARD_QWERTY; + return true; + } else if (strcmp(name, "12key") == 0) { + if (out) out->keyboard = out->KEYBOARD_12KEY; + return true; + } + + return false; +} + +bool AaptGroupEntry::getNavHiddenName(const char* name, + ResTable_config* out) +{ + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_ANY; + } else if (strcmp(name, "navexposed") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_NO; + } else if (strcmp(name, "navhidden") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_YES; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + +bool AaptGroupEntry::getNavigationName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->navigation = out->NAVIGATION_ANY; + return true; + } else if (strcmp(name, "nonav") == 0) { + if (out) out->navigation = out->NAVIGATION_NONAV; + return true; + } else if (strcmp(name, "dpad") == 0) { + if (out) out->navigation = out->NAVIGATION_DPAD; + return true; + } else if (strcmp(name, "trackball") == 0) { + if (out) out->navigation = out->NAVIGATION_TRACKBALL; + return true; + } else if (strcmp(name, "wheel") == 0) { + if (out) out->navigation = out->NAVIGATION_WHEEL; + return true; + } + + return false; +} + +bool AaptGroupEntry::getScreenSizeName(const char* name, ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenWidth = out->SCREENWIDTH_ANY; + out->screenHeight = out->SCREENHEIGHT_ANY; + } + return true; + } + + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || *x != 'x') return false; + String8 xName(name, x-name); + x++; + + const char* y = x; + while (*y >= '0' && *y <= '9') y++; + if (y == name || *y != 0) return false; + String8 yName(x, y-x); + + uint16_t w = (uint16_t)atoi(xName.string()); + uint16_t h = (uint16_t)atoi(yName.string()); + if (w < h) { + return false; + } + + if (out) { + out->screenWidth = w; + out->screenHeight = h; + } + + return true; +} + +bool AaptGroupEntry::getSmallestScreenWidthDpName(const char* name, ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 's') return false; + name++; + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->smallestScreenWidthDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool AaptGroupEntry::getScreenWidthDpName(const char* name, ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenWidthDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->screenWidthDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool AaptGroupEntry::getScreenHeightDpName(const char* name, ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenHeightDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 'h') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->screenHeightDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->sdkVersion = out->SDKVERSION_ANY; + out->minorVersion = out->MINORVERSION_ANY; + } + return true; + } + + if (*name != 'v') { + return false; + } + + name++; + const char* s = name; + while (*s >= '0' && *s <= '9') s++; + if (s == name || *s != 0) return false; + String8 sdkName(name, s-name); + + if (out) { + out->sdkVersion = (uint16_t)atoi(sdkName.string()); + out->minorVersion = 0; + } + + return true; +} + +int AaptGroupEntry::compare(const AaptGroupEntry& o) const +{ + int v = mcc.compare(o.mcc); + if (v == 0) v = mnc.compare(o.mnc); + if (v == 0) v = locale.compare(o.locale); + if (v == 0) v = layoutDirection.compare(o.layoutDirection); + if (v == 0) v = vendor.compare(o.vendor); + if (v == 0) v = smallestScreenWidthDp.compare(o.smallestScreenWidthDp); + if (v == 0) v = screenWidthDp.compare(o.screenWidthDp); + if (v == 0) v = screenHeightDp.compare(o.screenHeightDp); + if (v == 0) v = screenLayoutSize.compare(o.screenLayoutSize); + if (v == 0) v = screenLayoutLong.compare(o.screenLayoutLong); + if (v == 0) v = orientation.compare(o.orientation); + if (v == 0) v = uiModeType.compare(o.uiModeType); + if (v == 0) v = uiModeNight.compare(o.uiModeNight); + if (v == 0) v = density.compare(o.density); + if (v == 0) v = touchscreen.compare(o.touchscreen); + if (v == 0) v = keysHidden.compare(o.keysHidden); + if (v == 0) v = keyboard.compare(o.keyboard); + if (v == 0) v = navHidden.compare(o.navHidden); + if (v == 0) v = navigation.compare(o.navigation); + if (v == 0) v = screenSize.compare(o.screenSize); + if (v == 0) v = version.compare(o.version); + return v; +} + +const ResTable_config& AaptGroupEntry::toParams() const +{ + if (!mParamsChanged) { + return mParams; + } + + mParamsChanged = false; + ResTable_config& params(mParams); + memset(¶ms, 0, sizeof(params)); + getMccName(mcc.string(), ¶ms); + getMncName(mnc.string(), ¶ms); + getLocaleName(locale.string(), ¶ms); + getLayoutDirectionName(layoutDirection.string(), ¶ms); + getSmallestScreenWidthDpName(smallestScreenWidthDp.string(), ¶ms); + getScreenWidthDpName(screenWidthDp.string(), ¶ms); + getScreenHeightDpName(screenHeightDp.string(), ¶ms); + getScreenLayoutSizeName(screenLayoutSize.string(), ¶ms); + getScreenLayoutLongName(screenLayoutLong.string(), ¶ms); + getOrientationName(orientation.string(), ¶ms); + getUiModeTypeName(uiModeType.string(), ¶ms); + getUiModeNightName(uiModeNight.string(), ¶ms); + getDensityName(density.string(), ¶ms); + getTouchscreenName(touchscreen.string(), ¶ms); + getKeysHiddenName(keysHidden.string(), ¶ms); + getKeyboardName(keyboard.string(), ¶ms); + getNavHiddenName(navHidden.string(), ¶ms); + getNavigationName(navigation.string(), ¶ms); + getScreenSizeName(screenSize.string(), ¶ms); + getVersionName(version.string(), ¶ms); + + // Fix up version number based on specified parameters. + int minSdk = 0; + if (params.smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY + || params.screenWidthDp != ResTable_config::SCREENWIDTH_ANY + || params.screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { + minSdk = SDK_HONEYCOMB_MR2; + } else if ((params.uiMode&ResTable_config::MASK_UI_MODE_TYPE) + != ResTable_config::UI_MODE_TYPE_ANY + || (params.uiMode&ResTable_config::MASK_UI_MODE_NIGHT) + != ResTable_config::UI_MODE_NIGHT_ANY) { + minSdk = SDK_FROYO; + } else if ((params.screenLayout&ResTable_config::MASK_SCREENSIZE) + != ResTable_config::SCREENSIZE_ANY + || (params.screenLayout&ResTable_config::MASK_SCREENLONG) + != ResTable_config::SCREENLONG_ANY + || params.density != ResTable_config::DENSITY_DEFAULT) { + minSdk = SDK_DONUT; + } + + if (minSdk > params.sdkVersion) { + params.sdkVersion = minSdk; + } + + return params; +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +void* AaptFile::editData(size_t size) +{ + if (size <= mBufferSize) { + mDataSize = size; + return mData; + } + size_t allocSize = (size*3)/2; + void* buf = realloc(mData, allocSize); + if (buf == NULL) { + return NULL; + } + mData = buf; + mDataSize = size; + mBufferSize = allocSize; + return buf; +} + +void* AaptFile::editData(size_t* outSize) +{ + if (outSize) { + *outSize = mDataSize; + } + return mData; +} + +void* AaptFile::padData(size_t wordSize) +{ + const size_t extra = mDataSize%wordSize; + if (extra == 0) { + return mData; + } + + size_t initial = mDataSize; + void* data = editData(initial+(wordSize-extra)); + if (data != NULL) { + memset(((uint8_t*)data) + initial, 0, wordSize-extra); + } + return data; +} + +status_t AaptFile::writeData(const void* data, size_t size) +{ + size_t end = mDataSize; + size_t total = size + end; + void* buf = editData(total); + if (buf == NULL) { + return UNKNOWN_ERROR; + } + memcpy(((char*)buf)+end, data, size); + return NO_ERROR; +} + +void AaptFile::clearData() +{ + if (mData != NULL) free(mData); + mData = NULL; + mDataSize = 0; + mBufferSize = 0; +} + +String8 AaptFile::getPrintableSource() const +{ + if (hasData()) { + String8 name(mGroupEntry.toDirName(String8())); + name.appendPath(mPath); + name.append(" #generated"); + return name; + } + return mSourceFile; +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +status_t AaptGroup::addFile(const sp<AaptFile>& file) +{ + if (mFiles.indexOfKey(file->getGroupEntry()) < 0) { + file->mPath = mPath; + mFiles.add(file->getGroupEntry(), file); + return NO_ERROR; + } + +#if 0 + printf("Error adding file %s: group %s already exists in leaf=%s path=%s\n", + file->getSourceFile().string(), + file->getGroupEntry().toDirName(String8()).string(), + mLeaf.string(), mPath.string()); +#endif + + SourcePos(file->getSourceFile(), -1).error("Duplicate file.\n%s: Original is here.", + getPrintableSource().string()); + return UNKNOWN_ERROR; +} + +void AaptGroup::removeFile(size_t index) +{ + mFiles.removeItemsAt(index); +} + +void AaptGroup::print(const String8& prefix) const +{ + printf("%s%s\n", prefix.string(), getPath().string()); + const size_t N=mFiles.size(); + size_t i; + for (i=0; i<N; i++) { + sp<AaptFile> file = mFiles.valueAt(i); + const AaptGroupEntry& e = file->getGroupEntry(); + if (file->hasData()) { + printf("%s Gen: (%s) %d bytes\n", prefix.string(), e.toDirName(String8()).string(), + (int)file->getSize()); + } else { + printf("%s Src: (%s) %s\n", prefix.string(), e.toDirName(String8()).string(), + file->getPrintableSource().string()); + } + //printf("%s File Group Entry: %s\n", prefix.string(), + // file->getGroupEntry().toDirName(String8()).string()); + } +} + +String8 AaptGroup::getPrintableSource() const +{ + if (mFiles.size() > 0) { + // Arbitrarily pull the first source file out of the list. + return mFiles.valueAt(0)->getPrintableSource(); + } + + // Should never hit this case, but to be safe... + return getPath(); + +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file) +{ + if (mFiles.indexOfKey(name) >= 0) { + return ALREADY_EXISTS; + } + mFiles.add(name, file); + return NO_ERROR; +} + +status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir) +{ + if (mDirs.indexOfKey(name) >= 0) { + return ALREADY_EXISTS; + } + mDirs.add(name, dir); + return NO_ERROR; +} + +sp<AaptDir> AaptDir::makeDir(const String8& path) +{ + String8 name; + String8 remain = path; + + sp<AaptDir> subdir = this; + while (name = remain.walkPath(&remain), remain != "") { + subdir = subdir->makeDir(name); + } + + ssize_t i = subdir->mDirs.indexOfKey(name); + if (i >= 0) { + return subdir->mDirs.valueAt(i); + } + sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name)); + subdir->mDirs.add(name, dir); + return dir; +} + +void AaptDir::removeFile(const String8& name) +{ + mFiles.removeItem(name); +} + +void AaptDir::removeDir(const String8& name) +{ + mDirs.removeItem(name); +} + +status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file) +{ + sp<AaptGroup> group; + if (mFiles.indexOfKey(leafName) >= 0) { + group = mFiles.valueFor(leafName); + } else { + group = new AaptGroup(leafName, mPath.appendPathCopy(leafName)); + mFiles.add(leafName, group); + } + + return group->addFile(file); +} + +ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir, + const AaptGroupEntry& kind, const String8& resType, + sp<FilePathStore>& fullResPaths) +{ + Vector<String8> fileNames; + { + DIR* dir = NULL; + + dir = opendir(srcDir.string()); + if (dir == NULL) { + fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno)); + return UNKNOWN_ERROR; + } + + /* + * Slurp the filenames out of the directory. + */ + while (1) { + struct dirent* entry; + + entry = readdir(dir); + if (entry == NULL) + break; + + if (isHidden(srcDir.string(), entry->d_name)) + continue; + + String8 name(entry->d_name); + fileNames.add(name); + // Add fully qualified path for dependency purposes + // if we're collecting them + if (fullResPaths != NULL) { + fullResPaths->add(srcDir.appendPathCopy(name)); + } + } + closedir(dir); + } + + ssize_t count = 0; + + /* + * Stash away the files and recursively descend into subdirectories. + */ + const size_t N = fileNames.size(); + size_t i; + for (i = 0; i < N; i++) { + String8 pathName(srcDir); + FileType type; + + pathName.appendPath(fileNames[i].string()); + type = getFileType(pathName.string()); + if (type == kFileTypeDirectory) { + sp<AaptDir> subdir; + bool notAdded = false; + if (mDirs.indexOfKey(fileNames[i]) >= 0) { + subdir = mDirs.valueFor(fileNames[i]); + } else { + subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i])); + notAdded = true; + } + ssize_t res = subdir->slurpFullTree(bundle, pathName, kind, + resType, fullResPaths); + if (res < NO_ERROR) { + return res; + } + if (res > 0 && notAdded) { + mDirs.add(fileNames[i], subdir); + } + count += res; + } else if (type == kFileTypeRegular) { + sp<AaptFile> file = new AaptFile(pathName, kind, resType); + status_t err = addLeafFile(fileNames[i], file); + if (err != NO_ERROR) { + return err; + } + + count++; + + } else { + if (bundle->getVerbose()) + printf(" (ignoring non-file/dir '%s')\n", pathName.string()); + } + } + + return count; +} + +status_t AaptDir::validate() const +{ + const size_t NF = mFiles.size(); + const size_t ND = mDirs.size(); + size_t i; + for (i = 0; i < NF; i++) { + if (!validateFileName(mFiles.valueAt(i)->getLeaf().string())) { + SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error( + "Invalid filename. Unable to add."); + return UNKNOWN_ERROR; + } + + size_t j; + for (j = i+1; j < NF; j++) { + if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(), + mFiles.valueAt(j)->getLeaf().string()) == 0) { + SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error( + "File is case-insensitive equivalent to: %s", + mFiles.valueAt(j)->getPrintableSource().string()); + return UNKNOWN_ERROR; + } + + // TODO: if ".gz", check for non-.gz; if non-, check for ".gz" + // (this is mostly caught by the "marked" stuff, below) + } + + for (j = 0; j < ND; j++) { + if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(), + mDirs.valueAt(j)->getLeaf().string()) == 0) { + SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error( + "File conflicts with dir from: %s", + mDirs.valueAt(j)->getPrintableSource().string()); + return UNKNOWN_ERROR; + } + } + } + + for (i = 0; i < ND; i++) { + if (!validateFileName(mDirs.valueAt(i)->getLeaf().string())) { + SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error( + "Invalid directory name, unable to add."); + return UNKNOWN_ERROR; + } + + size_t j; + for (j = i+1; j < ND; j++) { + if (strcasecmp(mDirs.valueAt(i)->getLeaf().string(), + mDirs.valueAt(j)->getLeaf().string()) == 0) { + SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error( + "Directory is case-insensitive equivalent to: %s", + mDirs.valueAt(j)->getPrintableSource().string()); + return UNKNOWN_ERROR; + } + } + + status_t err = mDirs.valueAt(i)->validate(); + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +void AaptDir::print(const String8& prefix) const +{ + const size_t ND=getDirs().size(); + size_t i; + for (i=0; i<ND; i++) { + getDirs().valueAt(i)->print(prefix); + } + + const size_t NF=getFiles().size(); + for (i=0; i<NF; i++) { + getFiles().valueAt(i)->print(prefix); + } +} + +String8 AaptDir::getPrintableSource() const +{ + if (mFiles.size() > 0) { + // Arbitrarily pull the first file out of the list as the source dir. + return mFiles.valueAt(0)->getPrintableSource().getPathDir(); + } + if (mDirs.size() > 0) { + // Or arbitrarily pull the first dir out of the list as the source dir. + return mDirs.valueAt(0)->getPrintableSource().getPathDir(); + } + + // Should never hit this case, but to be safe... + return mPath; + +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols) +{ + status_t err = NO_ERROR; + size_t N = javaSymbols->mSymbols.size(); + for (size_t i=0; i<N; i++) { + const String8& name = javaSymbols->mSymbols.keyAt(i); + const AaptSymbolEntry& entry = javaSymbols->mSymbols.valueAt(i); + ssize_t pos = mSymbols.indexOfKey(name); + if (pos < 0) { + entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string()); + err = UNKNOWN_ERROR; + continue; + } + //printf("**** setting symbol #%d/%d %s to isJavaSymbol=%d\n", + // i, N, name.string(), entry.isJavaSymbol ? 1 : 0); + mSymbols.editValueAt(pos).isJavaSymbol = entry.isJavaSymbol; + } + + N = javaSymbols->mNestedSymbols.size(); + for (size_t i=0; i<N; i++) { + const String8& name = javaSymbols->mNestedSymbols.keyAt(i); + const sp<AaptSymbols>& symbols = javaSymbols->mNestedSymbols.valueAt(i); + ssize_t pos = mNestedSymbols.indexOfKey(name); + if (pos < 0) { + SourcePos pos; + pos.error("Java symbol dir %s not defined\n", name.string()); + err = UNKNOWN_ERROR; + continue; + } + //printf("**** applying java symbols in dir %s\n", name.string()); + status_t myerr = mNestedSymbols.valueAt(pos)->applyJavaSymbols(symbols); + if (myerr != NO_ERROR) { + err = myerr; + } + } + + return err; +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +AaptAssets::AaptAssets() + : AaptDir(String8(), String8()), + mChanged(false), mHaveIncludedAssets(false), mRes(NULL) +{ +} + +const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const { + if (mChanged) { + } + return mGroupEntries; +} + +status_t AaptAssets::addFile(const String8& name, const sp<AaptGroup>& file) +{ + mChanged = true; + return AaptDir::addFile(name, file); +} + +sp<AaptFile> AaptAssets::addFile( + const String8& filePath, const AaptGroupEntry& entry, + const String8& srcDir, sp<AaptGroup>* outGroup, + const String8& resType) +{ + sp<AaptDir> dir = this; + sp<AaptGroup> group; + sp<AaptFile> file; + String8 root, remain(filePath), partialPath; + while (remain.length() > 0) { + root = remain.walkPath(&remain); + partialPath.appendPath(root); + + const String8 rootStr(root); + + if (remain.length() == 0) { + ssize_t i = dir->getFiles().indexOfKey(rootStr); + if (i >= 0) { + group = dir->getFiles().valueAt(i); + } else { + group = new AaptGroup(rootStr, filePath); + status_t res = dir->addFile(rootStr, group); + if (res != NO_ERROR) { + return NULL; + } + } + file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType); + status_t res = group->addFile(file); + if (res != NO_ERROR) { + return NULL; + } + break; + + } else { + ssize_t i = dir->getDirs().indexOfKey(rootStr); + if (i >= 0) { + dir = dir->getDirs().valueAt(i); + } else { + sp<AaptDir> subdir = new AaptDir(rootStr, partialPath); + status_t res = dir->addDir(rootStr, subdir); + if (res != NO_ERROR) { + return NULL; + } + dir = subdir; + } + } + } + + mGroupEntries.add(entry); + if (outGroup) *outGroup = group; + return file; +} + +void AaptAssets::addResource(const String8& leafName, const String8& path, + const sp<AaptFile>& file, const String8& resType) +{ + sp<AaptDir> res = AaptDir::makeDir(kResString); + String8 dirname = file->getGroupEntry().toDirName(resType); + sp<AaptDir> subdir = res->makeDir(dirname); + sp<AaptGroup> grr = new AaptGroup(leafName, path); + grr->addFile(file); + + subdir->addFile(leafName, grr); +} + + +ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) +{ + int count; + int totalCount = 0; + FileType type; + const Vector<const char *>& resDirs = bundle->getResourceSourceDirs(); + const size_t dirCount =resDirs.size(); + sp<AaptAssets> current = this; + + const int N = bundle->getFileSpecCount(); + + /* + * If a package manifest was specified, include that first. + */ + if (bundle->getAndroidManifestFile() != NULL) { + // place at root of zip. + String8 srcFile(bundle->getAndroidManifestFile()); + addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(), + NULL, String8()); + totalCount++; + } + + /* + * If a directory of custom assets was supplied, slurp 'em up. + */ + if (bundle->getAssetSourceDir()) { + const char* assetDir = bundle->getAssetSourceDir(); + + FileType type = getFileType(assetDir); + if (type == kFileTypeNonexistent) { + fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDir); + return UNKNOWN_ERROR; + } + if (type != kFileTypeDirectory) { + fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir); + return UNKNOWN_ERROR; + } + + String8 assetRoot(assetDir); + sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir)); + AaptGroupEntry group; + count = assetAaptDir->slurpFullTree(bundle, assetRoot, group, + String8(), mFullAssetPaths); + if (count < 0) { + totalCount = count; + goto bail; + } + if (count > 0) { + mGroupEntries.add(group); + } + totalCount += count; + + if (bundle->getVerbose()) + printf("Found %d custom asset file%s in %s\n", + count, (count==1) ? "" : "s", assetDir); + } + + /* + * If a directory of resource-specific assets was supplied, slurp 'em up. + */ + for (size_t i=0; i<dirCount; i++) { + const char *res = resDirs[i]; + if (res) { + type = getFileType(res); + if (type == kFileTypeNonexistent) { + fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res); + return UNKNOWN_ERROR; + } + if (type == kFileTypeDirectory) { + if (i>0) { + sp<AaptAssets> nextOverlay = new AaptAssets(); + current->setOverlay(nextOverlay); + current = nextOverlay; + current->setFullResPaths(mFullResPaths); + } + count = current->slurpResourceTree(bundle, String8(res)); + + if (count < 0) { + totalCount = count; + goto bail; + } + totalCount += count; + } + else { + fprintf(stderr, "ERROR: '%s' is not a directory\n", res); + return UNKNOWN_ERROR; + } + } + + } + /* + * Now do any additional raw files. + */ + for (int arg=0; arg<N; arg++) { + const char* assetDir = bundle->getFileSpecEntry(arg); + + FileType type = getFileType(assetDir); + if (type == kFileTypeNonexistent) { + fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir); + return UNKNOWN_ERROR; + } + if (type != kFileTypeDirectory) { + fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir); + return UNKNOWN_ERROR; + } + + String8 assetRoot(assetDir); + + if (bundle->getVerbose()) + printf("Processing raw dir '%s'\n", (const char*) assetDir); + + /* + * Do a recursive traversal of subdir tree. We don't make any + * guarantees about ordering, so we're okay with an inorder search + * using whatever order the OS happens to hand back to us. + */ + count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths); + if (count < 0) { + /* failure; report error and remove archive */ + totalCount = count; + goto bail; + } + totalCount += count; + + if (bundle->getVerbose()) + printf("Found %d asset file%s in %s\n", + count, (count==1) ? "" : "s", assetDir); + } + + count = validate(); + if (count != NO_ERROR) { + totalCount = count; + goto bail; + } + + count = filter(bundle); + if (count != NO_ERROR) { + totalCount = count; + goto bail; + } + +bail: + return totalCount; +} + +ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir, + const AaptGroupEntry& kind, + const String8& resType, + sp<FilePathStore>& fullResPaths) +{ + ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths); + if (res > 0) { + mGroupEntries.add(kind); + } + + return res; +} + +ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir) +{ + ssize_t err = 0; + + DIR* dir = opendir(srcDir.string()); + if (dir == NULL) { + fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno)); + return UNKNOWN_ERROR; + } + + status_t count = 0; + + /* + * Run through the directory, looking for dirs that match the + * expected pattern. + */ + while (1) { + struct dirent* entry = readdir(dir); + if (entry == NULL) { + break; + } + + if (isHidden(srcDir.string(), entry->d_name)) { + continue; + } + + String8 subdirName(srcDir); + subdirName.appendPath(entry->d_name); + + AaptGroupEntry group; + String8 resType; + bool b = group.initFromDirName(entry->d_name, &resType); + if (!b) { + fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(), + entry->d_name); + err = -1; + continue; + } + + if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) { + int maxResInt = atoi(bundle->getMaxResVersion()); + const char *verString = group.getVersionString().string(); + int dirVersionInt = atoi(verString + 1); // skip 'v' in version name + if (dirVersionInt > maxResInt) { + fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name); + continue; + } + } + + FileType type = getFileType(subdirName.string()); + + if (type == kFileTypeDirectory) { + sp<AaptDir> dir = makeDir(resType); + ssize_t res = dir->slurpFullTree(bundle, subdirName, group, + resType, mFullResPaths); + if (res < 0) { + count = res; + goto bail; + } + if (res > 0) { + mGroupEntries.add(group); + count += res; + } + + // Only add this directory if we don't already have a resource dir + // for the current type. This ensures that we only add the dir once + // for all configs. + sp<AaptDir> rdir = resDir(resType); + if (rdir == NULL) { + mResDirs.add(dir); + } + } else { + if (bundle->getVerbose()) { + fprintf(stderr, " (ignoring file '%s')\n", subdirName.string()); + } + } + } + +bail: + closedir(dir); + dir = NULL; + + if (err != 0) { + return err; + } + return count; +} + +ssize_t +AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename) +{ + 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()) { + continue; + } + + String8 entryName(entry->getFileName()); + + String8 dirName = entryName.getPathDir(); + sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName); + + String8 resType; + AaptGroupEntry kind; + + 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); + } + + // use the one from the zip file if they both exist. + dir->removeFile(entryName.getPathLeaf()); + + 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()); + +#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 + + free(data); + + count++; + } + +bail: + delete zip; + return count; +} + +status_t AaptAssets::filter(Bundle* bundle) +{ + ResourceFilter reqFilter; + status_t err = reqFilter.parse(bundle->getConfigurations()); + if (err != NO_ERROR) { + return err; + } + + ResourceFilter prefFilter; + err = prefFilter.parse(bundle->getPreferredConfigurations()); + if (err != NO_ERROR) { + return err; + } + + if (reqFilter.isEmpty() && prefFilter.isEmpty()) { + return NO_ERROR; + } + + if (bundle->getVerbose()) { + if (!reqFilter.isEmpty()) { + printf("Applying required filter: %s\n", + bundle->getConfigurations()); + } + if (!prefFilter.isEmpty()) { + printf("Applying preferred filter: %s\n", + bundle->getPreferredConfigurations()); + } + } + + const Vector<sp<AaptDir> >& resdirs = mResDirs; + const size_t ND = resdirs.size(); + for (size_t i=0; i<ND; i++) { + const sp<AaptDir>& dir = resdirs.itemAt(i); + if (dir->getLeaf() == kValuesDir) { + // The "value" dir is special since a single file defines + // multiple resources, so we can not do filtering on the + // files themselves. + continue; + } + if (dir->getLeaf() == kMipmapDir) { + // We also skip the "mipmap" directory, since the point of this + // is to include all densities without stripping. If you put + // other configurations in here as well they won't be stripped + // either... So don't do that. Seriously. What is wrong with you? + continue; + } + + const size_t NG = dir->getFiles().size(); + for (size_t j=0; j<NG; j++) { + sp<AaptGroup> grp = dir->getFiles().valueAt(j); + + // First remove any configurations we know we don't need. + for (size_t k=0; k<grp->getFiles().size(); k++) { + sp<AaptFile> file = grp->getFiles().valueAt(k); + if (k == 0 && grp->getFiles().size() == 1) { + // If this is the only file left, we need to keep it. + // Otherwise the resource IDs we are using will be inconsistent + // with what we get when not stripping. Sucky, but at least + // for now we can rely on the back-end doing another filtering + // pass to take this out and leave us with this resource name + // containing no entries. + continue; + } + if (file->getPath().getPathExtension() == ".xml") { + // We can't remove .xml files at this point, because when + // we parse them they may add identifier resources, so + // removing them can cause our resource identifiers to + // become inconsistent. + continue; + } + const ResTable_config& config(file->getGroupEntry().toParams()); + if (!reqFilter.match(config)) { + if (bundle->getVerbose()) { + printf("Pruning unneeded resource: %s\n", + file->getPrintableSource().string()); + } + grp->removeFile(k); + k--; + } + } + + // Quick check: no preferred filters, nothing more to do. + if (prefFilter.isEmpty()) { + continue; + } + + // Now deal with preferred configurations. + for (int axis=AXIS_START; axis<=AXIS_END; axis++) { + for (size_t k=0; k<grp->getFiles().size(); k++) { + sp<AaptFile> file = grp->getFiles().valueAt(k); + if (k == 0 && grp->getFiles().size() == 1) { + // If this is the only file left, we need to keep it. + // Otherwise the resource IDs we are using will be inconsistent + // with what we get when not stripping. Sucky, but at least + // for now we can rely on the back-end doing another filtering + // pass to take this out and leave us with this resource name + // containing no entries. + continue; + } + if (file->getPath().getPathExtension() == ".xml") { + // We can't remove .xml files at this point, because when + // we parse them they may add identifier resources, so + // removing them can cause our resource identifiers to + // become inconsistent. + continue; + } + const ResTable_config& config(file->getGroupEntry().toParams()); + if (!prefFilter.match(axis, config)) { + // This is a resource we would prefer not to have. Check + // to see if have a similar variation that we would like + // to have and, if so, we can drop it. + for (size_t m=0; m<grp->getFiles().size(); m++) { + if (m == k) continue; + sp<AaptFile> mfile = grp->getFiles().valueAt(m); + const ResTable_config& mconfig(mfile->getGroupEntry().toParams()); + if (AaptGroupEntry::configSameExcept(config, mconfig, axis)) { + if (prefFilter.match(axis, mconfig)) { + if (bundle->getVerbose()) { + printf("Pruning unneeded resource: %s\n", + file->getPrintableSource().string()); + } + grp->removeFile(k); + k--; + break; + } + } + } + } + } + } + } + } + + return NO_ERROR; +} + +sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name) +{ + sp<AaptSymbols> sym = mSymbols.valueFor(name); + if (sym == NULL) { + sym = new AaptSymbols(); + mSymbols.add(name, sym); + } + return sym; +} + +sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name) +{ + sp<AaptSymbols> sym = mJavaSymbols.valueFor(name); + if (sym == NULL) { + sym = new AaptSymbols(); + mJavaSymbols.add(name, sym); + } + return sym; +} + +status_t AaptAssets::applyJavaSymbols() +{ + size_t N = mJavaSymbols.size(); + for (size_t i=0; i<N; i++) { + const String8& name = mJavaSymbols.keyAt(i); + const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i); + ssize_t pos = mSymbols.indexOfKey(name); + if (pos < 0) { + SourcePos pos; + pos.error("Java symbol dir %s not defined\n", name.string()); + return UNKNOWN_ERROR; + } + //printf("**** applying java symbols in dir %s\n", name.string()); + status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols); + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const { + //printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n", + // sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0, + // sym.isJavaSymbol ? 1 : 0); + if (!mHavePrivateSymbols) return true; + if (sym.isPublic) return true; + if (includePrivate && sym.isJavaSymbol) return true; + return false; +} + +status_t AaptAssets::buildIncludedResources(Bundle* bundle) +{ + if (!mHaveIncludedAssets) { + // Add in all includes. + const Vector<const char*>& incl = bundle->getPackageIncludes(); + const size_t N=incl.size(); + for (size_t i=0; i<N; i++) { + if (bundle->getVerbose()) + printf("Including resources from package: %s\n", incl[i]); + if (!mIncludedAssets.addAssetPath(String8(incl[i]), NULL)) { + fprintf(stderr, "ERROR: Asset package include '%s' not found.\n", + incl[i]); + return UNKNOWN_ERROR; + } + } + mHaveIncludedAssets = true; + } + + return NO_ERROR; +} + +status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file) +{ + const ResTable& res = getIncludedResources(); + // XXX dirty! + return const_cast<ResTable&>(res).add(file->getData(), file->getSize(), NULL); +} + +const ResTable& AaptAssets::getIncludedResources() const +{ + return mIncludedAssets.getResources(false); +} + +void AaptAssets::print(const String8& prefix) const +{ + String8 innerPrefix(prefix); + innerPrefix.append(" "); + String8 innerInnerPrefix(innerPrefix); + innerInnerPrefix.append(" "); + printf("%sConfigurations:\n", prefix.string()); + const size_t N=mGroupEntries.size(); + for (size_t i=0; i<N; i++) { + String8 cname = mGroupEntries.itemAt(i).toDirName(String8()); + printf("%s %s\n", prefix.string(), + cname != "" ? cname.string() : "(default)"); + } + + printf("\n%sFiles:\n", prefix.string()); + AaptDir::print(innerPrefix); + + printf("\n%sResource Dirs:\n", prefix.string()); + const Vector<sp<AaptDir> >& resdirs = mResDirs; + const size_t NR = resdirs.size(); + for (size_t i=0; i<NR; i++) { + const sp<AaptDir>& d = resdirs.itemAt(i); + printf("%s Type %s\n", prefix.string(), d->getLeaf().string()); + d->print(innerInnerPrefix); + } +} + +sp<AaptDir> AaptAssets::resDir(const String8& name) const +{ + const Vector<sp<AaptDir> >& resdirs = mResDirs; + const size_t N = resdirs.size(); + for (size_t i=0; i<N; i++) { + const sp<AaptDir>& d = resdirs.itemAt(i); + if (d->getLeaf() == name) { + return d; + } + } + return NULL; +} + +bool +valid_symbol_name(const String8& symbol) +{ + static char const * const KEYWORDS[] = { + "abstract", "assert", "boolean", "break", + "byte", "case", "catch", "char", "class", "const", "continue", + "default", "do", "double", "else", "enum", "extends", "final", + "finally", "float", "for", "goto", "if", "implements", "import", + "instanceof", "int", "interface", "long", "native", "new", "package", + "private", "protected", "public", "return", "short", "static", + "strictfp", "super", "switch", "synchronized", "this", "throw", + "throws", "transient", "try", "void", "volatile", "while", + "true", "false", "null", + NULL + }; + const char*const* k = KEYWORDS; + const char*const s = symbol.string(); + while (*k) { + if (0 == strcmp(s, *k)) { + return false; + } + k++; + } + return true; +} diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h new file mode 100644 index 0000000..5cfa913 --- /dev/null +++ b/tools/aapt/AaptAssets.h @@ -0,0 +1,633 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Information about assets being operated on. +// +#ifndef __AAPT_ASSETS_H +#define __AAPT_ASSETS_H + +#include <stdlib.h> +#include <androidfw/AssetManager.h> +#include <androidfw/ResourceTypes.h> +#include <utils/KeyedVector.h> +#include <utils/RefBase.h> +#include <utils/SortedVector.h> +#include <utils/String8.h> +#include <utils/String8.h> +#include <utils/Vector.h> +#include "ZipFile.h" + +#include "Bundle.h" +#include "SourcePos.h" + +using namespace android; + + +extern const char * const gDefaultIgnoreAssets; +extern const char * gUserIgnoreAssets; + +bool valid_symbol_name(const String8& str); + +class AaptAssets; + +enum { + AXIS_NONE = 0, + AXIS_MCC = 1, + AXIS_MNC, + AXIS_LANGUAGE, + AXIS_REGION, + AXIS_SCREENLAYOUTSIZE, + AXIS_SCREENLAYOUTLONG, + AXIS_ORIENTATION, + AXIS_UIMODETYPE, + AXIS_UIMODENIGHT, + AXIS_DENSITY, + AXIS_TOUCHSCREEN, + AXIS_KEYSHIDDEN, + AXIS_KEYBOARD, + AXIS_NAVHIDDEN, + AXIS_NAVIGATION, + AXIS_SCREENSIZE, + AXIS_SMALLESTSCREENWIDTHDP, + AXIS_SCREENWIDTHDP, + AXIS_SCREENHEIGHTDP, + AXIS_LAYOUTDIR, + AXIS_VERSION, + + AXIS_START = AXIS_MCC, + AXIS_END = AXIS_VERSION, +}; + +/** + * This structure contains a specific variation of a single file out + * of all the variations it can have that we can have. + */ +struct AaptGroupEntry +{ +public: + AaptGroupEntry() : mParamsChanged(true) { } + AaptGroupEntry(const String8& _locale, const String8& _vendor) + : locale(_locale), vendor(_vendor), mParamsChanged(true) { } + + bool initFromDirName(const char* dir, String8* resType); + + static status_t parseNamePart(const String8& part, int* axis, uint32_t* value); + + static uint32_t getConfigValueForAxis(const ResTable_config& config, int axis); + + static bool configSameExcept(const ResTable_config& config, + const ResTable_config& otherConfig, int axis); + + static bool getMccName(const char* name, ResTable_config* out = NULL); + static bool getMncName(const char* name, ResTable_config* out = NULL); + static bool getLocaleName(const char* name, ResTable_config* out = NULL); + static bool getScreenLayoutSizeName(const char* name, ResTable_config* out = NULL); + static bool getScreenLayoutLongName(const char* name, ResTable_config* out = NULL); + static bool getOrientationName(const char* name, ResTable_config* out = NULL); + static bool getUiModeTypeName(const char* name, ResTable_config* out = NULL); + static bool getUiModeNightName(const char* name, ResTable_config* out = NULL); + static bool getDensityName(const char* name, ResTable_config* out = NULL); + static bool getTouchscreenName(const char* name, ResTable_config* out = NULL); + static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL); + static bool getKeyboardName(const char* name, ResTable_config* out = NULL); + static bool getNavigationName(const char* name, ResTable_config* out = NULL); + static bool getNavHiddenName(const char* name, ResTable_config* out = NULL); + static bool getScreenSizeName(const char* name, ResTable_config* out = NULL); + static bool getSmallestScreenWidthDpName(const char* name, ResTable_config* out = NULL); + static bool getScreenWidthDpName(const char* name, ResTable_config* out = NULL); + static bool getScreenHeightDpName(const char* name, ResTable_config* out = NULL); + static bool getLayoutDirectionName(const char* name, ResTable_config* out = NULL); + static bool getVersionName(const char* name, ResTable_config* out = NULL); + + int compare(const AaptGroupEntry& o) const; + + const ResTable_config& toParams() const; + + inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; } + inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; } + inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; } + inline bool operator!=(const AaptGroupEntry& o) const { return compare(o) != 0; } + inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; } + inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; } + + String8 toString() const; + String8 toDirName(const String8& resType) const; + + const String8& getVersionString() const { return version; } + +private: + String8 mcc; + String8 mnc; + String8 locale; + String8 vendor; + String8 smallestScreenWidthDp; + String8 screenWidthDp; + String8 screenHeightDp; + String8 screenLayoutSize; + String8 screenLayoutLong; + String8 orientation; + String8 uiModeType; + String8 uiModeNight; + String8 density; + String8 touchscreen; + String8 keysHidden; + String8 keyboard; + String8 navHidden; + String8 navigation; + String8 screenSize; + String8 layoutDirection; + String8 version; + + mutable bool mParamsChanged; + mutable ResTable_config mParams; +}; + +inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs) +{ + return lhs.compare(rhs); +} + +inline int strictly_order_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs) +{ + return compare_type(lhs, rhs) < 0; +} + +class AaptGroup; +class FilePathStore; + +/** + * A single asset file we know about. + */ +class AaptFile : public RefBase +{ +public: + AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry, + const String8& resType) + : mGroupEntry(groupEntry) + , mResourceType(resType) + , mSourceFile(sourceFile) + , mData(NULL) + , mDataSize(0) + , mBufferSize(0) + , mCompression(ZipEntry::kCompressStored) + { + //printf("new AaptFile created %s\n", (const char*)sourceFile); + } + virtual ~AaptFile() { + free(mData); + } + + const String8& getPath() const { return mPath; } + const AaptGroupEntry& getGroupEntry() const { return mGroupEntry; } + + // Data API. If there is data attached to the file, + // getSourceFile() is not used. + bool hasData() const { return mData != NULL; } + const void* getData() const { return mData; } + size_t getSize() const { return mDataSize; } + void* editData(size_t size); + void* editData(size_t* outSize = NULL); + void* padData(size_t wordSize); + status_t writeData(const void* data, size_t size); + void clearData(); + + const String8& getResourceType() const { return mResourceType; } + + // File API. If the file does not hold raw data, this is + // a full path to a file on the filesystem that holds its data. + const String8& getSourceFile() const { return mSourceFile; } + + String8 getPrintableSource() const; + + // Desired compression method, as per utils/ZipEntry.h. For example, + // no compression is ZipEntry::kCompressStored. + int getCompressionMethod() const { return mCompression; } + void setCompressionMethod(int c) { mCompression = c; } +private: + friend class AaptGroup; + + String8 mPath; + AaptGroupEntry mGroupEntry; + String8 mResourceType; + String8 mSourceFile; + void* mData; + size_t mDataSize; + size_t mBufferSize; + int mCompression; +}; + +/** + * A group of related files (the same file, with different + * vendor/locale variations). + */ +class AaptGroup : public RefBase +{ +public: + AaptGroup(const String8& leaf, const String8& path) + : mLeaf(leaf), mPath(path) { } + virtual ~AaptGroup() { } + + const String8& getLeaf() const { return mLeaf; } + + // Returns the relative path after the AaptGroupEntry dirs. + const String8& getPath() const { return mPath; } + + const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& getFiles() const + { return mFiles; } + + status_t addFile(const sp<AaptFile>& file); + void removeFile(size_t index); + + void print(const String8& prefix) const; + + String8 getPrintableSource() const; + +private: + String8 mLeaf; + String8 mPath; + + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles; +}; + +/** + * A single directory of assets, which can contain files and other + * sub-directories. + */ +class AaptDir : public RefBase +{ +public: + AaptDir(const String8& leaf, const String8& path) + : mLeaf(leaf), mPath(path) { } + virtual ~AaptDir() { } + + const String8& getLeaf() const { return mLeaf; } + + const String8& getPath() const { return mPath; } + + const DefaultKeyedVector<String8, sp<AaptGroup> >& getFiles() const { return mFiles; } + const DefaultKeyedVector<String8, sp<AaptDir> >& getDirs() const { return mDirs; } + + virtual status_t addFile(const String8& name, const sp<AaptGroup>& file); + + void removeFile(const String8& name); + void removeDir(const String8& name); + + /* + * Perform some sanity checks on the names of files and directories here. + * In particular: + * - Check for illegal chars in filenames. + * - Check filename length. + * - Check for presence of ".gz" and non-".gz" copies of same file. + * - Check for multiple files whose names match in a case-insensitive + * fashion (problematic for some systems). + * + * Comparing names against all other names is O(n^2). We could speed + * it up some by sorting the entries and being smarter about what we + * compare against, but I'm not expecting to have enough files in a + * single directory to make a noticeable difference in speed. + * + * Note that sorting here is not enough to guarantee that the package + * contents are sorted -- subsequent updates can rearrange things. + */ + status_t validate() const; + + void print(const String8& prefix) const; + + String8 getPrintableSource() const; + +private: + friend class AaptAssets; + + status_t addDir(const String8& name, const sp<AaptDir>& dir); + sp<AaptDir> makeDir(const String8& name); + status_t addLeafFile(const String8& leafName, + const sp<AaptFile>& file); + virtual ssize_t slurpFullTree(Bundle* bundle, + const String8& srcDir, + const AaptGroupEntry& kind, + const String8& resType, + sp<FilePathStore>& fullResPaths); + + String8 mLeaf; + String8 mPath; + + DefaultKeyedVector<String8, sp<AaptGroup> > mFiles; + DefaultKeyedVector<String8, sp<AaptDir> > mDirs; +}; + +/** + * All information we know about a particular symbol. + */ +class AaptSymbolEntry +{ +public: + AaptSymbolEntry() + : isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN) + { + } + AaptSymbolEntry(const String8& _name) + : name(_name), isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN) + { + } + AaptSymbolEntry(const AaptSymbolEntry& o) + : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic) + , isJavaSymbol(o.isJavaSymbol), comment(o.comment), typeComment(o.typeComment) + , typeCode(o.typeCode), int32Val(o.int32Val), stringVal(o.stringVal) + { + } + AaptSymbolEntry operator=(const AaptSymbolEntry& o) + { + sourcePos = o.sourcePos; + isPublic = o.isPublic; + isJavaSymbol = o.isJavaSymbol; + comment = o.comment; + typeComment = o.typeComment; + typeCode = o.typeCode; + int32Val = o.int32Val; + stringVal = o.stringVal; + return *this; + } + + const String8 name; + + SourcePos sourcePos; + bool isPublic; + bool isJavaSymbol; + + String16 comment; + String16 typeComment; + + enum { + TYPE_UNKNOWN = 0, + TYPE_INT32, + TYPE_STRING + }; + + int typeCode; + + // Value. May be one of these. + int32_t int32Val; + String8 stringVal; +}; + +/** + * A group of related symbols (such as indices into a string block) + * that have been generated from the assets. + */ +class AaptSymbols : public RefBase +{ +public: + AaptSymbols() { } + virtual ~AaptSymbols() { } + + status_t addSymbol(const String8& name, int32_t value, const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "symbol")) { + return BAD_VALUE; + } + AaptSymbolEntry& sym = edit_symbol(name, &pos); + sym.typeCode = AaptSymbolEntry::TYPE_INT32; + sym.int32Val = value; + return NO_ERROR; + } + + status_t addStringSymbol(const String8& name, const String8& value, + const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "symbol")) { + return BAD_VALUE; + } + AaptSymbolEntry& sym = edit_symbol(name, &pos); + sym.typeCode = AaptSymbolEntry::TYPE_STRING; + sym.stringVal = value; + return NO_ERROR; + } + + status_t makeSymbolPublic(const String8& name, const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "symbol")) { + return BAD_VALUE; + } + AaptSymbolEntry& sym = edit_symbol(name, &pos); + sym.isPublic = true; + return NO_ERROR; + } + + status_t makeSymbolJavaSymbol(const String8& name, const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "symbol")) { + return BAD_VALUE; + } + AaptSymbolEntry& sym = edit_symbol(name, &pos); + sym.isJavaSymbol = true; + return NO_ERROR; + } + + void appendComment(const String8& name, const String16& comment, const SourcePos& pos) { + if (comment.size() <= 0) { + return; + } + AaptSymbolEntry& sym = edit_symbol(name, &pos); + if (sym.comment.size() == 0) { + sym.comment = comment; + } else { + sym.comment.append(String16("\n")); + sym.comment.append(comment); + } + } + + void appendTypeComment(const String8& name, const String16& comment) { + if (comment.size() <= 0) { + return; + } + AaptSymbolEntry& sym = edit_symbol(name, NULL); + if (sym.typeComment.size() == 0) { + sym.typeComment = comment; + } else { + sym.typeComment.append(String16("\n")); + sym.typeComment.append(comment); + } + } + + sp<AaptSymbols> addNestedSymbol(const String8& name, const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "nested symbol")) { + return NULL; + } + + sp<AaptSymbols> sym = mNestedSymbols.valueFor(name); + if (sym == NULL) { + sym = new AaptSymbols(); + mNestedSymbols.add(name, sym); + } + + return sym; + } + + status_t applyJavaSymbols(const sp<AaptSymbols>& javaSymbols); + + const KeyedVector<String8, AaptSymbolEntry>& getSymbols() const + { return mSymbols; } + const DefaultKeyedVector<String8, sp<AaptSymbols> >& getNestedSymbols() const + { return mNestedSymbols; } + + const String16& getComment(const String8& name) const + { return get_symbol(name).comment; } + const String16& getTypeComment(const String8& name) const + { return get_symbol(name).typeComment; } + +private: + bool check_valid_symbol_name(const String8& symbol, const SourcePos& pos, const char* label) { + if (valid_symbol_name(symbol)) { + return true; + } + pos.error("invalid %s: '%s'\n", label, symbol.string()); + return false; + } + AaptSymbolEntry& edit_symbol(const String8& symbol, const SourcePos* pos) { + ssize_t i = mSymbols.indexOfKey(symbol); + if (i < 0) { + i = mSymbols.add(symbol, AaptSymbolEntry(symbol)); + } + AaptSymbolEntry& sym = mSymbols.editValueAt(i); + if (pos != NULL && sym.sourcePos.line < 0) { + sym.sourcePos = *pos; + } + return sym; + } + const AaptSymbolEntry& get_symbol(const String8& symbol) const { + ssize_t i = mSymbols.indexOfKey(symbol); + if (i >= 0) { + return mSymbols.valueAt(i); + } + return mDefSymbol; + } + + KeyedVector<String8, AaptSymbolEntry> mSymbols; + DefaultKeyedVector<String8, sp<AaptSymbols> > mNestedSymbols; + AaptSymbolEntry mDefSymbol; +}; + +class ResourceTypeSet : public RefBase, + public KeyedVector<String8,sp<AaptGroup> > +{ +public: + ResourceTypeSet(); +}; + +// Storage for lists of fully qualified paths for +// resources encountered during slurping. +class FilePathStore : public RefBase, + public Vector<String8> +{ +public: + FilePathStore(); +}; + +/** + * Asset hierarchy being operated on. + */ +class AaptAssets : public AaptDir +{ +public: + AaptAssets(); + virtual ~AaptAssets() { delete mRes; } + + const String8& getPackage() const { return mPackage; } + void setPackage(const String8& package) { + mPackage = package; + mSymbolsPrivatePackage = package; + mHavePrivateSymbols = false; + } + + const SortedVector<AaptGroupEntry>& getGroupEntries() const; + + virtual status_t addFile(const String8& name, const sp<AaptGroup>& file); + + sp<AaptFile> addFile(const String8& filePath, + const AaptGroupEntry& entry, + const String8& srcDir, + sp<AaptGroup>* outGroup, + const String8& resType); + + void addResource(const String8& leafName, + const String8& path, + const sp<AaptFile>& file, + const String8& resType); + + void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); } + + ssize_t slurpFromArgs(Bundle* bundle); + + sp<AaptSymbols> getSymbolsFor(const String8& name); + + sp<AaptSymbols> getJavaSymbolsFor(const String8& name); + + status_t applyJavaSymbols(); + + const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; } + + String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; } + void setSymbolsPrivatePackage(const String8& pkg) { + mSymbolsPrivatePackage = pkg; + mHavePrivateSymbols = mSymbolsPrivatePackage != mPackage; + } + + bool havePrivateSymbols() const { return mHavePrivateSymbols; } + + bool isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const; + + status_t buildIncludedResources(Bundle* bundle); + status_t addIncludedResources(const sp<AaptFile>& file); + const ResTable& getIncludedResources() const; + + void print(const String8& prefix) const; + + inline const Vector<sp<AaptDir> >& resDirs() const { return mResDirs; } + sp<AaptDir> resDir(const String8& name) const; + + inline sp<AaptAssets> getOverlay() { return mOverlay; } + inline void setOverlay(sp<AaptAssets>& overlay) { mOverlay = overlay; } + + inline KeyedVector<String8, sp<ResourceTypeSet> >* getResources() { return mRes; } + inline void + setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { delete mRes; mRes = res; } + + inline sp<FilePathStore>& getFullResPaths() { return mFullResPaths; } + inline void + setFullResPaths(sp<FilePathStore>& res) { mFullResPaths = res; } + + inline sp<FilePathStore>& getFullAssetPaths() { return mFullAssetPaths; } + inline void + setFullAssetPaths(sp<FilePathStore>& res) { mFullAssetPaths = res; } + +private: + virtual ssize_t slurpFullTree(Bundle* bundle, + const String8& srcDir, + const AaptGroupEntry& kind, + const String8& resType, + sp<FilePathStore>& fullResPaths); + + ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir); + ssize_t slurpResourceZip(Bundle* bundle, const char* filename); + + status_t filter(Bundle* bundle); + + String8 mPackage; + SortedVector<AaptGroupEntry> mGroupEntries; + DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols; + DefaultKeyedVector<String8, sp<AaptSymbols> > mJavaSymbols; + String8 mSymbolsPrivatePackage; + bool mHavePrivateSymbols; + + Vector<sp<AaptDir> > mResDirs; + + bool mChanged; + + bool mHaveIncludedAssets; + AssetManager mIncludedAssets; + + sp<AaptAssets> mOverlay; + KeyedVector<String8, sp<ResourceTypeSet> >* mRes; + + sp<FilePathStore> mFullResPaths; + sp<FilePathStore> mFullAssetPaths; +}; + +#endif // __AAPT_ASSETS_H + diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk new file mode 100644 index 0000000..452c60a --- /dev/null +++ b/tools/aapt/Android.mk @@ -0,0 +1,103 @@ +# +# Copyright 2006 The Android Open Source Project +# +# Android Asset Packaging Tool +# + +# This tool is prebuilt if we're doing an app-only build. +ifeq ($(TARGET_BUILD_APPS),) + + +aapt_src_files := \ + AaptAssets.cpp \ + Command.cpp \ + CrunchCache.cpp \ + FileFinder.cpp \ + Main.cpp \ + Package.cpp \ + StringPool.cpp \ + XMLNode.cpp \ + ResourceFilter.cpp \ + ResourceIdCache.cpp \ + ResourceTable.cpp \ + Images.cpp \ + Resource.cpp \ + pseudolocalize.cpp \ + SourcePos.cpp \ + WorkQueue.cpp \ + ZipEntry.cpp \ + ZipFile.cpp \ + qsort_r_compat.c + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(aapt_src_files) + +LOCAL_CFLAGS += -Wno-format-y2k +ifeq (darwin,$(HOST_OS)) +LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS +endif + +LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS + +LOCAL_C_INCLUDES += external/libpng +LOCAL_C_INCLUDES += external/zlib + +LOCAL_STATIC_LIBRARIES := \ + libandroidfw \ + libutils \ + libcutils \ + libexpat \ + libpng \ + liblog + +ifeq ($(HOST_OS),linux) +LOCAL_LDLIBS += -lrt -ldl -lpthread +endif + +# Statically link libz for MinGW (Win SDK under Linux), +# and dynamically link for all others. +ifneq ($(strip $(USE_MINGW)),) + LOCAL_STATIC_LIBRARIES += libz +else + LOCAL_LDLIBS += -lz +endif + +LOCAL_MODULE := aapt + +include $(BUILD_HOST_EXECUTABLE) + +# aapt for running on the device +# ========================================================= +ifneq ($(SDK_ONLY),true) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(aapt_src_files) + +LOCAL_MODULE := aapt + +LOCAL_C_INCLUDES += bionic +LOCAL_C_INCLUDES += bionic/libstdc++/include +LOCAL_C_INCLUDES += external/stlport/stlport +LOCAL_C_INCLUDES += external/libpng +LOCAL_C_INCLUDES += external/zlib + +LOCAL_CFLAGS += -Wno-non-virtual-dtor + +LOCAL_SHARED_LIBRARIES := \ + libandroidfw \ + libutils \ + libcutils \ + libpng \ + liblog \ + libz + +LOCAL_STATIC_LIBRARIES := \ + libstlport_static \ + libexpat_static + +include $(BUILD_EXECUTABLE) +endif + +endif # TARGET_BUILD_APPS diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h new file mode 100644 index 0000000..b67ca09 --- /dev/null +++ b/tools/aapt/Bundle.h @@ -0,0 +1,309 @@ +// +// Copyright 2006 The Android Open Source Project +// +// State bundle. Used to pass around stuff like command-line args. +// +#ifndef __BUNDLE_H +#define __BUNDLE_H + +#include <stdlib.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +enum { + SDK_CUPCAKE = 3, + SDK_DONUT = 4, + SDK_ECLAIR = 5, + SDK_ECLAIR_0_1 = 6, + SDK_MR1 = 7, + SDK_FROYO = 8, + SDK_HONEYCOMB_MR2 = 13, + SDK_ICE_CREAM_SANDWICH = 14, + SDK_ICE_CREAM_SANDWICH_MR1 = 15, +}; + +/* + * Things we can do. + */ +typedef enum Command { + kCommandUnknown = 0, + kCommandVersion, + kCommandList, + kCommandDump, + kCommandAdd, + kCommandRemove, + kCommandPackage, + kCommandCrunch, + kCommandSingleCrunch, +} Command; + +/* + * Bundle of goodies, including everything specified on the command line. + */ +class Bundle { +public: + Bundle(void) + : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false), + mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), + mUpdate(false), mExtending(false), + mRequireLocalization(false), mPseudolocalize(false), + mWantUTF16(false), mValues(false), mIncludeMetaData(false), + mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), + mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL), + mAutoAddOverlay(false), mGenDependencies(false), + 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), + mUseCrunchCache(false), mErrorOnFailedInsert(false), mOutputTextSymbols(NULL), + mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), + mArgc(0), mArgv(NULL) + {} + ~Bundle(void) {} + + /* + * Set the command value. Returns "false" if it was previously set. + */ + Command getCommand(void) const { return mCmd; } + void setCommand(Command cmd) { mCmd = cmd; } + + /* + * Command modifiers. Not all modifiers are appropriate for all + * commands. + */ + bool getVerbose(void) const { return mVerbose; } + void setVerbose(bool val) { mVerbose = val; } + bool getAndroidList(void) const { return mAndroidList; } + void setAndroidList(bool val) { mAndroidList = val; } + bool getForce(void) const { return mForce; } + void setForce(bool val) { mForce = val; } + void setGrayscaleTolerance(int val) { mGrayscaleTolerance = val; } + int getGrayscaleTolerance() const { return mGrayscaleTolerance; } + bool getMakePackageDirs(void) const { return mMakePackageDirs; } + void setMakePackageDirs(bool val) { mMakePackageDirs = val; } + bool getUpdate(void) const { return mUpdate; } + void setUpdate(bool val) { mUpdate = val; } + bool getExtending(void) const { return mExtending; } + void setExtending(bool val) { mExtending = val; } + bool getRequireLocalization(void) const { return mRequireLocalization; } + void setRequireLocalization(bool val) { mRequireLocalization = val; } + bool getPseudolocalize(void) const { return mPseudolocalize; } + void setPseudolocalize(bool val) { mPseudolocalize = val; } + void setWantUTF16(bool val) { mWantUTF16 = val; } + bool getValues(void) const { return mValues; } + void setValues(bool val) { mValues = val; } + bool getIncludeMetaData(void) const { return mIncludeMetaData; } + void setIncludeMetaData(bool val) { mIncludeMetaData = val; } + int getCompressionMethod(void) const { return mCompressionMethod; } + void setCompressionMethod(int val) { mCompressionMethod = val; } + bool getJunkPath(void) const { return mJunkPath; } + void setJunkPath(bool val) { mJunkPath = val; } + const char* getOutputAPKFile() const { return mOutputAPKFile; } + void setOutputAPKFile(const char* val) { mOutputAPKFile = val; } + const char* getManifestPackageNameOverride() const { return mManifestPackageNameOverride; } + void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; } + const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; } + void setInstrumentationPackageNameOverride(const char * val) { mInstrumentationPackageNameOverride = val; } + bool getAutoAddOverlay() { return mAutoAddOverlay; } + void setAutoAddOverlay(bool val) { mAutoAddOverlay = val; } + bool getGenDependencies() { return mGenDependencies; } + void setGenDependencies(bool val) { mGenDependencies = val; } + bool getErrorOnFailedInsert() { return mErrorOnFailedInsert; } + void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; } + + bool getUTF16StringsOption() { + return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO); + } + + /* + * Input options. + */ + 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 addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); } + const char* getAndroidManifestFile() const { return mAndroidManifestFile; } + void setAndroidManifestFile(const char* file) { mAndroidManifestFile = file; } + const char* getPublicOutputFile() const { return mPublicOutputFile; } + void setPublicOutputFile(const char* file) { mPublicOutputFile = file; } + const char* getRClassDir() const { return mRClassDir; } + void setRClassDir(const char* dir) { mRClassDir = dir; } + const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; } + void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } } + const char* getPreferredConfigurations() const { return mPreferredConfigurations.size() > 0 ? mPreferredConfigurations.string() : NULL; } + void addPreferredConfigurations(const char* val) { if (mPreferredConfigurations.size() > 0) { mPreferredConfigurations.append(","); mPreferredConfigurations.append(val); } else { mPreferredConfigurations = val; } } + const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; } + void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; } + const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; } + void addPackageInclude(const char* file) { mPackageIncludes.add(file); } + const android::Vector<const char*>& getJarFiles() const { return mJarFiles; } + void addJarFile(const char* file) { mJarFiles.add(file); } + const android::Vector<const char*>& getNoCompressExtensions() const { return mNoCompressExtensions; } + void addNoCompressExtension(const char* ext) { mNoCompressExtensions.add(ext); } + + const char* getManifestMinSdkVersion() const { return mManifestMinSdkVersion; } + void setManifestMinSdkVersion(const char* val) { mManifestMinSdkVersion = val; } + const char* getMinSdkVersion() const { return mMinSdkVersion; } + void setMinSdkVersion(const char* val) { mMinSdkVersion = val; } + const char* getTargetSdkVersion() const { return mTargetSdkVersion; } + void setTargetSdkVersion(const char* val) { mTargetSdkVersion = val; } + const char* getMaxSdkVersion() const { return mMaxSdkVersion; } + void setMaxSdkVersion(const char* val) { mMaxSdkVersion = val; } + const char* getVersionCode() const { return mVersionCode; } + void setVersionCode(const char* val) { mVersionCode = val; } + const char* getVersionName() const { return mVersionName; } + void setVersionName(const char* val) { mVersionName = val; } + const char* getCustomPackage() const { return mCustomPackage; } + void setCustomPackage(const char* val) { mCustomPackage = val; } + const char* getExtraPackages() const { return mExtraPackages; } + void setExtraPackages(const char* val) { mExtraPackages = val; } + const char* getMaxResVersion() const { return mMaxResVersion; } + void setMaxResVersion(const char * val) { mMaxResVersion = val; } + bool getDebugMode() const { return mDebugMode; } + void setDebugMode(bool val) { mDebugMode = val; } + bool getNonConstantId() const { return mNonConstantId; } + 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() const { return mUseCrunchCache; } + const char* getOutputTextSymbols() const { return mOutputTextSymbols; } + void setOutputTextSymbols(const char* val) { mOutputTextSymbols = val; } + const char* getSingleCrunchInputFile() const { return mSingleCrunchInputFile; } + void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; } + const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; } + void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } + + /* + * Set and get the file specification. + * + * Note this does NOT make a copy of argv. + */ + void setFileSpec(char* const argv[], int argc) { + mArgc = argc; + mArgv = argv; + } + int getFileSpecCount(void) const { return mArgc; } + const char* getFileSpecEntry(int idx) const { return mArgv[idx]; } + void eatArgs(int n) { + if (n > mArgc) n = mArgc; + mArgv += n; + mArgc -= n; + } + +#if 0 + /* + * Package count. Nothing to do with anything else here; this is + * just a convenient place to stuff it so we don't have to pass it + * around everywhere. + */ + int getPackageCount(void) const { return mPackageCount; } + void setPackageCount(int val) { mPackageCount = val; } +#endif + + /* Certain features may only be available on a specific SDK level or + * above. SDK levels that have a non-numeric identifier are assumed + * to be newer than any SDK level that has a number designated. + */ + bool isMinSdkAtLeast(int desired) { + /* If the application specifies a minSdkVersion in the manifest + * then use that. Otherwise, check what the user specified on + * the command line. If neither, it's not available since + * the minimum SDK version is assumed to be 1. + */ + const char *minVer; + if (mManifestMinSdkVersion != NULL) { + minVer = mManifestMinSdkVersion; + } else if (mMinSdkVersion != NULL) { + minVer = mMinSdkVersion; + } else { + return false; + } + + char *end; + int minSdkNum = (int)strtol(minVer, &end, 0); + if (*end == '\0') { + if (minSdkNum < desired) { + return false; + } + } + return true; + } + +private: + /* commands & modifiers */ + Command mCmd; + bool mVerbose; + bool mAndroidList; + bool mForce; + int mGrayscaleTolerance; + bool mMakePackageDirs; + bool mUpdate; + bool mExtending; + bool mRequireLocalization; + bool mPseudolocalize; + bool mWantUTF16; + bool mValues; + bool mIncludeMetaData; + int mCompressionMethod; + bool mJunkPath; + const char* mOutputAPKFile; + const char* mManifestPackageNameOverride; + const char* mInstrumentationPackageNameOverride; + bool mAutoAddOverlay; + bool mGenDependencies; + const char* mAssetSourceDir; + const char* mCrunchedOutputDir; + const char* mProguardFile; + const char* mAndroidManifestFile; + const char* mPublicOutputFile; + const char* mRClassDir; + const char* mResourceIntermediatesDir; + android::String8 mConfigurations; + android::String8 mPreferredConfigurations; + android::Vector<const char*> mPackageIncludes; + android::Vector<const char*> mJarFiles; + android::Vector<const char*> mNoCompressExtensions; + android::Vector<const char*> mResourceSourceDirs; + + const char* mManifestMinSdkVersion; + const char* mMinSdkVersion; + const char* mTargetSdkVersion; + const char* mMaxSdkVersion; + const char* mVersionCode; + const char* mVersionName; + const char* mCustomPackage; + const char* mExtraPackages; + const char* mMaxResVersion; + bool mDebugMode; + bool mNonConstantId; + const char* mProduct; + bool mUseCrunchCache; + bool mErrorOnFailedInsert; + const char* mOutputTextSymbols; + const char* mSingleCrunchInputFile; + const char* mSingleCrunchOutputFile; + + /* file specification */ + int mArgc; + char* const* mArgv; + +#if 0 + /* misc stuff */ + int mPackageCount; +#endif + +}; + +#endif // __BUNDLE_H diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h new file mode 100644 index 0000000..0e65589 --- /dev/null +++ b/tools/aapt/CacheUpdater.h @@ -0,0 +1,107 @@ +// +// 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 diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp new file mode 100644 index 0000000..7fa8c9d --- /dev/null +++ b/tools/aapt/Command.cpp @@ -0,0 +1,2121 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Android Asset Packaging Tool main entry point. +// +#include "Main.h" +#include "Bundle.h" +#include "ResourceFilter.h" +#include "ResourceTable.h" +#include "Images.h" +#include "XMLNode.h" + +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> + +#include <fcntl.h> +#include <errno.h> + +using namespace android; + +/* + * Show version info. All the cool kids do it. + */ +int doVersion(Bundle* bundle) +{ + if (bundle->getFileSpecCount() != 0) { + printf("(ignoring extra arguments)\n"); + } + printf("Android Asset Packaging Tool, v0.2\n"); + + return 0; +} + + +/* + * Open the file read only. The call fails if the file doesn't exist. + * + * Returns NULL on failure. + */ +ZipFile* openReadOnly(const char* fileName) +{ + ZipFile* zip; + status_t result; + + zip = new ZipFile; + result = zip->open(fileName, ZipFile::kOpenReadOnly); + if (result != NO_ERROR) { + if (result == NAME_NOT_FOUND) { + fprintf(stderr, "ERROR: '%s' not found\n", fileName); + } else if (result == PERMISSION_DENIED) { + fprintf(stderr, "ERROR: '%s' access denied\n", fileName); + } else { + fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n", + fileName); + } + delete zip; + return NULL; + } + + return zip; +} + +/* + * Open the file read-write. The file will be created if it doesn't + * already exist and "okayToCreate" is set. + * + * Returns NULL on failure. + */ +ZipFile* openReadWrite(const char* fileName, bool okayToCreate) +{ + ZipFile* zip = NULL; + status_t result; + int flags; + + flags = ZipFile::kOpenReadWrite; + if (okayToCreate) { + flags |= ZipFile::kOpenCreate; + } + + zip = new ZipFile; + result = zip->open(fileName, flags); + if (result != NO_ERROR) { + delete zip; + zip = NULL; + goto bail; + } + +bail: + return zip; +} + + +/* + * Return a short string describing the compression method. + */ +const char* compressionName(int method) +{ + if (method == ZipEntry::kCompressStored) { + return "Stored"; + } else if (method == ZipEntry::kCompressDeflated) { + return "Deflated"; + } else { + return "Unknown"; + } +} + +/* + * Return the percent reduction in size (0% == no compression). + */ +int calcPercent(long uncompressedLen, long compressedLen) +{ + if (!uncompressedLen) { + return 0; + } else { + return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5); + } +} + +/* + * Handle the "list" command, which can be a simple file dump or + * a verbose listing. + * + * The verbose listing closely matches the output of the Info-ZIP "unzip" + * command. + */ +int doList(Bundle* bundle) +{ + int result = 1; + ZipFile* zip = NULL; + const ZipEntry* entry; + long totalUncLen, totalCompLen; + const char* zipFileName; + + if (bundle->getFileSpecCount() != 1) { + fprintf(stderr, "ERROR: specify zip file name (only)\n"); + goto bail; + } + zipFileName = bundle->getFileSpecEntry(0); + + zip = openReadOnly(zipFileName); + if (zip == NULL) { + goto bail; + } + + int count, i; + + if (bundle->getVerbose()) { + printf("Archive: %s\n", zipFileName); + printf( + " Length Method Size Ratio Offset Date Time CRC-32 Name\n"); + printf( + "-------- ------ ------- ----- ------- ---- ---- ------ ----\n"); + } + + totalUncLen = totalCompLen = 0; + + count = zip->getNumEntries(); + for (i = 0; i < count; i++) { + entry = zip->getEntryByIndex(i); + if (bundle->getVerbose()) { + char dateBuf[32]; + time_t when; + + when = entry->getModWhen(); + strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M", + localtime(&when)); + + printf("%8ld %-7.7s %7ld %3d%% %8zd %s %08lx %s\n", + (long) entry->getUncompressedLen(), + compressionName(entry->getCompressionMethod()), + (long) entry->getCompressedLen(), + calcPercent(entry->getUncompressedLen(), + entry->getCompressedLen()), + (size_t) entry->getLFHOffset(), + dateBuf, + entry->getCRC32(), + entry->getFileName()); + } else { + printf("%s\n", entry->getFileName()); + } + + totalUncLen += entry->getUncompressedLen(); + totalCompLen += entry->getCompressedLen(); + } + + if (bundle->getVerbose()) { + printf( + "-------- ------- --- -------\n"); + printf("%8ld %7ld %2d%% %d files\n", + totalUncLen, + totalCompLen, + calcPercent(totalUncLen, totalCompLen), + zip->getNumEntries()); + } + + if (bundle->getAndroidList()) { + AssetManager assets; + if (!assets.addAssetPath(String8(zipFileName), NULL)) { + fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n"); + goto bail; + } + + const ResTable& res = assets.getResources(false); + if (&res == NULL) { + printf("\nNo resource table found.\n"); + } else { +#ifndef HAVE_ANDROID_OS + printf("\nResource table:\n"); + res.print(false); +#endif + } + + Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml", + Asset::ACCESS_BUFFER); + if (manifestAsset == NULL) { + printf("\nNo AndroidManifest.xml found.\n"); + } else { + printf("\nAndroid manifest:\n"); + ResXMLTree tree; + tree.setTo(manifestAsset->getBuffer(true), + manifestAsset->getLength()); + printXMLBlock(&tree); + } + delete manifestAsset; + } + + result = 0; + +bail: + delete zip; + return result; +} + +static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) +{ + size_t N = tree.getAttributeCount(); + for (size_t i=0; i<N; i++) { + if (tree.getAttributeNameResID(i) == attrRes) { + return (ssize_t)i; + } + } + return -1; +} + +String8 getAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, String8* outError) +{ + ssize_t idx = tree.indexOfAttribute(ns, attr); + if (idx < 0) { + return String8(); + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + } + size_t len; + const uint16_t* str = tree.getAttributeStringValue(idx, &len); + return str ? String8(str, len) : String8(); +} + +static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) +{ + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return String8(); + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + } + size_t len; + const uint16_t* str = tree.getAttributeStringValue(idx, &len); + return str ? String8(str, len) : String8(); +} + +static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, + String8* outError, int32_t defValue = -1) +{ + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return defValue; + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType < Res_value::TYPE_FIRST_INT + || value.dataType > Res_value::TYPE_LAST_INT) { + if (outError != NULL) { + *outError = "attribute is not an integer value"; + } + return defValue; + } + } + return value.data; +} + +static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree, + uint32_t attrRes, String8* outError, int32_t defValue = -1) +{ + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return defValue; + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType == Res_value::TYPE_REFERENCE) { + resTable->resolveReference(&value, 0); + } + if (value.dataType < Res_value::TYPE_FIRST_INT + || value.dataType > Res_value::TYPE_LAST_INT) { + if (outError != NULL) { + *outError = "attribute is not an integer value"; + } + return defValue; + } + } + return value.data; +} + +static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree, + uint32_t attrRes, String8* outError) +{ + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return String8(); + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType == Res_value::TYPE_STRING) { + size_t len; + const uint16_t* str = tree.getAttributeStringValue(idx, &len); + return str ? String8(str, len) : String8(); + } + resTable->resolveReference(&value, 0); + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + } + size_t len; + const Res_value* value2 = &value; + const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len); + return str ? String8(str, len) : String8(); +} + +static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable, + const ResXMLTree& tree, uint32_t attrRes, String8* outError) +{ + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + if (outError != NULL) { + *outError = "attribute could not be found"; + } + return; + } + if (tree.getAttributeValue(idx, value) != NO_ERROR) { + if (value->dataType == Res_value::TYPE_REFERENCE) { + resTable->resolveReference(value, 0); + } + // The attribute was found and was resolved if need be. + return; + } + if (outError != NULL) { + *outError = "error getting resolved resource attribute"; + } +} + +// These are attribute resource constants for the platform, as found +// in android.R.attr +enum { + LABEL_ATTR = 0x01010001, + ICON_ATTR = 0x01010002, + NAME_ATTR = 0x01010003, + DEBUGGABLE_ATTR = 0x0101000f, + VALUE_ATTR = 0x01010024, + VERSION_CODE_ATTR = 0x0101021b, + VERSION_NAME_ATTR = 0x0101021c, + SCREEN_ORIENTATION_ATTR = 0x0101001e, + MIN_SDK_VERSION_ATTR = 0x0101020c, + MAX_SDK_VERSION_ATTR = 0x01010271, + REQ_TOUCH_SCREEN_ATTR = 0x01010227, + REQ_KEYBOARD_TYPE_ATTR = 0x01010228, + REQ_HARD_KEYBOARD_ATTR = 0x01010229, + REQ_NAVIGATION_ATTR = 0x0101022a, + REQ_FIVE_WAY_NAV_ATTR = 0x01010232, + TARGET_SDK_VERSION_ATTR = 0x01010270, + TEST_ONLY_ATTR = 0x01010272, + ANY_DENSITY_ATTR = 0x0101026c, + GL_ES_VERSION_ATTR = 0x01010281, + SMALL_SCREEN_ATTR = 0x01010284, + NORMAL_SCREEN_ATTR = 0x01010285, + LARGE_SCREEN_ATTR = 0x01010286, + XLARGE_SCREEN_ATTR = 0x010102bf, + REQUIRED_ATTR = 0x0101028e, + SCREEN_SIZE_ATTR = 0x010102ca, + SCREEN_DENSITY_ATTR = 0x010102cb, + REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364, + COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365, + LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366, + PUBLIC_KEY_ATTR = 0x010103a6, +}; + +const char *getComponentName(String8 &pkgName, String8 &componentName) { + ssize_t idx = componentName.find("."); + String8 retStr(pkgName); + if (idx == 0) { + retStr += componentName; + } else if (idx < 0) { + retStr += "."; + retStr += componentName; + } else { + return componentName.string(); + } + return retStr.string(); +} + +static void printCompatibleScreens(ResXMLTree& tree) { + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + bool first = true; + printf("compatible-screens:"); + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + depth--; + if (depth < 0) { + break; + } + continue; + } + if (code != ResXMLTree::START_TAG) { + continue; + } + depth++; + String8 tag(tree.getElementName(&len)); + if (tag == "screen") { + int32_t screenSize = getIntegerAttribute(tree, + SCREEN_SIZE_ATTR, NULL, -1); + int32_t screenDensity = getIntegerAttribute(tree, + SCREEN_DENSITY_ATTR, NULL, -1); + if (screenSize > 0 && screenDensity > 0) { + if (!first) { + printf(","); + } + first = false; + printf("'%d/%d'", screenSize, screenDensity); + } + } + } + printf("\n"); +} + +/* + * Handle the "dump" command, to extract select data from an archive. + */ +extern char CONSOLE_DATA[2925]; // see EOF +int doDump(Bundle* bundle) +{ + status_t result = UNKNOWN_ERROR; + Asset* asset = NULL; + + if (bundle->getFileSpecCount() < 1) { + fprintf(stderr, "ERROR: no dump option specified\n"); + return 1; + } + + if (bundle->getFileSpecCount() < 2) { + fprintf(stderr, "ERROR: no dump file specified\n"); + return 1; + } + + const char* option = bundle->getFileSpecEntry(0); + const char* filename = bundle->getFileSpecEntry(1); + + AssetManager assets; + void* assetsCookie; + if (!assets.addAssetPath(String8(filename), &assetsCookie)) { + fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n"); + return 1; + } + + // Make a dummy config for retrieving resources... we need to supply + // non-default values for some configs so that we can retrieve resources + // in the app that don't have a default. The most important of these is + // the API version because key resources like icons will have an implicit + // version if they are using newer config types like density. + ResTable_config config; + config.language[0] = 'e'; + config.language[1] = 'n'; + config.country[0] = 'U'; + config.country[1] = 'S'; + config.orientation = ResTable_config::ORIENTATION_PORT; + config.density = ResTable_config::DENSITY_MEDIUM; + config.sdkVersion = 10000; // Very high. + config.screenWidthDp = 320; + config.screenHeightDp = 480; + config.smallestScreenWidthDp = 320; + assets.setConfiguration(config); + + const ResTable& res = assets.getResources(false); + if (&res == NULL) { + fprintf(stderr, "ERROR: dump failed because no resource table was found\n"); + goto bail; + } + + if (strcmp("resources", option) == 0) { +#ifndef HAVE_ANDROID_OS + res.print(bundle->getValues()); +#endif + + } else if (strcmp("strings", option) == 0) { + const ResStringPool* pool = res.getTableStringBlock(0); + printStringPool(pool); + + } else if (strcmp("xmltree", option) == 0) { + if (bundle->getFileSpecCount() < 3) { + fprintf(stderr, "ERROR: no dump xmltree resource file specified\n"); + goto bail; + } + + for (int i=2; i<bundle->getFileSpecCount(); i++) { + const char* resname = bundle->getFileSpecEntry(i); + ResXMLTree tree; + asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER); + if (asset == NULL) { + fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); + goto bail; + } + + if (tree.setTo(asset->getBuffer(true), + asset->getLength()) != NO_ERROR) { + fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname); + goto bail; + } + tree.restart(); + printXMLBlock(&tree); + tree.uninit(); + delete asset; + asset = NULL; + } + + } else if (strcmp("xmlstrings", option) == 0) { + if (bundle->getFileSpecCount() < 3) { + fprintf(stderr, "ERROR: no dump xmltree resource file specified\n"); + goto bail; + } + + for (int i=2; i<bundle->getFileSpecCount(); i++) { + const char* resname = bundle->getFileSpecEntry(i); + ResXMLTree tree; + asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER); + if (asset == NULL) { + fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); + goto bail; + } + + if (tree.setTo(asset->getBuffer(true), + asset->getLength()) != NO_ERROR) { + fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname); + goto bail; + } + printStringPool(&tree.getStrings()); + delete asset; + asset = NULL; + } + + } else { + ResXMLTree tree; + asset = assets.openNonAsset("AndroidManifest.xml", + Asset::ACCESS_BUFFER); + if (asset == NULL) { + fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n"); + goto bail; + } + + if (tree.setTo(asset->getBuffer(true), + asset->getLength()) != NO_ERROR) { + fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n"); + goto bail; + } + tree.restart(); + + if (strcmp("permissions", option) == 0) { + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + depth--; + continue; + } + if (code != ResXMLTree::START_TAG) { + continue; + } + depth++; + String8 tag(tree.getElementName(&len)); + //printf("Depth %d tag %s\n", depth, tag.string()); + if (depth == 1) { + if (tag != "manifest") { + fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); + goto bail; + } + String8 pkg = getAttribute(tree, NULL, "package", NULL); + printf("package: %s\n", pkg.string()); + } else if (depth == 2 && tag == "permission") { + String8 error; + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + goto bail; + } + printf("permission: %s\n", name.string()); + } else if (depth == 2 && tag == "uses-permission") { + String8 error; + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + goto bail; + } + printf("uses-permission: %s\n", name.string()); + int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); + if (!req) { + printf("optional-permission: %s\n", name.string()); + } + } + } + } else if (strcmp("badging", option) == 0) { + Vector<String8> locales; + res.getLocales(&locales); + + Vector<ResTable_config> configs; + res.getConfigurations(&configs); + SortedVector<int> densities; + const size_t NC = configs.size(); + for (size_t i=0; i<NC; i++) { + int dens = configs[i].density; + if (dens == 0) { + dens = 160; + } + densities.add(dens); + } + + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + String8 error; + bool withinActivity = false; + bool isMainActivity = false; + bool isLauncherActivity = false; + bool isSearchable = false; + bool withinApplication = false; + bool withinReceiver = false; + bool withinService = false; + bool withinIntentFilter = false; + bool hasMainActivity = false; + bool hasOtherActivities = false; + bool hasOtherReceivers = false; + bool hasOtherServices = false; + bool hasWallpaperService = false; + bool hasImeService = false; + bool hasWidgetReceivers = false; + bool hasIntentFilter = false; + bool actMainActivity = false; + bool actWidgetReceivers = false; + bool actImeService = false; + bool actWallpaperService = false; + + // These two implement the implicit permissions that are granted + // to pre-1.6 applications. + bool hasWriteExternalStoragePermission = false; + bool hasReadPhoneStatePermission = false; + + // If an app requests write storage, they will also get read storage. + bool hasReadExternalStoragePermission = false; + + // Implement transition to read and write call log. + bool hasReadContactsPermission = false; + bool hasWriteContactsPermission = false; + bool hasReadCallLogPermission = false; + bool hasWriteCallLogPermission = false; + + // This next group of variables is used to implement a group of + // backward-compatibility heuristics necessitated by the addition of + // some new uses-feature constants in 2.1 and 2.2. In most cases, the + // heuristic is "if an app requests a permission but doesn't explicitly + // request the corresponding <uses-feature>, presume it's there anyway". + bool specCameraFeature = false; // camera-related + bool specCameraAutofocusFeature = false; + bool reqCameraAutofocusFeature = false; + bool reqCameraFlashFeature = false; + bool hasCameraPermission = false; + bool specLocationFeature = false; // location-related + bool specNetworkLocFeature = false; + bool reqNetworkLocFeature = false; + bool specGpsFeature = false; + bool reqGpsFeature = false; + bool hasMockLocPermission = false; + bool hasCoarseLocPermission = false; + bool hasGpsPermission = false; + bool hasGeneralLocPermission = false; + bool specBluetoothFeature = false; // Bluetooth API-related + bool hasBluetoothPermission = false; + bool specMicrophoneFeature = false; // microphone-related + bool hasRecordAudioPermission = false; + bool specWiFiFeature = false; + bool hasWiFiPermission = false; + bool specTelephonyFeature = false; // telephony-related + bool reqTelephonySubFeature = false; + bool hasTelephonyPermission = false; + bool specTouchscreenFeature = false; // touchscreen-related + bool specMultitouchFeature = false; + bool reqDistinctMultitouchFeature = false; + bool specScreenPortraitFeature = false; + bool specScreenLandscapeFeature = false; + bool reqScreenPortraitFeature = false; + bool reqScreenLandscapeFeature = false; + // 2.2 also added some other features that apps can request, but that + // have no corresponding permission, so we cannot implement any + // back-compatibility heuristic for them. The below are thus unnecessary + // (but are retained here for documentary purposes.) + //bool specCompassFeature = false; + //bool specAccelerometerFeature = false; + //bool specProximityFeature = false; + //bool specAmbientLightFeature = false; + //bool specLiveWallpaperFeature = false; + + int targetSdk = 0; + int smallScreen = 1; + int normalScreen = 1; + int largeScreen = 1; + int xlargeScreen = 1; + int anyDensity = 1; + int requiresSmallestWidthDp = 0; + int compatibleWidthLimitDp = 0; + int largestWidthLimitDp = 0; + String8 pkg; + String8 activityName; + String8 activityLabel; + String8 activityIcon; + String8 receiverName; + String8 serviceName; + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + depth--; + if (depth < 2) { + withinApplication = false; + } else if (depth < 3) { + if (withinActivity && isMainActivity && isLauncherActivity) { + const char *aName = getComponentName(pkg, activityName); + printf("launchable-activity:"); + if (aName != NULL) { + printf(" name='%s' ", aName); + } + printf(" label='%s' icon='%s'\n", + activityLabel.string(), + activityIcon.string()); + } + if (!hasIntentFilter) { + hasOtherActivities |= withinActivity; + hasOtherReceivers |= withinReceiver; + hasOtherServices |= withinService; + } + withinActivity = false; + withinService = false; + withinReceiver = false; + hasIntentFilter = false; + isMainActivity = isLauncherActivity = false; + } else if (depth < 4) { + if (withinIntentFilter) { + if (withinActivity) { + hasMainActivity |= actMainActivity; + hasOtherActivities |= !actMainActivity; + } else if (withinReceiver) { + hasWidgetReceivers |= actWidgetReceivers; + hasOtherReceivers |= !actWidgetReceivers; + } else if (withinService) { + hasImeService |= actImeService; + hasWallpaperService |= actWallpaperService; + hasOtherServices |= (!actImeService && !actWallpaperService); + } + } + withinIntentFilter = false; + } + continue; + } + if (code != ResXMLTree::START_TAG) { + continue; + } + depth++; + String8 tag(tree.getElementName(&len)); + //printf("Depth %d, %s\n", depth, tag.string()); + if (depth == 1) { + if (tag != "manifest") { + fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); + goto bail; + } + pkg = getAttribute(tree, NULL, "package", NULL); + printf("package: name='%s' ", pkg.string()); + int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string()); + goto bail; + } + if (versionCode > 0) { + printf("versionCode='%d' ", versionCode); + } else { + printf("versionCode='' "); + } + String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string()); + goto bail; + } + printf("versionName='%s'\n", versionName.string()); + } else if (depth == 2) { + withinApplication = false; + if (tag == "application") { + withinApplication = true; + + String8 label; + const size_t NL = locales.size(); + for (size_t i=0; i<NL; i++) { + const char* localeStr = locales[i].string(); + assets.setLocale(localeStr != NULL ? localeStr : ""); + String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + if (llabel != "") { + if (localeStr == NULL || strlen(localeStr) == 0) { + label = llabel; + printf("application-label:'%s'\n", llabel.string()); + } else { + if (label == "") { + label = llabel; + } + printf("application-label-%s:'%s'\n", localeStr, + llabel.string()); + } + } + } + + ResTable_config tmpConfig = config; + const size_t ND = densities.size(); + for (size_t i=0; i<ND; i++) { + tmpConfig.density = densities[i]; + assets.setConfiguration(tmpConfig); + String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + if (icon != "") { + printf("application-icon-%d:'%s'\n", densities[i], icon.string()); + } + } + assets.setConfiguration(config); + + String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); + goto bail; + } + int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string()); + goto bail; + } + printf("application: label='%s' ", label.string()); + printf("icon='%s'\n", icon.string()); + if (testOnly != 0) { + printf("testOnly='%d'\n", testOnly); + } + + int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string()); + goto bail; + } + if (debuggable != 0) { + printf("application-debuggable\n"); + } + } else if (tag == "uses-sdk") { + int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); + if (error != "") { + error = ""; + String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n", + error.string()); + goto bail; + } + if (name == "Donut") targetSdk = 4; + printf("sdkVersion:'%s'\n", name.string()); + } else if (code != -1) { + targetSdk = code; + printf("sdkVersion:'%d'\n", code); + } + code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1); + if (code != -1) { + printf("maxSdkVersion:'%d'\n", code); + } + code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); + if (error != "") { + error = ""; + String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n", + error.string()); + goto bail; + } + if (name == "Donut" && targetSdk < 4) targetSdk = 4; + printf("targetSdkVersion:'%s'\n", name.string()); + } else if (code != -1) { + if (targetSdk < code) { + targetSdk = code; + } + printf("targetSdkVersion:'%d'\n", code); + } + } else if (tag == "uses-configuration") { + int32_t reqTouchScreen = getIntegerAttribute(tree, + REQ_TOUCH_SCREEN_ATTR, NULL, 0); + int32_t reqKeyboardType = getIntegerAttribute(tree, + REQ_KEYBOARD_TYPE_ATTR, NULL, 0); + int32_t reqHardKeyboard = getIntegerAttribute(tree, + REQ_HARD_KEYBOARD_ATTR, NULL, 0); + int32_t reqNavigation = getIntegerAttribute(tree, + REQ_NAVIGATION_ATTR, NULL, 0); + int32_t reqFiveWayNav = getIntegerAttribute(tree, + REQ_FIVE_WAY_NAV_ATTR, NULL, 0); + printf("uses-configuration:"); + if (reqTouchScreen != 0) { + printf(" reqTouchScreen='%d'", reqTouchScreen); + } + if (reqKeyboardType != 0) { + printf(" reqKeyboardType='%d'", reqKeyboardType); + } + if (reqHardKeyboard != 0) { + printf(" reqHardKeyboard='%d'", reqHardKeyboard); + } + if (reqNavigation != 0) { + printf(" reqNavigation='%d'", reqNavigation); + } + if (reqFiveWayNav != 0) { + printf(" reqFiveWayNav='%d'", reqFiveWayNav); + } + printf("\n"); + } else if (tag == "supports-screens") { + smallScreen = getIntegerAttribute(tree, + SMALL_SCREEN_ATTR, NULL, 1); + normalScreen = getIntegerAttribute(tree, + NORMAL_SCREEN_ATTR, NULL, 1); + largeScreen = getIntegerAttribute(tree, + LARGE_SCREEN_ATTR, NULL, 1); + xlargeScreen = getIntegerAttribute(tree, + XLARGE_SCREEN_ATTR, NULL, 1); + anyDensity = getIntegerAttribute(tree, + ANY_DENSITY_ATTR, NULL, 1); + requiresSmallestWidthDp = getIntegerAttribute(tree, + REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0); + compatibleWidthLimitDp = getIntegerAttribute(tree, + COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0); + largestWidthLimitDp = getIntegerAttribute(tree, + LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0); + } else if (tag == "uses-feature") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + + if (name != "" && error == "") { + int req = getIntegerAttribute(tree, + REQUIRED_ATTR, NULL, 1); + + if (name == "android.hardware.camera") { + specCameraFeature = true; + } else if (name == "android.hardware.camera.autofocus") { + // these have no corresponding permission to check for, + // but should imply the foundational camera permission + reqCameraAutofocusFeature = reqCameraAutofocusFeature || req; + specCameraAutofocusFeature = true; + } else if (req && (name == "android.hardware.camera.flash")) { + // these have no corresponding permission to check for, + // but should imply the foundational camera permission + reqCameraFlashFeature = true; + } else if (name == "android.hardware.location") { + specLocationFeature = true; + } else if (name == "android.hardware.location.network") { + specNetworkLocFeature = true; + reqNetworkLocFeature = reqNetworkLocFeature || req; + } else if (name == "android.hardware.location.gps") { + specGpsFeature = true; + reqGpsFeature = reqGpsFeature || req; + } else if (name == "android.hardware.bluetooth") { + specBluetoothFeature = true; + } else if (name == "android.hardware.touchscreen") { + specTouchscreenFeature = true; + } else if (name == "android.hardware.touchscreen.multitouch") { + specMultitouchFeature = true; + } else if (name == "android.hardware.touchscreen.multitouch.distinct") { + reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req; + } else if (name == "android.hardware.microphone") { + specMicrophoneFeature = true; + } else if (name == "android.hardware.wifi") { + specWiFiFeature = true; + } else if (name == "android.hardware.telephony") { + specTelephonyFeature = true; + } else if (req && (name == "android.hardware.telephony.gsm" || + name == "android.hardware.telephony.cdma")) { + // these have no corresponding permission to check for, + // but should imply the foundational telephony permission + reqTelephonySubFeature = true; + } else if (name == "android.hardware.screen.portrait") { + specScreenPortraitFeature = true; + } else if (name == "android.hardware.screen.landscape") { + specScreenLandscapeFeature = true; + } + printf("uses-feature%s:'%s'\n", + req ? "" : "-not-required", name.string()); + } else { + int vers = getIntegerAttribute(tree, + GL_ES_VERSION_ATTR, &error); + if (error == "") { + printf("uses-gl-es:'0x%x'\n", vers); + } + } + } else if (tag == "uses-permission") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + if (name == "android.permission.CAMERA") { + hasCameraPermission = true; + } else if (name == "android.permission.ACCESS_FINE_LOCATION") { + hasGpsPermission = true; + } else if (name == "android.permission.ACCESS_MOCK_LOCATION") { + hasMockLocPermission = true; + } else if (name == "android.permission.ACCESS_COARSE_LOCATION") { + hasCoarseLocPermission = true; + } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" || + name == "android.permission.INSTALL_LOCATION_PROVIDER") { + hasGeneralLocPermission = true; + } else if (name == "android.permission.BLUETOOTH" || + name == "android.permission.BLUETOOTH_ADMIN") { + hasBluetoothPermission = true; + } else if (name == "android.permission.RECORD_AUDIO") { + hasRecordAudioPermission = true; + } else if (name == "android.permission.ACCESS_WIFI_STATE" || + name == "android.permission.CHANGE_WIFI_STATE" || + name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") { + hasWiFiPermission = true; + } else if (name == "android.permission.CALL_PHONE" || + name == "android.permission.CALL_PRIVILEGED" || + name == "android.permission.MODIFY_PHONE_STATE" || + name == "android.permission.PROCESS_OUTGOING_CALLS" || + name == "android.permission.READ_SMS" || + name == "android.permission.RECEIVE_SMS" || + name == "android.permission.RECEIVE_MMS" || + name == "android.permission.RECEIVE_WAP_PUSH" || + name == "android.permission.SEND_SMS" || + name == "android.permission.WRITE_APN_SETTINGS" || + name == "android.permission.WRITE_SMS") { + hasTelephonyPermission = true; + } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") { + hasWriteExternalStoragePermission = true; + } else if (name == "android.permission.READ_EXTERNAL_STORAGE") { + hasReadExternalStoragePermission = true; + } else if (name == "android.permission.READ_PHONE_STATE") { + hasReadPhoneStatePermission = true; + } else if (name == "android.permission.READ_CONTACTS") { + hasReadContactsPermission = true; + } else if (name == "android.permission.WRITE_CONTACTS") { + hasWriteContactsPermission = true; + } else if (name == "android.permission.READ_CALL_LOG") { + hasReadCallLogPermission = true; + } else if (name == "android.permission.WRITE_CALL_LOG") { + hasWriteCallLogPermission = true; + } + printf("uses-permission:'%s'\n", name.string()); + int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); + if (!req) { + printf("optional-permission:'%s'\n", name.string()); + } + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + } else if (tag == "uses-package") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + printf("uses-package:'%s'\n", name.string()); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + } else if (tag == "original-package") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + printf("original-package:'%s'\n", name.string()); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + } else if (tag == "supports-gl-texture") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + printf("supports-gl-texture:'%s'\n", name.string()); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + } else if (tag == "compatible-screens") { + printCompatibleScreens(tree); + depth--; + } else if (tag == "package-verifier") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error); + if (publicKey != "" && error == "") { + printf("package-verifier: name='%s' publicKey='%s'\n", + name.string(), publicKey.string()); + } + } + } + } else if (depth == 3 && withinApplication) { + withinActivity = false; + withinReceiver = false; + withinService = false; + hasIntentFilter = false; + if(tag == "activity") { + withinActivity = true; + activityName = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); + goto bail; + } + + activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string()); + goto bail; + } + + activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); + goto bail; + } + + int32_t orien = getResolvedIntegerAttribute(&res, tree, + SCREEN_ORIENTATION_ATTR, &error); + if (error == "") { + if (orien == 0 || orien == 6 || orien == 8) { + // Requests landscape, sensorLandscape, or reverseLandscape. + reqScreenLandscapeFeature = true; + } else if (orien == 1 || orien == 7 || orien == 9) { + // Requests portrait, sensorPortrait, or reversePortrait. + reqScreenPortraitFeature = true; + } + } + } else if (tag == "uses-library") { + String8 libraryName = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for uses-library: %s\n", error.string()); + goto bail; + } + int req = getIntegerAttribute(tree, + REQUIRED_ATTR, NULL, 1); + printf("uses-library%s:'%s'\n", + req ? "" : "-not-required", libraryName.string()); + } else if (tag == "receiver") { + withinReceiver = true; + receiverName = getAttribute(tree, NAME_ATTR, &error); + + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "receiver:%s\n", error.string()); + goto bail; + } + } else if (tag == "service") { + withinService = true; + serviceName = getAttribute(tree, NAME_ATTR, &error); + + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "service:%s\n", error.string()); + goto bail; + } + } else if (bundle->getIncludeMetaData() && tag == "meta-data") { + String8 metaDataName = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "meta-data:%s\n", error.string()); + goto bail; + } + printf("meta-data: name='%s' ", metaDataName.string()); + Res_value value; + getResolvedResourceAttribute(&value, &res, tree, VALUE_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:value' attribute for " + "meta-data:%s\n", error.string()); + goto bail; + } + if (value.dataType == Res_value::TYPE_STRING) { + String8 metaDataValue = getAttribute(tree, value.data, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:value' attribute for " + "meta-data: %s\n", error.string()); + goto bail; + } + printf("value='%s'\n", metaDataValue.string()); + } else if (Res_value::TYPE_FIRST_INT <= value.dataType && + value.dataType <= Res_value::TYPE_LAST_INT) { + printf("value='%d'\n", value.data); + } else { + printf("value=(type 0x%x)0x%x", (int)value.dataType, (int)value.data); + } + } + } else if ((depth == 4) && (tag == "intent-filter")) { + hasIntentFilter = true; + withinIntentFilter = true; + actMainActivity = actWidgetReceivers = actImeService = actWallpaperService = + false; + } else if ((depth == 5) && withinIntentFilter) { + String8 action; + if (tag == "action") { + action = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } + if (withinActivity) { + if (action == "android.intent.action.MAIN") { + isMainActivity = true; + actMainActivity = true; + } + } else if (withinReceiver) { + if (action == "android.appwidget.action.APPWIDGET_UPDATE") { + actWidgetReceivers = true; + } + } else if (withinService) { + if (action == "android.view.InputMethod") { + actImeService = true; + } else if (action == "android.service.wallpaper.WallpaperService") { + actWallpaperService = true; + } + } + if (action == "android.intent.action.SEARCH") { + isSearchable = true; + } + } + + if (tag == "category") { + String8 category = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string()); + goto bail; + } + if (withinActivity) { + if (category == "android.intent.category.LAUNCHER") { + isLauncherActivity = true; + } + } + } + } + } + + // Pre-1.6 implicitly granted permission compatibility logic + if (targetSdk < 4) { + if (!hasWriteExternalStoragePermission) { + printf("uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE'\n"); + printf("uses-implied-permission:'android.permission.WRITE_EXTERNAL_STORAGE'," \ + "'targetSdkVersion < 4'\n"); + hasWriteExternalStoragePermission = true; + } + if (!hasReadPhoneStatePermission) { + printf("uses-permission:'android.permission.READ_PHONE_STATE'\n"); + printf("uses-implied-permission:'android.permission.READ_PHONE_STATE'," \ + "'targetSdkVersion < 4'\n"); + } + } + + // If the application has requested WRITE_EXTERNAL_STORAGE, we will + // force them to always take READ_EXTERNAL_STORAGE as well. We always + // do this (regardless of target API version) because we can't have + // an app with write permission but not read permission. + if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) { + printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n"); + printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \ + "'requested WRITE_EXTERNAL_STORAGE'\n"); + } + + // Pre-JellyBean call log permission compatibility. + if (targetSdk < 16) { + if (!hasReadCallLogPermission && hasReadContactsPermission) { + printf("uses-permission:'android.permission.READ_CALL_LOG'\n"); + printf("uses-implied-permission:'android.permission.READ_CALL_LOG'," \ + "'targetSdkVersion < 16 and requested READ_CONTACTS'\n"); + } + if (!hasWriteCallLogPermission && hasWriteContactsPermission) { + printf("uses-permission:'android.permission.WRITE_CALL_LOG'\n"); + printf("uses-implied-permission:'android.permission.WRITE_CALL_LOG'," \ + "'targetSdkVersion < 16 and requested WRITE_CONTACTS'\n"); + } + } + + /* The following blocks handle printing "inferred" uses-features, based + * on whether related features or permissions are used by the app. + * Note that the various spec*Feature variables denote whether the + * relevant tag was *present* in the AndroidManfest, not that it was + * present and set to true. + */ + // Camera-related back-compatibility logic + if (!specCameraFeature) { + if (reqCameraFlashFeature) { + // if app requested a sub-feature (autofocus or flash) and didn't + // request the base camera feature, we infer that it meant to + printf("uses-feature:'android.hardware.camera'\n"); + printf("uses-implied-feature:'android.hardware.camera'," \ + "'requested android.hardware.camera.flash feature'\n"); + } else if (reqCameraAutofocusFeature) { + // if app requested a sub-feature (autofocus or flash) and didn't + // request the base camera feature, we infer that it meant to + printf("uses-feature:'android.hardware.camera'\n"); + printf("uses-implied-feature:'android.hardware.camera'," \ + "'requested android.hardware.camera.autofocus feature'\n"); + } else if (hasCameraPermission) { + // if app wants to use camera but didn't request the feature, we infer + // that it meant to, and further that it wants autofocus + // (which was the 1.0 - 1.5 behavior) + printf("uses-feature:'android.hardware.camera'\n"); + if (!specCameraAutofocusFeature) { + printf("uses-feature:'android.hardware.camera.autofocus'\n"); + printf("uses-implied-feature:'android.hardware.camera.autofocus'," \ + "'requested android.permission.CAMERA permission'\n"); + } + } + } + + // Location-related back-compatibility logic + if (!specLocationFeature && + (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission || + hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) { + // if app either takes a location-related permission or requests one of the + // sub-features, we infer that it also meant to request the base location feature + printf("uses-feature:'android.hardware.location'\n"); + printf("uses-implied-feature:'android.hardware.location'," \ + "'requested a location access permission'\n"); + } + if (!specGpsFeature && hasGpsPermission) { + // if app takes GPS (FINE location) perm but does not request the GPS + // feature, we infer that it meant to + printf("uses-feature:'android.hardware.location.gps'\n"); + printf("uses-implied-feature:'android.hardware.location.gps'," \ + "'requested android.permission.ACCESS_FINE_LOCATION permission'\n"); + } + if (!specNetworkLocFeature && hasCoarseLocPermission) { + // if app takes Network location (COARSE location) perm but does not request the + // network location feature, we infer that it meant to + printf("uses-feature:'android.hardware.location.network'\n"); + printf("uses-implied-feature:'android.hardware.location.network'," \ + "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n"); + } + + // Bluetooth-related compatibility logic + if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) { + // if app takes a Bluetooth permission but does not request the Bluetooth + // feature, we infer that it meant to + printf("uses-feature:'android.hardware.bluetooth'\n"); + printf("uses-implied-feature:'android.hardware.bluetooth'," \ + "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \ + "permission and targetSdkVersion > 4'\n"); + } + + // Microphone-related compatibility logic + if (!specMicrophoneFeature && hasRecordAudioPermission) { + // if app takes the record-audio permission but does not request the microphone + // feature, we infer that it meant to + printf("uses-feature:'android.hardware.microphone'\n"); + printf("uses-implied-feature:'android.hardware.microphone'," \ + "'requested android.permission.RECORD_AUDIO permission'\n"); + } + + // WiFi-related compatibility logic + if (!specWiFiFeature && hasWiFiPermission) { + // if app takes one of the WiFi permissions but does not request the WiFi + // feature, we infer that it meant to + printf("uses-feature:'android.hardware.wifi'\n"); + printf("uses-implied-feature:'android.hardware.wifi'," \ + "'requested android.permission.ACCESS_WIFI_STATE, " \ + "android.permission.CHANGE_WIFI_STATE, or " \ + "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n"); + } + + // Telephony-related compatibility logic + if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) { + // if app takes one of the telephony permissions or requests a sub-feature but + // does not request the base telephony feature, we infer that it meant to + printf("uses-feature:'android.hardware.telephony'\n"); + printf("uses-implied-feature:'android.hardware.telephony'," \ + "'requested a telephony-related permission or feature'\n"); + } + + // Touchscreen-related back-compatibility logic + if (!specTouchscreenFeature) { // not a typo! + // all apps are presumed to require a touchscreen, unless they explicitly say + // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> + // Note that specTouchscreenFeature is true if the tag is present, regardless + // of whether its value is true or false, so this is safe + printf("uses-feature:'android.hardware.touchscreen'\n"); + printf("uses-implied-feature:'android.hardware.touchscreen'," \ + "'assumed you require a touch screen unless explicitly made optional'\n"); + } + if (!specMultitouchFeature && reqDistinctMultitouchFeature) { + // if app takes one of the telephony permissions or requests a sub-feature but + // does not request the base telephony feature, we infer that it meant to + printf("uses-feature:'android.hardware.touchscreen.multitouch'\n"); + printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \ + "'requested android.hardware.touchscreen.multitouch.distinct feature'\n"); + } + + // Landscape/portrait-related compatibility logic + if (!specScreenLandscapeFeature && !specScreenPortraitFeature) { + // If the app has specified any activities in its manifest + // that request a specific orientation, then assume that + // orientation is required. + if (reqScreenLandscapeFeature) { + printf("uses-feature:'android.hardware.screen.landscape'\n"); + printf("uses-implied-feature:'android.hardware.screen.landscape'," \ + "'one or more activities have specified a landscape orientation'\n"); + } + if (reqScreenPortraitFeature) { + printf("uses-feature:'android.hardware.screen.portrait'\n"); + printf("uses-implied-feature:'android.hardware.screen.portrait'," \ + "'one or more activities have specified a portrait orientation'\n"); + } + } + + if (hasMainActivity) { + printf("main\n"); + } + if (hasWidgetReceivers) { + printf("app-widget\n"); + } + if (hasImeService) { + printf("ime\n"); + } + if (hasWallpaperService) { + printf("wallpaper\n"); + } + if (hasOtherActivities) { + printf("other-activities\n"); + } + if (isSearchable) { + printf("search\n"); + } + if (hasOtherReceivers) { + printf("other-receivers\n"); + } + if (hasOtherServices) { + printf("other-services\n"); + } + + // For modern apps, if screen size buckets haven't been specified + // but the new width ranges have, then infer the buckets from them. + if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0 + && requiresSmallestWidthDp > 0) { + int compatWidth = compatibleWidthLimitDp; + if (compatWidth <= 0) { + compatWidth = requiresSmallestWidthDp; + } + if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) { + smallScreen = -1; + } else { + smallScreen = 0; + } + if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) { + normalScreen = -1; + } else { + normalScreen = 0; + } + if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) { + largeScreen = -1; + } else { + largeScreen = 0; + } + if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) { + xlargeScreen = -1; + } else { + xlargeScreen = 0; + } + } + + // Determine default values for any unspecified screen sizes, + // based on the target SDK of the package. As of 4 (donut) + // the screen size support was introduced, so all default to + // enabled. + if (smallScreen > 0) { + smallScreen = targetSdk >= 4 ? -1 : 0; + } + if (normalScreen > 0) { + normalScreen = -1; + } + if (largeScreen > 0) { + largeScreen = targetSdk >= 4 ? -1 : 0; + } + if (xlargeScreen > 0) { + // Introduced in Gingerbread. + xlargeScreen = targetSdk >= 9 ? -1 : 0; + } + if (anyDensity > 0) { + anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0 + || compatibleWidthLimitDp > 0) ? -1 : 0; + } + printf("supports-screens:"); + if (smallScreen != 0) { + printf(" 'small'"); + } + if (normalScreen != 0) { + printf(" 'normal'"); + } + if (largeScreen != 0) { + printf(" 'large'"); + } + if (xlargeScreen != 0) { + printf(" 'xlarge'"); + } + printf("\n"); + printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false"); + if (requiresSmallestWidthDp > 0) { + printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp); + } + if (compatibleWidthLimitDp > 0) { + printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp); + } + if (largestWidthLimitDp > 0) { + printf("largest-width-limit:'%d'\n", largestWidthLimitDp); + } + + printf("locales:"); + const size_t NL = locales.size(); + for (size_t i=0; i<NL; i++) { + const char* localeStr = locales[i].string(); + if (localeStr == NULL || strlen(localeStr) == 0) { + localeStr = "--_--"; + } + printf(" '%s'", localeStr); + } + printf("\n"); + + printf("densities:"); + const size_t ND = densities.size(); + for (size_t i=0; i<ND; i++) { + printf(" '%d'", densities[i]); + } + printf("\n"); + + AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib"); + if (dir != NULL) { + if (dir->getFileCount() > 0) { + printf("native-code:"); + for (size_t i=0; i<dir->getFileCount(); i++) { + printf(" '%s'", dir->getFileName(i).string()); + } + printf("\n"); + } + delete dir; + } + } else if (strcmp("badger", option) == 0) { + printf("%s", CONSOLE_DATA); + } else if (strcmp("configurations", option) == 0) { + Vector<ResTable_config> configs; + res.getConfigurations(&configs); + const size_t N = configs.size(); + for (size_t i=0; i<N; i++) { + printf("%s\n", configs[i].toString().string()); + } + } else { + fprintf(stderr, "ERROR: unknown dump option '%s'\n", option); + goto bail; + } + } + + result = NO_ERROR; + +bail: + if (asset) { + delete asset; + } + return (result != NO_ERROR); +} + + +/* + * Handle the "add" command, which wants to add files to a new or + * pre-existing archive. + */ +int doAdd(Bundle* bundle) +{ + ZipFile* zip = NULL; + status_t result = UNKNOWN_ERROR; + const char* zipFileName; + + if (bundle->getUpdate()) { + /* avoid confusion */ + fprintf(stderr, "ERROR: can't use '-u' with add\n"); + goto bail; + } + + if (bundle->getFileSpecCount() < 1) { + fprintf(stderr, "ERROR: must specify zip file name\n"); + goto bail; + } + zipFileName = bundle->getFileSpecEntry(0); + + if (bundle->getFileSpecCount() < 2) { + fprintf(stderr, "NOTE: nothing to do\n"); + goto bail; + } + + zip = openReadWrite(zipFileName, true); + if (zip == NULL) { + fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName); + goto bail; + } + + for (int i = 1; i < bundle->getFileSpecCount(); i++) { + const char* fileName = bundle->getFileSpecEntry(i); + + if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) { + printf(" '%s'... (from gzip)\n", fileName); + result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL); + } else { + if (bundle->getJunkPath()) { + String8 storageName = String8(fileName).getPathLeaf(); + printf(" '%s' as '%s'...\n", fileName, storageName.string()); + result = zip->add(fileName, storageName.string(), + bundle->getCompressionMethod(), NULL); + } else { + printf(" '%s'...\n", fileName); + result = zip->add(fileName, bundle->getCompressionMethod(), NULL); + } + } + if (result != NO_ERROR) { + fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName); + if (result == NAME_NOT_FOUND) { + fprintf(stderr, ": file not found\n"); + } else if (result == ALREADY_EXISTS) { + fprintf(stderr, ": already exists in archive\n"); + } else { + fprintf(stderr, "\n"); + } + goto bail; + } + } + + result = NO_ERROR; + +bail: + delete zip; + return (result != NO_ERROR); +} + + +/* + * Delete files from an existing archive. + */ +int doRemove(Bundle* bundle) +{ + ZipFile* zip = NULL; + status_t result = UNKNOWN_ERROR; + const char* zipFileName; + + if (bundle->getFileSpecCount() < 1) { + fprintf(stderr, "ERROR: must specify zip file name\n"); + goto bail; + } + zipFileName = bundle->getFileSpecEntry(0); + + if (bundle->getFileSpecCount() < 2) { + fprintf(stderr, "NOTE: nothing to do\n"); + goto bail; + } + + zip = openReadWrite(zipFileName, false); + if (zip == NULL) { + fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n", + zipFileName); + goto bail; + } + + for (int i = 1; i < bundle->getFileSpecCount(); i++) { + const char* fileName = bundle->getFileSpecEntry(i); + ZipEntry* entry; + + entry = zip->getEntryByName(fileName); + if (entry == NULL) { + printf(" '%s' NOT FOUND\n", fileName); + continue; + } + + result = zip->remove(entry); + + if (result != NO_ERROR) { + fprintf(stderr, "Unable to delete '%s' from '%s'\n", + bundle->getFileSpecEntry(i), zipFileName); + goto bail; + } + } + + /* update the archive */ + zip->flush(); + +bail: + delete zip; + return (result != NO_ERROR); +} + + +/* + * Package up an asset directory and associated application files. + */ +int doPackage(Bundle* bundle) +{ + const char* outputAPKFile; + int retVal = 1; + status_t err; + sp<AaptAssets> assets; + int N; + FILE* fp; + String8 dependencyFile; + + // -c zz_ZZ means do pseudolocalization + ResourceFilter filter; + err = filter.parse(bundle->getConfigurations()); + if (err != NO_ERROR) { + goto bail; + } + if (filter.containsPseudo()) { + bundle->setPseudolocalize(true); + } + + N = bundle->getFileSpecCount(); + if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0 + && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) { + fprintf(stderr, "ERROR: no input files\n"); + goto bail; + } + + outputAPKFile = bundle->getOutputAPKFile(); + + // Make sure the filenames provided exist and are of the appropriate type. + if (outputAPKFile) { + FileType type; + type = getFileType(outputAPKFile); + if (type != kFileTypeNonexistent && type != kFileTypeRegular) { + fprintf(stderr, + "ERROR: output file '%s' exists but is not regular file\n", + outputAPKFile); + goto bail; + } + } + + // Load the assets. + assets = new AaptAssets(); + + // Set up the resource gathering in assets if we're going to generate + // dependency files. Every time we encounter a resource while slurping + // the tree, we'll add it to these stores so we have full resource paths + // to write to a dependency file. + if (bundle->getGenDependencies()) { + sp<FilePathStore> resPathStore = new FilePathStore; + assets->setFullResPaths(resPathStore); + sp<FilePathStore> assetPathStore = new FilePathStore; + assets->setFullAssetPaths(assetPathStore); + } + + err = assets->slurpFromArgs(bundle); + if (err < 0) { + goto bail; + } + + if (bundle->getVerbose()) { + assets->print(String8()); + } + + // If they asked for any fileAs that need to be compiled, do so. + if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) { + err = buildResources(bundle, assets); + if (err != 0) { + goto bail; + } + } + + // At this point we've read everything and processed everything. From here + // on out it's just writing output files. + if (SourcePos::hasErrors()) { + goto bail; + } + + // Update symbols with information about which ones are needed as Java symbols. + assets->applyJavaSymbols(); + if (SourcePos::hasErrors()) { + goto bail; + } + + // If we've been asked to generate a dependency file, do that here + if (bundle->getGenDependencies()) { + // If this is the packaging step, generate the dependency file next to + // the output apk (e.g. bin/resources.ap_.d) + if (outputAPKFile) { + dependencyFile = String8(outputAPKFile); + // Add the .d extension to the dependency file. + dependencyFile.append(".d"); + } else { + // Else if this is the R.java dependency generation step, + // generate the dependency file in the R.java package subdirectory + // e.g. gen/com/foo/app/R.java.d + dependencyFile = String8(bundle->getRClassDir()); + dependencyFile.appendPath("R.java.d"); + } + // Make sure we have a clean dependency file to start with + fp = fopen(dependencyFile, "w"); + fclose(fp); + } + + // Write out R.java constants + if (!assets->havePrivateSymbols()) { + if (bundle->getCustomPackage() == NULL) { + // Write the R.java file into the appropriate class directory + // e.g. gen/com/foo/app/R.java + err = writeResourceSymbols(bundle, assets, assets->getPackage(), true); + } else { + const String8 customPkg(bundle->getCustomPackage()); + err = writeResourceSymbols(bundle, assets, customPkg, true); + } + if (err < 0) { + goto bail; + } + // If we have library files, we're going to write our R.java file into + // the appropriate class directory for those libraries as well. + // e.g. gen/com/foo/app/lib/R.java + if (bundle->getExtraPackages() != NULL) { + // Split on colon + String8 libs(bundle->getExtraPackages()); + char* packageString = strtok(libs.lockBuffer(libs.length()), ":"); + while (packageString != NULL) { + // Write the R.java file out with the correct package name + err = writeResourceSymbols(bundle, assets, String8(packageString), true); + if (err < 0) { + goto bail; + } + packageString = strtok(NULL, ":"); + } + libs.unlockBuffer(); + } + } else { + err = writeResourceSymbols(bundle, assets, assets->getPackage(), false); + if (err < 0) { + goto bail; + } + err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true); + if (err < 0) { + goto bail; + } + } + + // Write out the ProGuard file + err = writeProguardFile(bundle, assets); + if (err < 0) { + goto bail; + } + + // Write the apk + if (outputAPKFile) { + err = writeAPK(bundle, assets, String8(outputAPKFile)); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile); + goto bail; + } + } + + // If we've been asked to generate a dependency file, we need to finish up here. + // the writeResourceSymbols and writeAPK functions have already written the target + // half of the dependency file, now we need to write the prerequisites. (files that + // the R.java file or .ap_ file depend on) + if (bundle->getGenDependencies()) { + // Now that writeResourceSymbols or writeAPK has taken care of writing + // the targets to our dependency file, we'll write the prereqs + fp = fopen(dependencyFile, "a+"); + fprintf(fp, " : "); + bool includeRaw = (outputAPKFile != NULL); + err = writeDependencyPreReqs(bundle, assets, fp, includeRaw); + // Also manually add the AndroidManifeset since it's not under res/ or assets/ + // and therefore was not added to our pathstores during slurping + fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile()); + fclose(fp); + } + + retVal = 0; +bail: + if (SourcePos::hasErrors()) { + SourcePos::printErrors(stderr); + } + 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; +} + +/* + * Do PNG Crunching on a single flag + * -i points to a single png file + * -o points to a single png output file + */ +int doSingleCrunch(Bundle* bundle) +{ + fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile()); + fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile()); + + String8 input(bundle->getSingleCrunchInputFile()); + String8 output(bundle->getSingleCrunchOutputFile()); + + if (preProcessImageToCache(bundle, input, output) != NO_ERROR) { + // we can't return the status_t as it gets truncate to the lower 8 bits. + return 42; + } + + return NO_ERROR; +} + +char CONSOLE_DATA[2925] = { + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63, + 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83, + 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81, + 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59, + 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, + 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87, + 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, + 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58, + 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81, + 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59, + 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59, + 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81, + 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81, + 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109, + 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, + 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59, + 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59, + 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59, + 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46, + 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32, + 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81, + 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81, + 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58, + 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37, + 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, + 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96, + 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, + 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32, + 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61, + 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32, + 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119, + 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81, + 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41, + 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, + 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, + 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, + 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59, + 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59, + 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, + 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59, + 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33, + 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, + 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95, + 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59, + 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45, + 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59, + 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59, + 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61, + 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, + 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59, + 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32, + 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61, + 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, + 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10 + }; diff --git a/tools/aapt/CrunchCache.cpp b/tools/aapt/CrunchCache.cpp new file mode 100644 index 0000000..c4cf6bc --- /dev/null +++ b/tools/aapt/CrunchCache.cpp @@ -0,0 +1,104 @@ +// +// 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 diff --git a/tools/aapt/CrunchCache.h b/tools/aapt/CrunchCache.h new file mode 100644 index 0000000..be3da5c --- /dev/null +++ b/tools/aapt/CrunchCache.h @@ -0,0 +1,102 @@ +// +// 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 diff --git a/tools/aapt/DirectoryWalker.h b/tools/aapt/DirectoryWalker.h new file mode 100644 index 0000000..88031d0 --- /dev/null +++ b/tools/aapt/DirectoryWalker.h @@ -0,0 +1,98 @@ +// +// 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 diff --git a/tools/aapt/FileFinder.cpp b/tools/aapt/FileFinder.cpp new file mode 100644 index 0000000..18775c0 --- /dev/null +++ b/tools/aapt/FileFinder.cpp @@ -0,0 +1,98 @@ +// +// 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 <dirent.h> +#include <sys/stat.h> + +#include "DirectoryWalker.h" +#include "FileFinder.h" + +//#define DEBUG + +using android::String8; + +// Private function to check whether a file is a directory or not +bool isDirectory(const char* filename) { + struct stat fileStat; + if (stat(filename, &fileStat) == -1) { + return false; + } + return(S_ISDIR(fileStat.st_mode)); +} + + +// Private function to check whether a file is a regular file or not +bool isFile(const char* filename) { + struct stat fileStat; + if (stat(filename, &fileStat) == -1) { + return false; + } + return(S_ISREG(fileStat.st_mode)); +} + +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; + } + /* + * 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 (isDirectory(fullPath.string()) ) { + 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 (isFile(fullPath.string()) ) { + 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) +{ + // 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) { + done = true; + fileStore.add(path,stats->st_mtime); + } + } +} + diff --git a/tools/aapt/FileFinder.h b/tools/aapt/FileFinder.h new file mode 100644 index 0000000..6974aee --- /dev/null +++ b/tools/aapt/FileFinder.h @@ -0,0 +1,80 @@ +// +// 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; + + virtual ~FileFinder() {}; +}; + +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 diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp new file mode 100644 index 0000000..b2cbf49 --- /dev/null +++ b/tools/aapt/Images.cpp @@ -0,0 +1,1387 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#define PNG_INTERNAL + +#include "Images.h" + +#include <androidfw/ResourceTypes.h> +#include <utils/ByteOrder.h> + +#include <png.h> +#include <zlib.h> + +#define NOISY(x) //x + +static void +png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length) +{ + AaptFile* aaptfile = (AaptFile*) png_get_io_ptr(png_ptr); + status_t err = aaptfile->writeData(data, length); + if (err != NO_ERROR) { + png_error(png_ptr, "Write Error"); + } +} + + +static void +png_flush_aapt_file(png_structp png_ptr) +{ +} + +// This holds an image as 8bpp RGBA. +struct image_info +{ + image_info() : rows(NULL), is9Patch(false), allocRows(NULL) { } + ~image_info() { + if (rows && rows != allocRows) { + free(rows); + } + if (allocRows) { + for (int i=0; i<(int)allocHeight; i++) { + free(allocRows[i]); + } + free(allocRows); + } + free(info9Patch.xDivs); + free(info9Patch.yDivs); + free(info9Patch.colors); + } + + png_uint_32 width; + png_uint_32 height; + png_bytepp rows; + + // 9-patch info. + bool is9Patch; + Res_png_9patch info9Patch; + + // Layout padding, if relevant + bool haveLayoutBounds; + int32_t layoutBoundsLeft; + int32_t layoutBoundsTop; + int32_t layoutBoundsRight; + int32_t layoutBoundsBottom; + + png_uint_32 allocHeight; + png_bytepp allocRows; +}; + +static void read_png(const char* imageName, + png_structp read_ptr, png_infop read_info, + image_info* outImageInfo) +{ + int color_type; + int bit_depth, interlace_type, compression_type; + int i; + + png_read_info(read_ptr, read_info); + + png_get_IHDR(read_ptr, read_info, &outImageInfo->width, + &outImageInfo->height, &bit_depth, &color_type, + &interlace_type, &compression_type, NULL); + + //printf("Image %s:\n", imageName); + //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n", + // color_type, bit_depth, interlace_type, compression_type); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(read_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(read_ptr); + + if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) { + //printf("Has PNG_INFO_tRNS!\n"); + png_set_tRNS_to_alpha(read_ptr); + } + + if (bit_depth == 16) + png_set_strip_16(read_ptr); + + if ((color_type&PNG_COLOR_MASK_ALPHA) == 0) + png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER); + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(read_ptr); + + png_read_update_info(read_ptr, read_info); + + outImageInfo->rows = (png_bytepp)malloc( + outImageInfo->height * sizeof(png_bytep)); + outImageInfo->allocHeight = outImageInfo->height; + outImageInfo->allocRows = outImageInfo->rows; + + png_set_rows(read_ptr, read_info, outImageInfo->rows); + + for (i = 0; i < (int)outImageInfo->height; i++) + { + outImageInfo->rows[i] = (png_bytep) + malloc(png_get_rowbytes(read_ptr, read_info)); + } + + png_read_image(read_ptr, outImageInfo->rows); + + png_read_end(read_ptr, read_info); + + NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n", + imageName, + (int)outImageInfo->width, (int)outImageInfo->height, + bit_depth, color_type, + interlace_type, compression_type)); + + png_get_IHDR(read_ptr, read_info, &outImageInfo->width, + &outImageInfo->height, &bit_depth, &color_type, + &interlace_type, &compression_type, NULL); +} + +#define COLOR_TRANSPARENT 0 +#define COLOR_WHITE 0xFFFFFFFF +#define COLOR_TICK 0xFF000000 +#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF + +enum { + TICK_TYPE_NONE, + TICK_TYPE_TICK, + TICK_TYPE_LAYOUT_BOUNDS, + TICK_TYPE_BOTH +}; + +static int tick_type(png_bytep p, bool transparent, const char** outError) +{ + png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + + if (transparent) { + if (p[3] == 0) { + return TICK_TYPE_NONE; + } + if (color == COLOR_LAYOUT_BOUNDS_TICK) { + return TICK_TYPE_LAYOUT_BOUNDS; + } + if (color == COLOR_TICK) { + return TICK_TYPE_TICK; + } + + // Error cases + if (p[3] != 0xff) { + *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)"; + return TICK_TYPE_NONE; + } + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + *outError = "Ticks in transparent frame must be black or red"; + } + return TICK_TYPE_TICK; + } + + if (p[3] != 0xFF) { + *outError = "White frame must be a solid color (no alpha)"; + } + if (color == COLOR_WHITE) { + return TICK_TYPE_NONE; + } + if (color == COLOR_TICK) { + return TICK_TYPE_TICK; + } + if (color == COLOR_LAYOUT_BOUNDS_TICK) { + return TICK_TYPE_LAYOUT_BOUNDS; + } + + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + *outError = "Ticks in white frame must be black or red"; + return TICK_TYPE_NONE; + } + return TICK_TYPE_TICK; +} + +enum { + TICK_START, + TICK_INSIDE_1, + TICK_OUTSIDE_1 +}; + +static status_t get_horizontal_ticks( + png_bytep row, int width, bool transparent, bool required, + int32_t* outLeft, int32_t* outRight, const char** outError, + uint8_t* outDivs, bool multipleAllowed) +{ + int i; + *outLeft = *outRight = -1; + int state = TICK_START; + bool found = false; + + for (i=1; i<width-1; i++) { + if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) { + if (state == TICK_START || + (state == TICK_OUTSIDE_1 && multipleAllowed)) { + *outLeft = i-1; + *outRight = width-2; + found = true; + if (outDivs != NULL) { + *outDivs += 2; + } + state = TICK_INSIDE_1; + } else if (state == TICK_OUTSIDE_1) { + *outError = "Can't have more than one marked region along edge"; + *outLeft = i; + return UNKNOWN_ERROR; + } + } else if (*outError == NULL) { + if (state == TICK_INSIDE_1) { + // We're done with this div. Move on to the next. + *outRight = i-1; + outRight += 2; + outLeft += 2; + state = TICK_OUTSIDE_1; + } + } else { + *outLeft = i; + return UNKNOWN_ERROR; + } + } + + if (required && !found) { + *outError = "No marked region found along edge"; + *outLeft = -1; + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +static status_t get_vertical_ticks( + png_bytepp rows, int offset, int height, bool transparent, bool required, + int32_t* outTop, int32_t* outBottom, const char** outError, + uint8_t* outDivs, bool multipleAllowed) +{ + int i; + *outTop = *outBottom = -1; + int state = TICK_START; + bool found = false; + + for (i=1; i<height-1; i++) { + if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) { + if (state == TICK_START || + (state == TICK_OUTSIDE_1 && multipleAllowed)) { + *outTop = i-1; + *outBottom = height-2; + found = true; + if (outDivs != NULL) { + *outDivs += 2; + } + state = TICK_INSIDE_1; + } else if (state == TICK_OUTSIDE_1) { + *outError = "Can't have more than one marked region along edge"; + *outTop = i; + return UNKNOWN_ERROR; + } + } else if (*outError == NULL) { + if (state == TICK_INSIDE_1) { + // We're done with this div. Move on to the next. + *outBottom = i-1; + outTop += 2; + outBottom += 2; + state = TICK_OUTSIDE_1; + } + } else { + *outTop = i; + return UNKNOWN_ERROR; + } + } + + if (required && !found) { + *outError = "No marked region found along edge"; + *outTop = -1; + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +static status_t get_horizontal_layout_bounds_ticks( + png_bytep row, int width, bool transparent, bool required, + int32_t* outLeft, int32_t* outRight, const char** outError) +{ + int i; + *outLeft = *outRight = 0; + + // Look for left tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) { + // Starting with a layout padding tick + i = 1; + while (i < width - 1) { + (*outLeft)++; + i++; + int tick = tick_type(row + i * 4, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + // Look for right tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) { + // Ending with a layout padding tick + i = width - 2; + while (i > 1) { + (*outRight)++; + i--; + int tick = tick_type(row+i*4, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + return NO_ERROR; +} + +static status_t get_vertical_layout_bounds_ticks( + png_bytepp rows, int offset, int height, bool transparent, bool required, + int32_t* outTop, int32_t* outBottom, const char** outError) +{ + int i; + *outTop = *outBottom = 0; + + // Look for top tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) { + // Starting with a layout padding tick + i = 1; + while (i < height - 1) { + (*outTop)++; + i++; + int tick = tick_type(rows[i] + offset, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + // Look for bottom tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) { + // Ending with a layout padding tick + i = height - 2; + while (i > 1) { + (*outBottom)++; + i--; + int tick = tick_type(rows[i] + offset, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + return NO_ERROR; +} + + +static uint32_t get_color( + png_bytepp rows, int left, int top, int right, int bottom) +{ + png_bytep color = rows[top] + left*4; + + if (left > right || top > bottom) { + return Res_png_9patch::TRANSPARENT_COLOR; + } + + while (top <= bottom) { + for (int i = left; i <= right; i++) { + png_bytep p = rows[top]+i*4; + if (color[3] == 0) { + if (p[3] != 0) { + return Res_png_9patch::NO_COLOR; + } + } else if (p[0] != color[0] || p[1] != color[1] + || p[2] != color[2] || p[3] != color[3]) { + return Res_png_9patch::NO_COLOR; + } + } + top++; + } + + if (color[3] == 0) { + return Res_png_9patch::TRANSPARENT_COLOR; + } + return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2]; +} + +static void select_patch( + int which, int front, int back, int size, int* start, int* end) +{ + switch (which) { + case 0: + *start = 0; + *end = front-1; + break; + case 1: + *start = front; + *end = back-1; + break; + case 2: + *start = back; + *end = size-1; + break; + } +} + +static uint32_t get_color(image_info* image, int hpatch, int vpatch) +{ + int left, right, top, bottom; + select_patch( + hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1], + image->width, &left, &right); + select_patch( + vpatch, image->info9Patch.yDivs[0], image->info9Patch.yDivs[1], + image->height, &top, &bottom); + //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n", + // hpatch, vpatch, left, top, right, bottom); + const uint32_t c = get_color(image->rows, left, top, right, bottom); + NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c)); + return c; +} + +static status_t do_9patch(const char* imageName, image_info* image) +{ + image->is9Patch = true; + + int W = image->width; + int H = image->height; + int i, j; + + int maxSizeXDivs = W * sizeof(int32_t); + int maxSizeYDivs = H * sizeof(int32_t); + int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs); + int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs); + uint8_t numXDivs = 0; + uint8_t numYDivs = 0; + int8_t numColors; + int numRows; + int numCols; + int top; + int left; + int right; + int bottom; + memset(xDivs, -1, maxSizeXDivs); + memset(yDivs, -1, maxSizeYDivs); + image->info9Patch.paddingLeft = image->info9Patch.paddingRight = + image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; + + image->layoutBoundsLeft = image->layoutBoundsRight = + image->layoutBoundsTop = image->layoutBoundsBottom = 0; + + png_bytep p = image->rows[0]; + bool transparent = p[3] == 0; + bool hasColor = false; + + const char* errorMsg = NULL; + int errorPixel = -1; + const char* errorEdge = NULL; + + int colorIndex = 0; + + // Validate size... + if (W < 3 || H < 3) { + errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; + goto getout; + } + + // Validate frame... + if (!transparent && + (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { + errorMsg = "Must have one-pixel frame that is either transparent or white"; + goto getout; + } + + // Find left and right of sizing areas... + if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0], + &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) { + errorPixel = xDivs[0]; + errorEdge = "top"; + goto getout; + } + + // Find top and bottom of sizing areas... + if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0], + &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) { + errorPixel = yDivs[0]; + errorEdge = "left"; + goto getout; + } + + // Find left and right of padding area... + if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft, + &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) { + errorPixel = image->info9Patch.paddingLeft; + errorEdge = "bottom"; + goto getout; + } + + // Find top and bottom of padding area... + if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop, + &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) { + errorPixel = image->info9Patch.paddingTop; + errorEdge = "right"; + goto getout; + } + + // Find left and right of layout padding... + get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false, + &image->layoutBoundsLeft, + &image->layoutBoundsRight, &errorMsg); + + get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false, + &image->layoutBoundsTop, + &image->layoutBoundsBottom, &errorMsg); + + image->haveLayoutBounds = image->layoutBoundsLeft != 0 + || image->layoutBoundsRight != 0 + || image->layoutBoundsTop != 0 + || image->layoutBoundsBottom != 0; + + if (image->haveLayoutBounds) { + NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop, + image->layoutBoundsRight, image->layoutBoundsBottom)); + } + + // Copy patch data into image + image->info9Patch.numXDivs = numXDivs; + image->info9Patch.numYDivs = numYDivs; + image->info9Patch.xDivs = xDivs; + image->info9Patch.yDivs = yDivs; + + // If padding is not yet specified, take values from size. + if (image->info9Patch.paddingLeft < 0) { + image->info9Patch.paddingLeft = xDivs[0]; + image->info9Patch.paddingRight = W - 2 - xDivs[1]; + } else { + // Adjust value to be correct! + image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; + } + if (image->info9Patch.paddingTop < 0) { + image->info9Patch.paddingTop = yDivs[0]; + image->info9Patch.paddingBottom = H - 2 - yDivs[1]; + } else { + // Adjust value to be correct! + image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; + } + + NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, + image->info9Patch.xDivs[0], image->info9Patch.xDivs[1], + image->info9Patch.yDivs[0], image->info9Patch.yDivs[1])); + NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, + image->info9Patch.paddingLeft, image->info9Patch.paddingRight, + image->info9Patch.paddingTop, image->info9Patch.paddingBottom)); + + // Remove frame from image. + image->rows = (png_bytepp)malloc((H-2) * sizeof(png_bytep)); + for (i=0; i<(H-2); i++) { + image->rows[i] = image->allocRows[i+1]; + memmove(image->rows[i], image->rows[i]+4, (W-2)*4); + } + image->width -= 2; + W = image->width; + image->height -= 2; + H = image->height; + + // Figure out the number of rows and columns in the N-patch + numCols = numXDivs + 1; + if (xDivs[0] == 0) { // Column 1 is strechable + numCols--; + } + if (xDivs[numXDivs - 1] == W) { + numCols--; + } + numRows = numYDivs + 1; + if (yDivs[0] == 0) { // Row 1 is strechable + numRows--; + } + if (yDivs[numYDivs - 1] == H) { + numRows--; + } + + // Make sure the amount of rows and columns will fit in the number of + // colors we can use in the 9-patch format. + if (numRows * numCols > 0x7F) { + errorMsg = "Too many rows and columns in 9-patch perimeter"; + goto getout; + } + + numColors = numRows * numCols; + image->info9Patch.numColors = numColors; + image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t)); + + // Fill in color information for each patch. + + uint32_t c; + top = 0; + + // The first row always starts with the top being at y=0 and the bottom + // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case + // the first row is stretchable along the Y axis, otherwise it is fixed. + // The last row always ends with the bottom being bitmap.height and the top + // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or + // yDivs[numYDivs-1]. In the former case the last row is stretchable along + // the Y axis, otherwise it is fixed. + // + // The first and last columns are similarly treated with respect to the X + // axis. + // + // The above is to help explain some of the special casing that goes on the + // code below. + + // The initial yDiv and whether the first row is considered stretchable or + // not depends on whether yDiv[0] was zero or not. + for (j = (yDivs[0] == 0 ? 1 : 0); + j <= numYDivs && top < H; + j++) { + if (j == numYDivs) { + bottom = H; + } else { + bottom = yDivs[j]; + } + left = 0; + // The initial xDiv and whether the first column is considered + // stretchable or not depends on whether xDiv[0] was zero or not. + for (i = xDivs[0] == 0 ? 1 : 0; + i <= numXDivs && left < W; + i++) { + if (i == numXDivs) { + right = W; + } else { + right = xDivs[i]; + } + c = get_color(image->rows, left, top, right - 1, bottom - 1); + image->info9Patch.colors[colorIndex++] = c; + NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true); + left = right; + } + top = bottom; + } + + assert(colorIndex == numColors); + + for (i=0; i<numColors; i++) { + if (hasColor) { + if (i == 0) printf("Colors in %s:\n ", imageName); + printf(" #%08x", image->info9Patch.colors[i]); + if (i == numColors - 1) printf("\n"); + } + } + + image->is9Patch = true; + image->info9Patch.deviceToFile(); + +getout: + if (errorMsg) { + fprintf(stderr, + "ERROR: 9-patch image %s malformed.\n" + " %s.\n", imageName, errorMsg); + if (errorEdge != NULL) { + if (errorPixel >= 0) { + fprintf(stderr, + " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge); + } else { + fprintf(stderr, + " Found along %s edge.\n", errorEdge); + } + } + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +static void checkNinePatchSerialization(Res_png_9patch* inPatch, void * data) +{ + if (sizeof(void*) != sizeof(int32_t)) { + // can't deserialize on a non-32 bit system + return; + } + size_t patchSize = inPatch->serializedSize(); + void * newData = malloc(patchSize); + memcpy(newData, data, patchSize); + Res_png_9patch* outPatch = inPatch->deserialize(newData); + // deserialization is done in place, so outPatch == newData + assert(outPatch == newData); + assert(outPatch->numXDivs == inPatch->numXDivs); + assert(outPatch->numYDivs == inPatch->numYDivs); + assert(outPatch->paddingLeft == inPatch->paddingLeft); + assert(outPatch->paddingRight == inPatch->paddingRight); + assert(outPatch->paddingTop == inPatch->paddingTop); + assert(outPatch->paddingBottom == inPatch->paddingBottom); + for (int i = 0; i < outPatch->numXDivs; i++) { + assert(outPatch->xDivs[i] == inPatch->xDivs[i]); + } + for (int i = 0; i < outPatch->numYDivs; i++) { + assert(outPatch->yDivs[i] == inPatch->yDivs[i]); + } + for (int i = 0; i < outPatch->numColors; i++) { + assert(outPatch->colors[i] == inPatch->colors[i]); + } + free(newData); +} + +static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) { + if (!(patch1.numXDivs == patch2.numXDivs && + patch1.numYDivs == patch2.numYDivs && + patch1.numColors == patch2.numColors && + patch1.paddingLeft == patch2.paddingLeft && + patch1.paddingRight == patch2.paddingRight && + patch1.paddingTop == patch2.paddingTop && + patch1.paddingBottom == patch2.paddingBottom)) { + return false; + } + for (int i = 0; i < patch1.numColors; i++) { + if (patch1.colors[i] != patch2.colors[i]) { + return false; + } + } + for (int i = 0; i < patch1.numXDivs; i++) { + if (patch1.xDivs[i] != patch2.xDivs[i]) { + return false; + } + } + for (int i = 0; i < patch1.numYDivs; i++) { + if (patch1.yDivs[i] != patch2.yDivs[i]) { + return false; + } + } + return true; +} + +static void dump_image(int w, int h, png_bytepp rows, int color_type) +{ + int i, j, rr, gg, bb, aa; + + int bpp; + if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { + bpp = 1; + } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + bpp = 2; + } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + // We use a padding byte even when there is no alpha + bpp = 4; + } else { + printf("Unknown color type %d.\n", color_type); + } + + for (j = 0; j < h; j++) { + png_bytep row = rows[j]; + for (i = 0; i < w; i++) { + rr = row[0]; + gg = row[1]; + bb = row[2]; + aa = row[3]; + row += bpp; + + if (i == 0) { + printf("Row %d:", j); + } + switch (bpp) { + case 1: + printf(" (%d)", rr); + break; + case 2: + printf(" (%d %d", rr, gg); + break; + case 3: + printf(" (%d %d %d)", rr, gg, bb); + break; + case 4: + printf(" (%d %d %d %d)", rr, gg, bb, aa); + break; + } + if (i == (w - 1)) { + NOISY(printf("\n")); + } + } + } +} + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define ABS(a) ((a)<0?-(a):(a)) + +static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance, + png_colorp rgbPalette, png_bytep alphaPalette, + int *paletteEntries, bool *hasTransparency, int *colorType, + png_bytepp outRows) +{ + int w = imageInfo.width; + int h = imageInfo.height; + int i, j, rr, gg, bb, aa, idx; + uint32_t colors[256], col; + int num_colors = 0; + int maxGrayDeviation = 0; + + bool isOpaque = true; + bool isPalette = true; + bool isGrayscale = true; + + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors + + // NOISY(printf("Initial image data:\n")); + // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA); + + for (j = 0; j < h; j++) { + png_bytep row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + int odev = maxGrayDeviation; + maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); + if (maxGrayDeviation > odev) { + NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", + maxGrayDeviation, i, j, rr, gg, bb, aa)); + } + + // Check if image is really grayscale + if (isGrayscale) { + if (rr != gg || rr != bb) { + NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", + i, j, rr, gg, bb, aa)); + isGrayscale = false; + } + } + + // Check if image is really opaque + if (isOpaque) { + if (aa != 0xff) { + NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", + i, j, rr, gg, bb, aa)); + isOpaque = false; + } + } + + // Check if image is really <= 256 colors + if (isPalette) { + col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa); + bool match = false; + for (idx = 0; idx < num_colors; idx++) { + if (colors[idx] == col) { + match = true; + break; + } + } + + // Write the palette index for the pixel to outRows optimistically + // We might overwrite it later if we decide to encode as gray or + // gray + alpha + *out++ = idx; + if (!match) { + if (num_colors == 256) { + NOISY(printf("Found 257th color at %d, %d\n", i, j)); + isPalette = false; + } else { + colors[num_colors++] = col; + } + } + } + } + } + + *paletteEntries = 0; + *hasTransparency = !isOpaque; + int bpp = isOpaque ? 3 : 4; + int paletteSize = w * h + bpp * num_colors; + + NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false")); + NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false")); + NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false")); + NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", + paletteSize, 2 * w * h, bpp * w * h)); + NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance)); + + // Choose the best color type for the image. + // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel + // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations + // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA + // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently + // small, otherwise use COLOR_TYPE_RGB{_ALPHA} + if (isGrayscale) { + if (isOpaque) { + *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel + } else { + // Use a simple heuristic to determine whether using a palette will + // save space versus using gray + alpha for each pixel. + // This doesn't take into account chunk overhead, filtering, LZ + // compression, etc. + if (isPalette && (paletteSize < 2 * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color + } else { + *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel + } + } + } else if (isPalette && (paletteSize < bpp * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; + } else { + if (maxGrayDeviation <= grayscaleTolerance) { + printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation); + *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; + } else { + *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + } + } + + // Perform postprocessing of the image or palette data based on the final + // color type chosen + + if (*colorType == PNG_COLOR_TYPE_PALETTE) { + // Create separate RGB and Alpha palettes and set the number of colors + *paletteEntries = num_colors; + + // Create the RGB and alpha palettes + for (int idx = 0; idx < num_colors; idx++) { + col = colors[idx]; + rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff); + rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff); + rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff); + alphaPalette[idx] = (png_byte) (col & 0xff); + } + } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + // If the image is gray or gray + alpha, compact the pixels into outRows + for (j = 0; j < h; j++) { + png_bytep row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + if (isGrayscale) { + *out++ = rr; + } else { + *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); + } + if (!isOpaque) { + *out++ = aa; + } + } + } + } +} + + +static void write_png(const char* imageName, + png_structp write_ptr, png_infop write_info, + image_info& imageInfo, int grayscaleTolerance) +{ + bool optimize = true; + png_uint_32 width, height; + int color_type; + int bit_depth, interlace_type, compression_type; + int i; + + png_unknown_chunk unknowns[2]; + unknowns[0].data = NULL; + unknowns[1].data = NULL; + + png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep)); + if (outRows == (png_bytepp) 0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + for (i = 0; i < (int) imageInfo.height; i++) { + outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width); + if (outRows[i] == (png_bytep) 0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + } + + png_set_compression_level(write_ptr, Z_BEST_COMPRESSION); + + NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName, + (int) imageInfo.width, (int) imageInfo.height)); + + png_color rgbPalette[256]; + png_byte alphaPalette[256]; + bool hasTransparency; + int paletteEntries; + + analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette, + &paletteEntries, &hasTransparency, &color_type, outRows); + + // If the image is a 9-patch, we need to preserve it as a ARGB file to make + // sure the pixels will not be pre-dithered/clamped until we decide they are + if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) { + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } + + switch (color_type) { + case PNG_COLOR_TYPE_PALETTE: + NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n", + imageName, paletteEntries, + hasTransparency ? " (with alpha)" : "")); + break; + case PNG_COLOR_TYPE_GRAY: + NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName)); + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName)); + break; + case PNG_COLOR_TYPE_RGB: + NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName)); + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName)); + break; + } + + png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height, + 8, color_type, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries); + if (hasTransparency) { + png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0); + } + png_set_filter(write_ptr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(write_ptr, 0, PNG_ALL_FILTERS); + } + + if (imageInfo.is9Patch) { + int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0); + int p_index = imageInfo.haveLayoutBounds ? 1 : 0; + int b_index = 0; + png_byte *chunk_names = imageInfo.haveLayoutBounds + ? (png_byte*)"npLb\0npTc\0" + : (png_byte*)"npTc"; + NOISY(printf("Adding 9-patch info...\n")); + strcpy((char*)unknowns[p_index].name, "npTc"); + unknowns[p_index].data = (png_byte*)imageInfo.info9Patch.serialize(); + unknowns[p_index].size = imageInfo.info9Patch.serializedSize(); + // TODO: remove the check below when everything works + checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data); + + if (imageInfo.haveLayoutBounds) { + int chunk_size = sizeof(png_uint_32) * 4; + strcpy((char*)unknowns[b_index].name, "npLb"); + unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1); + memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size); + unknowns[b_index].size = chunk_size; + } + + for (int i = 0; i < chunk_count; i++) { + unknowns[i].location = PNG_HAVE_PLTE; + } + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, + chunk_names, chunk_count); + png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count); +#if PNG_LIBPNG_VER < 10600 + /* Deal with unknown chunk location bug in 1.5.x and earlier */ + png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE); + if (imageInfo.haveLayoutBounds) { + png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE); + } +#endif + } + + + png_write_info(write_ptr, write_info); + + png_bytepp rows; + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + if (color_type == PNG_COLOR_TYPE_RGB) { + png_set_filler(write_ptr, 0, PNG_FILLER_AFTER); + } + rows = imageInfo.rows; + } else { + rows = outRows; + } + png_write_image(write_ptr, rows); + +// NOISY(printf("Final image data:\n")); +// dump_image(imageInfo.width, imageInfo.height, rows, color_type); + + png_write_end(write_ptr, write_info); + + for (i = 0; i < (int) imageInfo.height; i++) { + free(outRows[i]); + } + free(outRows); + free(unknowns[0].data); + free(unknowns[1].data); + + png_get_IHDR(write_ptr, write_info, &width, &height, + &bit_depth, &color_type, &interlace_type, + &compression_type, NULL); + + NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n", + (int)width, (int)height, bit_depth, color_type, interlace_type, + compression_type)); +} + +status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, + const sp<AaptFile>& file, String8* outNewLeafName) +{ + String8 ext(file->getPath().getPathExtension()); + + // We currently only process PNG images. + if (strcmp(ext.string(), ".png") != 0) { + 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; + + image_info imageInfo; + + png_structp write_ptr = NULL; + png_infop write_info = NULL; + + status_t error = UNKNOWN_ERROR; + + const size_t nameLen = file->getPath().length(); + + fp = fopen(file->getSourceFile().string(), "rb"); + if (fp == NULL) { + fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string()); + goto bail; + } + + read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL, + (png_error_ptr)NULL); + if (!read_ptr) { + goto bail; + } + + read_info = png_create_info_struct(read_ptr); + if (!read_info) { + goto bail; + } + + if (setjmp(png_jmpbuf(read_ptr))) { + goto bail; + } + + png_init_io(read_ptr, fp); + + 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; + } + } + } + + write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL, + (png_error_ptr)NULL); + if (!write_ptr) + { + goto bail; + } + + write_info = png_create_info_struct(write_ptr); + if (!write_info) + { + goto bail; + } + + png_set_write_fn(write_ptr, (void*)file.get(), + png_write_aapt_file, png_flush_aapt_file); + + if (setjmp(png_jmpbuf(write_ptr))) + { + goto bail; + } + + write_png(printableName.string(), write_ptr, write_info, imageInfo, + bundle->getGrayscaleTolerance()); + + error = NO_ERROR; + + if (bundle->getVerbose()) { + 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); + } + +bail: + if (read_ptr) { + png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL); + } + if (fp) { + fclose(fp); + } + if (write_ptr) { + png_destroy_write_struct(&write_ptr, &write_info); + } + + if (error != NO_ERROR) { + fprintf(stderr, "ERROR: Failure processing PNG image %s\n", + file->getPrintableSource().string()); + } + return error; +} + +status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const 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; + + if (bundle->getVerbose()) { + printf("Processing image to cache: %s => %s\n", source.string(), dest.string()); + } + + // 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) +{ + String8 ext(file->getPath().getPathExtension()); + + // At this point, now that we have all the resource data, all we need to + // do is compile XML files. + if (strcmp(ext.string(), ".xml") == 0) { + return compileXmlFile(assets, file, table); + } + + return NO_ERROR; +} diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h new file mode 100644 index 0000000..91b6554 --- /dev/null +++ b/tools/aapt/Images.h @@ -0,0 +1,26 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#ifndef IMAGES_H +#define IMAGES_H + +#include "ResourceTable.h" +#include "Bundle.h" + +#include <utils/String8.h> +#include <utils/RefBase.h> + +using android::String8; + +status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, + const sp<AaptFile>& file, String8* outNewLeafName); + +status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest); + +status_t postProcessImage(const sp<AaptAssets>& assets, + ResourceTable* table, const sp<AaptFile>& file); + +#endif diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp new file mode 100644 index 0000000..4a8aa9c --- /dev/null +++ b/tools/aapt/Main.cpp @@ -0,0 +1,655 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Android Asset Packaging Tool main entry point. +// +#include "Main.h" +#include "Bundle.h" + +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> + +#include <stdlib.h> +#include <getopt.h> +#include <assert.h> + +using namespace android; + +static const char* gProgName = "aapt"; + +/* + * When running under Cygwin on Windows, this will convert slash-based + * paths into back-slash-based ones. Otherwise the ApptAssets file comparisons + * fail later as they use back-slash separators under Windows. + * + * This operates in-place on the path string. + */ +void convertPath(char *path) { + if (path != NULL && OS_PATH_SEPARATOR != '/') { + for (; *path; path++) { + if (*path == '/') { + *path = OS_PATH_SEPARATOR; + } + } + } +} + +/* + * Print usage info. + */ +void usage(void) +{ + fprintf(stderr, "Android Asset Packaging Tool\n\n"); + fprintf(stderr, "Usage:\n"); + fprintf(stderr, + " %s l[ist] [-v] [-a] file.{zip,jar,apk}\n" + " List contents of Zip-compatible archive.\n\n", gProgName); + fprintf(stderr, + " %s d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]\n" + " strings Print the contents of the resource table string pool in the APK.\n" + " badging Print the label and icon for the app declared in APK.\n" + " permissions Print the permissions from the APK.\n" + " resources Print the resource table from the APK.\n" + " configurations Print the configurations in the APK.\n" + " 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" + " [-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" + " [--rename-manifest-package PACKAGE] \\\n" + " [--rename-instrumentation-target-package PACKAGE] \\\n" + " [--utf16] [--auto-add-overlay] \\\n" + " [--max-res-version VAL] \\\n" + " [-I base-package [-I base-package ...]] \\\n" + " [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n" + " [-S resource-sources [-S resource-sources ...]] \\\n" + " [-F apk-file] [-J R-file-dir] \\\n" + " [--product product1,product2,...] \\\n" + " [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n" + " [raw-files-dir [raw-files-dir] ...] \\\n" + " [--output-text-symbols DIR]\n" + "\n" + " Package the android resources. It will read assets and resources that are\n" + " supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R\n" + " options control which files are output.\n\n" + , gProgName); + fprintf(stderr, + " %s r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]\n" + " Delete specified files from Zip-compatible archive.\n\n", + gProgName); + 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 on one or several resource folders\n" + " and store the results in the output folder.\n\n", gProgName); + fprintf(stderr, + " %s s[ingleCrunch] [-v] -i input-file -o outputfile\n" + " Do PNG preprocessing on a single file.\n\n", gProgName); + fprintf(stderr, + " %s v[ersion]\n" + " Print program version.\n\n", gProgName); + fprintf(stderr, + " Modifiers:\n" + " -a print Android-specific data (resources, manifest) when listing\n" + " -c specify which configurations to include. The default is all\n" + " configurations. The value of the parameter should be a comma\n" + " separated list of configuration values. Locales should be specified\n" + " as either a language or language-region pair. Some examples:\n" + " en\n" + " port,en\n" + " port,land,en_US\n" + " If you put the special locale, zz_ZZ on the list, it will perform\n" + " pseudolocalization on the default locale, modifying all of the\n" + " strings so you can look for strings that missed the\n" + " internationalization process. For example:\n" + " port,land,zz_ZZ\n" + " -d one or more device assets to include, separated by commas\n" + " -f force overwrite of existing files\n" + " -g specify a pixel tolerance to force images to grayscale, default 0\n" + " -j specify a jar or zip file containing classes to include\n" + " -k junk path of file(s) added\n" + " -m make package directories under location specified by -J\n" +#if 0 + " -p pseudolocalize the default configuration\n" +#endif + " -u update existing packages (add new, replace older, remove deleted files)\n" + " -v verbose output\n" + " -x create 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" + " -G A file to output proguard options into.\n" + " -F specify the apk file to output\n" + " -I add an existing package to base include set\n" + " -J specify where to output R.java resource constant definitions\n" + " -M specify full path to AndroidManifest.xml to include in zip\n" + " -P specify where to output public resource definitions\n" + " -S directory in which to find resources. Multiple directories will be scanned\n" + " and the first match found (left to right) will take precedence.\n" + " -0 specifies an additional extension for which such files will not\n" + " be stored compressed in the .apk. An empty string means to not\n" + " compress any files at all.\n" + " --debug-mode\n" + " inserts android:debuggable=\"true\" in to the application node of the\n" + " manifest, making the application debuggable even on production devices.\n" + " --include-meta-data\n" + " when used with \"dump badging\" also includes meta-data tags.\n" + " --min-sdk-version\n" + " inserts android:minSdkVersion in to manifest. If the version is 7 or\n" + " higher, the default encoding for resources will be in UTF-8.\n" + " --target-sdk-version\n" + " inserts android:targetSdkVersion in to manifest.\n" + " --max-res-version\n" + " ignores versioned resource directories above the given value.\n" + " --values\n" + " when used with \"dump resources\" also includes resource values.\n" + " --version-code\n" + " inserts android:versionCode in to manifest.\n" + " --version-name\n" + " inserts android:versionName in to manifest.\n" + " --custom-package\n" + " generates R.java into a different package.\n" + " --extra-packages\n" + " generate R.java for libraries. Separate libraries with ':'.\n" + " --generate-dependencies\n" + " generate dependency files in the same directories for R.java and resource package\n" + " --auto-add-overlay\n" + " Automatically add resources that are only in overlays.\n" + " --preferred-configurations\n" + " Like the -c option for filtering out unneeded configurations, but\n" + " only expresses a preference. If there is no resource available with\n" + " the preferred configuration then it will not be stripped.\n" + " --rename-manifest-package\n" + " Rewrite the manifest so that its package name is the package name\n" + " given here. Relative class names (for example .Foo) will be\n" + " changed to absolute names with the old package so that the code\n" + " does not need to change.\n" + " --rename-instrumentation-target-package\n" + " Rewrite the manifest so that all of its instrumentation\n" + " components target the given package. Useful when used in\n" + " conjunction with --rename-manifest-package to fix tests against\n" + " a package that has been renamed.\n" + " --product\n" + " Specifies which variant to choose for strings that have\n" + " product variants\n" + " --utf16\n" + " changes default encoding for resources to UTF-16. Only useful when API\n" + " level is set to 7 or higher where the default encoding is UTF-8.\n" + " --non-constant-id\n" + " Make the resources ID non constant. This is required to make an R java class\n" + " that does not contain the final value but is used to make reusable compiled\n" + " libraries that need to access resources.\n" + " --error-on-failed-insert\n" + " Forces aapt to return an error if it fails to insert values into the manifest\n" + " with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n" + " and --version-name.\n" + " Insertion typically fails if the manifest already defines the attribute.\n" + " --output-text-symbols\n" + " Generates a text file containing the resource symbols of the R class in the\n" + " specified folder.\n" + " --ignore-assets\n" + " Assets to be ignored. Default pattern is:\n" + " %s\n", + gDefaultIgnoreAssets); +} + +/* + * Dispatch the command. + */ +int handleCommand(Bundle* bundle) +{ + //printf("--- command %d (verbose=%d force=%d):\n", + // bundle->getCommand(), bundle->getVerbose(), bundle->getForce()); + //for (int i = 0; i < bundle->getFileSpecCount(); i++) + // printf(" %d: '%s'\n", i, bundle->getFileSpecEntry(i)); + + switch (bundle->getCommand()) { + case kCommandVersion: return doVersion(bundle); + case kCommandList: return doList(bundle); + case kCommandDump: return doDump(bundle); + case kCommandAdd: return doAdd(bundle); + case kCommandRemove: return doRemove(bundle); + case kCommandPackage: return doPackage(bundle); + case kCommandCrunch: return doCrunch(bundle); + case kCommandSingleCrunch: return doSingleCrunch(bundle); + default: + fprintf(stderr, "%s: requested command not yet supported\n", gProgName); + return 1; + } +} + +/* + * Parse args. + */ +int main(int argc, char* const argv[]) +{ + char *prog = argv[0]; + Bundle bundle; + bool wantUsage = false; + int result = 1; // pessimistically assume an error. + int tolerance = 0; + + /* default to compression */ + bundle.setCompressionMethod(ZipEntry::kCompressDeflated); + + if (argc < 2) { + wantUsage = true; + goto bail; + } + + if (argv[1][0] == 'v') + bundle.setCommand(kCommandVersion); + else if (argv[1][0] == 'd') + bundle.setCommand(kCommandDump); + else if (argv[1][0] == 'l') + bundle.setCommand(kCommandList); + else if (argv[1][0] == 'a') + bundle.setCommand(kCommandAdd); + else if (argv[1][0] == 'r') + bundle.setCommand(kCommandRemove); + else if (argv[1][0] == 'p') + bundle.setCommand(kCommandPackage); + else if (argv[1][0] == 'c') + bundle.setCommand(kCommandCrunch); + else if (argv[1][0] == 's') + bundle.setCommand(kCommandSingleCrunch); + else { + fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]); + wantUsage = true; + goto bail; + } + argc -= 2; + argv += 2; + + /* + * Pull out flags. We support "-fv" and "-f -v". + */ + while (argc && argv[0][0] == '-') { + /* flag(s) found */ + const char* cp = argv[0] +1; + + while (*cp != '\0') { + switch (*cp) { + case 'v': + bundle.setVerbose(true); + break; + case 'a': + bundle.setAndroidList(true); + break; + case 'c': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-c' option\n"); + wantUsage = true; + goto bail; + } + bundle.addConfigurations(argv[0]); + break; + case 'f': + bundle.setForce(true); + break; + case 'g': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-g' option\n"); + wantUsage = true; + goto bail; + } + tolerance = atoi(argv[0]); + bundle.setGrayscaleTolerance(tolerance); + printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance); + break; + case 'k': + bundle.setJunkPath(true); + break; + case 'm': + bundle.setMakePackageDirs(true); + break; +#if 0 + case 'p': + bundle.setPseudolocalize(true); + break; +#endif + case 'u': + bundle.setUpdate(true); + break; + case 'x': + bundle.setExtending(true); + break; + case 'z': + bundle.setRequireLocalization(true); + break; + case 'j': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-j' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.addJarFile(argv[0]); + break; + case 'A': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-A' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setAssetSourceDir(argv[0]); + break; + case 'G': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-G' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setProguardFile(argv[0]); + break; + case 'I': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-I' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.addPackageInclude(argv[0]); + break; + case 'F': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-F' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setOutputAPKFile(argv[0]); + break; + case 'J': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-J' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setRClassDir(argv[0]); + break; + case 'M': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-M' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setAndroidManifestFile(argv[0]); + break; + case 'P': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-P' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setPublicOutputFile(argv[0]); + break; + case 'S': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-S' option\n"); + wantUsage = true; + goto bail; + } + 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 'i': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-i' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setSingleCrunchInputFile(argv[0]); + break; + case 'o': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-o' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setSingleCrunchOutputFile(argv[0]); + break; + case '0': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-e' option\n"); + wantUsage = true; + goto bail; + } + if (argv[0][0] != 0) { + bundle.addNoCompressExtension(argv[0]); + } else { + bundle.setCompressionMethod(ZipEntry::kCompressStored); + } + break; + case '-': + if (strcmp(cp, "-debug-mode") == 0) { + bundle.setDebugMode(true); + } else if (strcmp(cp, "-min-sdk-version") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--min-sdk-version' option\n"); + wantUsage = true; + goto bail; + } + bundle.setMinSdkVersion(argv[0]); + } else if (strcmp(cp, "-target-sdk-version") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--target-sdk-version' option\n"); + wantUsage = true; + goto bail; + } + bundle.setTargetSdkVersion(argv[0]); + } else if (strcmp(cp, "-max-sdk-version") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--max-sdk-version' option\n"); + wantUsage = true; + goto bail; + } + bundle.setMaxSdkVersion(argv[0]); + } else if (strcmp(cp, "-max-res-version") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--max-res-version' option\n"); + wantUsage = true; + goto bail; + } + bundle.setMaxResVersion(argv[0]); + } else if (strcmp(cp, "-version-code") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--version-code' option\n"); + wantUsage = true; + goto bail; + } + bundle.setVersionCode(argv[0]); + } else if (strcmp(cp, "-version-name") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--version-name' option\n"); + wantUsage = true; + goto bail; + } + bundle.setVersionName(argv[0]); + } else if (strcmp(cp, "-values") == 0) { + bundle.setValues(true); + } else if (strcmp(cp, "-include-meta-data") == 0) { + bundle.setIncludeMetaData(true); + } else if (strcmp(cp, "-custom-package") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--custom-package' option\n"); + wantUsage = true; + goto bail; + } + bundle.setCustomPackage(argv[0]); + } else if (strcmp(cp, "-extra-packages") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--extra-packages' option\n"); + wantUsage = true; + goto bail; + } + bundle.setExtraPackages(argv[0]); + } else if (strcmp(cp, "-generate-dependencies") == 0) { + bundle.setGenDependencies(true); + } else if (strcmp(cp, "-utf16") == 0) { + bundle.setWantUTF16(true); + } else if (strcmp(cp, "-preferred-configurations") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n"); + wantUsage = true; + goto bail; + } + bundle.addPreferredConfigurations(argv[0]); + } else if (strcmp(cp, "-rename-manifest-package") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--rename-manifest-package' option\n"); + wantUsage = true; + goto bail; + } + bundle.setManifestPackageNameOverride(argv[0]); + } else if (strcmp(cp, "-rename-instrumentation-target-package") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--rename-instrumentation-target-package' option\n"); + wantUsage = true; + goto bail; + } + bundle.setInstrumentationPackageNameOverride(argv[0]); + } else if (strcmp(cp, "-auto-add-overlay") == 0) { + bundle.setAutoAddOverlay(true); + } else if (strcmp(cp, "-error-on-failed-insert") == 0) { + bundle.setErrorOnFailedInsert(true); + } else if (strcmp(cp, "-output-text-symbols") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-output-text-symbols' option\n"); + wantUsage = true; + goto bail; + } + bundle.setOutputTextSymbols(argv[0]); + } else if (strcmp(cp, "-product") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--product' option\n"); + wantUsage = true; + goto bail; + } + bundle.setProduct(argv[0]); + } else if (strcmp(cp, "-non-constant-id") == 0) { + bundle.setNonConstantId(true); + } else if (strcmp(cp, "-no-crunch") == 0) { + bundle.setUseCrunchCache(true); + } else if (strcmp(cp, "-ignore-assets") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--ignore-assets' option\n"); + wantUsage = true; + goto bail; + } + gUserIgnoreAssets = argv[0]; + } else { + fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp); + wantUsage = true; + goto bail; + } + cp += strlen(cp) - 1; + break; + default: + fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp); + wantUsage = true; + goto bail; + } + + cp++; + } + argc--; + argv++; + } + + /* + * We're past the flags. The rest all goes straight in. + */ + bundle.setFileSpec(argv, argc); + + result = handleCommand(&bundle); + +bail: + if (wantUsage) { + usage(); + result = 2; + } + + //printf("--> returning %d\n", result); + return result; +} diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h new file mode 100644 index 0000000..a6b39ac --- /dev/null +++ b/tools/aapt/Main.h @@ -0,0 +1,63 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Some global defines that don't really merit their own header. +// +#ifndef __MAIN_H +#define __MAIN_H + +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> +#include "Bundle.h" +#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 doSingleCrunch(Bundle* bundle); + +extern int calcPercent(long uncompressedLen, long compressedLen); + +extern android::status_t writeAPK(Bundle* bundle, + 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); + +extern android::status_t writeResourceSymbols(Bundle* bundle, + const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate); + +extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets); + +extern bool isValidResourceType(const String8& type); + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); + +extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets); + +int dumpResources(Bundle* bundle); + +String8 getAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, String8* outError); + +status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, + FILE* fp, bool includeRaw); +#endif // __MAIN_H diff --git a/tools/aapt/NOTICE b/tools/aapt/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/tools/aapt/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp new file mode 100644 index 0000000..872d95c --- /dev/null +++ b/tools/aapt/Package.cpp @@ -0,0 +1,505 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Package assets into Zip files. +// +#include "Main.h" +#include "AaptAssets.h" +#include "ResourceTable.h" +#include "ResourceFilter.h" + +#include <androidfw/misc.h> + +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> +#include <utils/misc.h> + +#include <sys/types.h> +#include <dirent.h> +#include <ctype.h> +#include <errno.h> + +using namespace android; + +static const char* kExcludeExtension = ".EXCLUDE"; + +/* these formats are already compressed, or don't compress well */ +static const char* kNoCompressExt[] = { + ".jpg", ".jpeg", ".png", ".gif", + ".wav", ".mp2", ".mp3", ".ogg", ".aac", + ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", + ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", + ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", + ".amr", ".awb", ".wma", ".wmv" +}; + +/* fwd decls, so I can write this downward */ +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, + const AaptGroupEntry& ge, const ResourceFilter* filter); +bool processFile(Bundle* bundle, ZipFile* zip, + const sp<AaptGroup>& group, const sp<AaptFile>& file); +bool okayToCompress(Bundle* bundle, const String8& pathName); +ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); + +/* + * 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, 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; + + //bundle->setPackageCount(0); + + /* + * Prep the Zip archive. + * + * If the file already exists, fail unless "update" or "force" is set. + * If "update" is set, update the contents of the existing archive. + * Else, if "force" is set, remove the existing archive. + */ + FileType fileType = getFileType(outputFile.string()); + if (fileType == kFileTypeNonexistent) { + // okay, create it below + } else if (fileType == kFileTypeRegular) { + if (bundle->getUpdate()) { + // okay, open it below + } else if (bundle->getForce()) { + if (unlink(outputFile.string()) != 0) { + fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(), + strerror(errno)); + goto bail; + } + } else { + fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n", + outputFile.string()); + goto bail; + } + } else { + fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string()); + goto bail; + } + + if (bundle->getVerbose()) { + printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening", + outputFile.string()); + } + + status_t status; + zip = new ZipFile; + status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate); + if (status != NO_ERROR) { + fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n", + outputFile.string()); + goto bail; + } + + if (bundle->getVerbose()) { + printf("Writing all files...\n"); + } + + count = processAssets(bundle, zip, assets); + 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; + + /* + * 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; + } + + /* anything here? */ + if (zip->getNumEntries() == 0) { + if (bundle->getVerbose()) { + printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string()); + } + delete zip; // close the file so we can remove it in Win32 + zip = NULL; + if (unlink(outputFile.string()) != 0) { + fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string()); + } + } + + // If we've been asked to generate a dependency file for the .ap_ package, + // do so here + if (bundle->getGenDependencies()) { + // The dependency file gets output to the same directory + // as the specified output file with an additional .d extension. + // e.g. bin/resources.ap_.d + String8 dependencyFile = outputFile; + dependencyFile.append(".d"); + + FILE* fp = fopen(dependencyFile.string(), "a"); + // Add this file to the dependency file + fprintf(fp, "%s \\\n", outputFile.string()); + fclose(fp); + } + + assert(result == NO_ERROR); + +bail: + delete zip; // must close before remove in Win32 + if (result != NO_ERROR) { + if (bundle->getVerbose()) { + printf("Removing %s due to earlier failures\n", outputFile.string()); + } + if (unlink(outputFile.string()) != 0) { + fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string()); + } + } + + 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; +} + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, + const sp<AaptAssets>& assets) +{ + ResourceFilter filter; + status_t status = filter.parse(bundle->getConfigurations()); + if (status != NO_ERROR) { + return -1; + } + + ssize_t count = 0; + + const size_t N = assets->getGroupEntries().size(); + for (size_t i=0; i<N; i++) { + const AaptGroupEntry& ge = assets->getGroupEntries()[i]; + + ssize_t res = processAssets(bundle, zip, assets, ge, &filter); + if (res < 0) { + return res; + } + + count += res; + } + + return count; +} + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, + const AaptGroupEntry& ge, const ResourceFilter* filter) +{ + ssize_t count = 0; + + const size_t ND = dir->getDirs().size(); + size_t i; + for (i=0; i<ND; i++) { + const sp<AaptDir>& subDir = dir->getDirs().valueAt(i); + + const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0; + + if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) { + continue; + } + + ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL); + if (res < 0) { + return res; + } + count += res; + } + + if (filter != NULL && !filter->match(ge.toParams())) { + return count; + } + + const size_t NF = dir->getFiles().size(); + for (i=0; i<NF; i++) { + sp<AaptGroup> gp = dir->getFiles().valueAt(i); + ssize_t fi = gp->getFiles().indexOfKey(ge); + if (fi >= 0) { + sp<AaptFile> fl = gp->getFiles().valueAt(fi); + if (!processFile(bundle, zip, gp, fl)) { + return UNKNOWN_ERROR; + } + count++; + } + } + + return count; +} + +/* + * Process a regular file, adding it to the archive if appropriate. + * + * If we're in "update" mode, and the file already exists in the archive, + * delete the existing entry before adding the new one. + */ +bool processFile(Bundle* bundle, ZipFile* zip, + const sp<AaptGroup>& group, const sp<AaptFile>& file) +{ + const bool hasData = file->hasData(); + + String8 storageName(group->getPath()); + storageName.convertToResPath(); + ZipEntry* entry; + bool fromGzip = false; + status_t result; + + /* + * See if the filename ends in ".EXCLUDE". We can't use + * String8::getPathExtension() because the length of what it considers + * to be an extension is capped. + * + * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives, + * so there's no value in adding them (and it makes life easier on + * the AssetManager lib if we don't). + * + * NOTE: this restriction has been removed. If you're in this code, you + * should clean this up, but I'm in here getting rid of Path Name, and I + * don't want to make other potentially breaking changes --joeo + */ + int fileNameLen = storageName.length(); + int excludeExtensionLen = strlen(kExcludeExtension); + if (fileNameLen > excludeExtensionLen + && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen), + kExcludeExtension))) { + fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.string()); + return true; + } + + 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; + } + if (!hasData) { + const String8& srcName = file->getSourceFile(); + time_t fileModWhen; + fileModWhen = getFileModDate(srcName.string()); + if (fileModWhen == (time_t) -1) { // file existence tested earlier, + return false; // not expecting an error here + } + + if (fileModWhen > entry->getModWhen()) { + // mark as deleted so add() will succeed + if (bundle->getVerbose()) { + printf(" (removing old '%s')\n", storageName.string()); + } + + zip->remove(entry); + } else { + // version in archive is newer + if (bundle->getVerbose()) { + printf(" (not updating '%s')\n", storageName.string()); + } + entry->setMarked(true); + return true; + } + } else { + // Generated files are always replaced. + zip->remove(entry); + } + } + } + + //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); + + if (fromGzip) { + result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry); + } else if (!hasData) { + /* don't compress certain files, e.g. PNGs */ + int compressionMethod = bundle->getCompressionMethod(); + if (!okayToCompress(bundle, storageName)) { + compressionMethod = ZipEntry::kCompressStored; + } + result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod, + &entry); + } else { + 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. + */ +bool okayToCompress(Bundle* bundle, const String8& pathName) +{ + String8 ext = pathName.getPathExtension(); + int i; + + if (ext.length() == 0) + return true; + + for (i = 0; i < NELEM(kNoCompressExt); i++) { + if (strcasecmp(ext.string(), kNoCompressExt[i]) == 0) + return false; + } + + const android::Vector<const char*>& others(bundle->getNoCompressExtensions()); + for (i = 0; i < (int)others.size(); i++) { + const char* str = others[i]; + int pos = pathName.length() - strlen(str); + if (pos < 0) { + continue; + } + const char* path = pathName.string(); + if (strcasecmp(path + pos, str) == 0) { + return false; + } + } + + return true; +} + +bool endsWith(const char* haystack, const char* needle) +{ + size_t a = strlen(haystack); + size_t b = strlen(needle); + if (a < b) return false; + return strcasecmp(haystack+(a-b), needle) == 0; +} + +ssize_t processJarFile(ZipFile* jar, ZipFile* out) +{ + status_t err; + size_t N = jar->getNumEntries(); + size_t count = 0; + for (size_t i=0; i<N; i++) { + ZipEntry* entry = jar->getEntryByIndex(i); + const char* storageName = entry->getFileName(); + if (endsWith(storageName, ".class")) { + int compressionMethod = entry->getCompressionMethod(); + size_t size = entry->getUncompressedLen(); + const void* data = jar->uncompress(entry); + if (data == NULL) { + fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n", + storageName); + return -1; + } + out->add(data, size, storageName, compressionMethod, NULL); + free((void*)data); + } + count++; + } + return count; +} + +ssize_t processJarFiles(Bundle* bundle, ZipFile* zip) +{ + status_t err; + ssize_t count = 0; + const android::Vector<const char*>& jars = bundle->getJarFiles(); + + size_t N = jars.size(); + for (size_t i=0; i<N; i++) { + ZipFile jar; + err = jar.open(jars[i], ZipFile::kOpenReadOnly); + if (err != 0) { + fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %d\n", + jars[i], err); + return err; + } + err += processJarFile(&jar, zip); + if (err < 0) { + fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]); + return err; + } + count += err; + } + + return count; +} diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp new file mode 100644 index 0000000..d617928 --- /dev/null +++ b/tools/aapt/Resource.cpp @@ -0,0 +1,2676 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// +#include "Main.h" +#include "AaptAssets.h" +#include "StringPool.h" +#include "XMLNode.h" +#include "ResourceTable.h" +#include "Images.h" + +#include "CrunchCache.h" +#include "FileFinder.h" +#include "CacheUpdater.h" + +#include "WorkQueue.h" + +#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; + +// ========================================================================== +// ========================================================================== +// ========================================================================== + +class PackageInfo +{ +public: + PackageInfo() + { + } + ~PackageInfo() + { + } + + status_t parsePackage(const sp<AaptGroup>& grp); +}; + +// ========================================================================== +// ========================================================================== +// ========================================================================== + +static String8 parseResourceName(const String8& leaf) +{ + const char* firstDot = strchr(leaf.string(), '.'); + const char* str = leaf.string(); + + if (firstDot) { + return String8(str, firstDot-str); + } else { + return String8(str); + } +} + +ResourceTypeSet::ResourceTypeSet() + :RefBase(), + KeyedVector<String8,sp<AaptGroup> >() +{ +} + +FilePathStore::FilePathStore() + :RefBase(), + Vector<String8>() +{ +} + +class ResourceDirIterator +{ +public: + ResourceDirIterator(const sp<ResourceTypeSet>& set, const String8& resType) + : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0) + { + } + + inline const sp<AaptGroup>& getGroup() const { return mGroup; } + inline const sp<AaptFile>& getFile() const { return mFile; } + + inline const String8& getBaseName() const { return mBaseName; } + inline const String8& getLeafName() const { return mLeafName; } + inline String8 getPath() const { return mPath; } + inline const ResTable_config& getParams() const { return mParams; } + + enum { + EOD = 1 + }; + + ssize_t next() + { + while (true) { + sp<AaptGroup> group; + sp<AaptFile> file; + + // Try to get next file in this current group. + if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) { + group = mGroup; + file = group->getFiles().valueAt(mGroupPos++); + + // Try to get the next group/file in this directory + } else if (mSetPos < mSet->size()) { + mGroup = group = mSet->valueAt(mSetPos++); + if (group->getFiles().size() < 1) { + continue; + } + file = group->getFiles().valueAt(0); + mGroupPos = 1; + + // All done! + } else { + return EOD; + } + + mFile = file; + + 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 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.uiMode, + mParams.density, mParams.touchscreen, mParams.keyboard, + mParams.inputFlags, mParams.navigation)); + mPath = "res"; + mPath.appendPath(file->getGroupEntry().toDirName(mResType)); + mPath.appendPath(leaf); + mBaseName = parseResourceName(leaf); + if (mBaseName == "") { + fprintf(stderr, "Error: malformed resource filename %s\n", + file->getPrintableSource().string()); + return UNKNOWN_ERROR; + } + + NOISY(printf("file name=%s\n", mBaseName.string())); + + return NO_ERROR; + } + } + +private: + String8 mResType; + + const sp<ResourceTypeSet> mSet; + size_t mSetPos; + + sp<AaptGroup> mGroup; + size_t mGroupPos; + + sp<AaptFile> mFile; + String8 mBaseName; + String8 mLeafName; + String8 mPath; + ResTable_config mParams; +}; + +// ========================================================================== +// ========================================================================== +// ========================================================================== + +bool isValidResourceType(const String8& type) +{ + return type == "anim" || type == "animator" || type == "interpolator" + || type == "transition" || type == "scene" + || type == "drawable" || type == "layout" + || type == "values" || type == "xml" || type == "raw" + || type == "color" || type == "menu" || type == "mipmap"; +} + +static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true) +{ + 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) +{ + if (grp->getFiles().size() != 1) { + fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n", + grp->getFiles().valueAt(0)->getPrintableSource().string()); + } + + sp<AaptFile> file = grp->getFiles().valueAt(0); + + ResXMLTree block; + status_t err = parseXMLResource(file, &block); + if (err != NO_ERROR) { + return err; + } + //printXMLBlock(&block); + + ResXMLTree::event_code_t code; + while ((code=block.next()) != ResXMLTree::START_TAG + && code != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + } + + size_t len; + if (code != ResXMLTree::START_TAG) { + fprintf(stderr, "%s:%d: No start tag found\n", + file->getPrintableSource().string(), block.getLineNumber()); + return UNKNOWN_ERROR; + } + if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) { + fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n", + file->getPrintableSource().string(), block.getLineNumber(), + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + + ssize_t nameIndex = block.indexOfAttribute(NULL, "package"); + if (nameIndex < 0) { + fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n", + file->getPrintableSource().string(), block.getLineNumber()); + return UNKNOWN_ERROR; + } + + 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; +} + +// ========================================================================== +// ========================================================================== +// ========================================================================== + +static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets, + ResourceTable* table, + const sp<ResourceTypeSet>& set, + const char* resType) +{ + String8 type8(resType); + String16 type16(resType); + + bool hasErrors = false; + + ResourceDirIterator it(set, String8(resType)); + ssize_t res; + while ((res=it.next()) == NO_ERROR) { + if (bundle->getVerbose()) { + printf(" (new resource id %s from %s)\n", + it.getBaseName().string(), it.getFile()->getPrintableSource().string()); + } + String16 baseName(it.getBaseName()); + const char16_t* str = baseName.string(); + const char16_t* const end = str + baseName.size(); + while (str < end) { + if (!((*str >= 'a' && *str <= 'z') + || (*str >= '0' && *str <= '9') + || *str == '_' || *str == '.')) { + fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n", + it.getPath().string()); + hasErrors = true; + } + str++; + } + String8 resPath = it.getPath(); + resPath.convertToResPath(); + table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()), + type16, + baseName, + String16(resPath), + NULL, + &it.getParams()); + assets->addResource(it.getLeafName(), resPath, it.getFile(), type8); + } + + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +class PreProcessImageWorkUnit : public WorkQueue::WorkUnit { +public: + PreProcessImageWorkUnit(const Bundle* bundle, const sp<AaptAssets>& assets, + const sp<AaptFile>& 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 + } + +private: + const Bundle* mBundle; + sp<AaptAssets> mAssets; + sp<AaptFile> mFile; + volatile bool* mHasErrors; +}; + +static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& assets, + const sp<ResourceTypeSet>& 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<AaptAssets>& assets, + ResourceTable* table, + const sp<ResourceTypeSet>& 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) { + hasErrors = true; + } + } + + return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; +} + +static void collect_files(const sp<AaptDir>& dir, + KeyedVector<String8, sp<ResourceTypeSet> >* resources) +{ + const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles(); + int N = groups.size(); + for (int i=0; i<N; i++) { + String8 leafName = groups.keyAt(i); + const sp<AaptGroup>& group = groups.valueAt(i); + + const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files + = group->getFiles(); + + if (files.size() == 0) { + continue; + } + + String8 resType = files.valueAt(0)->getResourceType(); + + ssize_t index = resources->indexOfKey(resType); + + if (index < 0) { + sp<ResourceTypeSet> 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<ResourceTypeSet> 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<AaptGroup> existingGroup = set->valueAt(index); + 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; j<files.size(); j++) { + NOISY(printf("Adding file %s in group %s resType %s\n", + files.valueAt(j)->getSourceFile().string(), + files.keyAt(j).toDirName(String8()).string(), + resType.string())); + status_t err = existingGroup->addFile(files.valueAt(j)); + } + } + } + } +} + +static void collect_files(const sp<AaptAssets>& ass, + KeyedVector<String8, sp<ResourceTypeSet> >* resources) +{ + const Vector<sp<AaptDir> >& dirs = ass->resDirs(); + int N = dirs.size(); + + for (int i=0; i<N; i++) { + sp<AaptDir> 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()); + } +} + +enum { + ATTR_OKAY = -1, + ATTR_NOT_FOUND = -2, + ATTR_LEADING_SPACES = -3, + ATTR_TRAILING_SPACES = -4 +}; +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; + 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<len; i++) { + uint16_t c = str[i]; + const char* p = validChars; + bool okay = false; + while (*p) { + if (c == *p) { + okay = true; + break; + } + p++; + } + if (!okay) { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid character '%c'.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr, (char)str[i]); + return (int)i; + } + } + } + if (*str == ' ') { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not start with a space.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr); + return ATTR_LEADING_SPACES; + } + if (str[len-1] == ' ') { + fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not end with a space.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr); + return ATTR_TRAILING_SPACES; + } + return ATTR_OKAY; + } + if (required) { + fprintf(stderr, "%s:%d: Tag <%s> missing required attribute %s.\n", + path.string(), parser.getLineNumber(), + String8(parser.getElementName(&len)).string(), attr); + return ATTR_NOT_FOUND; + } + return ATTR_OKAY; +} + +static void checkForIds(const String8& path, ResXMLParser& parser) +{ + ResXMLTree::event_code_t code; + while ((code=parser.next()) != ResXMLTree::END_DOCUMENT + && code > ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + ssize_t index = parser.indexOfAttribute(NULL, "id"); + if (index >= 0) { + fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n", + path.string(), parser.getLineNumber()); + } + } + } +} + +static bool applyFileOverlay(Bundle *bundle, + const sp<AaptAssets>& assets, + sp<ResourceTypeSet> *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<AaptAssets> overlay = assets->getOverlay(); + String8 resTypeString(resType); + + // work through the linked list of overlays + while (overlay.get()) { + KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources(); + + // get the overlay resources of the requested type + ssize_t index = overlayRes->indexOfKey(resTypeString); + if (index >= 0) { + sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index); + + // for each of the resources, check for a match in the previously built + // non-overlay "baseset". + size_t overlayCount = overlaySet->size(); + for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) { + 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<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex); + sp<AaptGroup> baseGroup = (*baseSet)->valueAt(baseIndex); + + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles = + overlayGroup->getFiles(); + if (bundle->getVerbose()) { + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > 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; + overlayGroupIndex<overlayGroupSize; + overlayGroupIndex++) { + size_t baseFileIndex = + baseGroup->getFiles().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) + (*baseSet)->add(overlaySet->keyAt(overlayIndex), + overlaySet->valueAt(overlayIndex)); + // make sure all flavors are defined in the resources. + sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex); + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles = + overlayGroup->getFiles(); + size_t overlayGroupSize = overlayFiles.size(); + for (size_t overlayGroupIndex = 0; + overlayGroupIndex<overlayGroupSize; + overlayGroupIndex++) { + assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex)); + } + } + } + // this overlay didn't have resources for this type + } + // try next overlay + overlay = overlay->getOverlay(); + } + return true; +} + +/* + * Inserts an attribute in a given node, only if the attribute does not + * exist. + * If errorOnFailedInsert is true, and the attribute already exists, returns false. + * Returns true otherwise, even if the attribute already exists. + */ +bool addTagAttribute(const sp<XMLNode>& node, const char* ns8, + const char* attr8, const char* value, bool errorOnFailedInsert) +{ + if (value == NULL) { + return true; + } + + const String16 ns(ns8); + const String16 attr(attr8); + + if (node->getAttribute(ns, attr) != NULL) { + if (errorOnFailedInsert) { + fprintf(stderr, "Error: AndroidManifest.xml already defines %s (in %s);" + " cannot insert new value %s.\n", + String8(attr).string(), String8(ns).string(), value); + return false; + } + + fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s);" + " using existing value in manifest.\n", + String8(attr).string(), String8(ns).string()); + + // don't stop the build. + return true; + } + + node->addAttribute(ns, attr, String16(value)); + return true; +} + +static void fullyQualifyClassName(const String8& package, sp<XMLNode> 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<XMLNode> root) +{ + root = root->searchElement(String16(), String16("manifest")); + if (root == NULL) { + fprintf(stderr, "No <manifest> tag.\n"); + return UNKNOWN_ERROR; + } + + bool errorOnFailedInsert = bundle->getErrorOnFailedInsert(); + + if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode", + bundle->getVersionCode(), errorOnFailedInsert)) { + return UNKNOWN_ERROR; + } + if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName", + bundle->getVersionName(), errorOnFailedInsert)) { + return UNKNOWN_ERROR; + } + + if (bundle->getMinSdkVersion() != NULL + || bundle->getTargetSdkVersion() != NULL + || bundle->getMaxSdkVersion() != NULL) { + sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk")); + if (vers == NULL) { + vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk")); + root->insertChildAt(vers, 0); + } + + if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion", + bundle->getMinSdkVersion(), errorOnFailedInsert)) { + return UNKNOWN_ERROR; + } + if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "targetSdkVersion", + bundle->getTargetSdkVersion(), errorOnFailedInsert)) { + return UNKNOWN_ERROR; + } + if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "maxSdkVersion", + bundle->getMaxSdkVersion(), errorOnFailedInsert)) { + return UNKNOWN_ERROR; + } + } + + if (bundle->getDebugMode()) { + sp<XMLNode> application = root->getChildElement(String16(), String16("application")); + if (application != NULL) { + if (!addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable", "true", + errorOnFailedInsert)) { + return UNKNOWN_ERROR; + } + } + } + + // 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<XMLNode> application = root->getChildElement(String16(), String16("application")); + if (application != NULL) { + fullyQualifyClassName(origPackage, application, String16("name")); + fullyQualifyClassName(origPackage, application, String16("backupAgent")); + + Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(application->getChildren()); + for (size_t i = 0; i < children.size(); i++) { + sp<XMLNode> 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<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(root->getChildren()); + for (size_t i = 0; i < children.size(); i++) { + sp<XMLNode> 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) \ + do { \ + ssize_t index = resources->indexOfKey(String8(#n)); \ + if (index >= 0) { \ + n ## s = resources->valueAt(index); \ + } \ + } 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 + // be able to generate the resource information. + sp<AaptGroup> androidManifestFile = + assets->getFiles().valueFor(String8("AndroidManifest.xml")); + if (androidManifestFile == NULL) { + fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n"); + return UNKNOWN_ERROR; + } + + status_t err = parsePackage(bundle, assets, androidManifestFile); + if (err != NO_ERROR) { + return err; + } + + NOISY(printf("Creating resources for package %s\n", + assets->getPackage().string())); + + ResourceTable table(bundle, String16(assets->getPackage())); + err = table.addIncludedResources(bundle, assets); + if (err != NO_ERROR) { + return err; + } + + 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. + // -------------------------------------------------------------- + + // resType -> leafName -> group + KeyedVector<String8, sp<ResourceTypeSet> > *resources = + new KeyedVector<String8, sp<ResourceTypeSet> >; + collect_files(assets, resources); + + sp<ResourceTypeSet> drawables; + sp<ResourceTypeSet> layouts; + sp<ResourceTypeSet> anims; + sp<ResourceTypeSet> animators; + sp<ResourceTypeSet> interpolators; + sp<ResourceTypeSet> transitions; + sp<ResourceTypeSet> scenes; + sp<ResourceTypeSet> xmls; + sp<ResourceTypeSet> raws; + sp<ResourceTypeSet> colors; + sp<ResourceTypeSet> menus; + sp<ResourceTypeSet> mipmaps; + + ASSIGN_IT(drawable); + ASSIGN_IT(layout); + ASSIGN_IT(anim); + ASSIGN_IT(animator); + ASSIGN_IT(interpolator); + ASSIGN_IT(transition); + ASSIGN_IT(scene); + 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 + sp<AaptAssets> current = assets->getOverlay(); + while(current.get()) { + KeyedVector<String8, sp<ResourceTypeSet> > *resources = + new KeyedVector<String8, sp<ResourceTypeSet> >; + current->setResources(resources); + collect_files(current, resources); + current = current->getOverlay(); + } + // apply the overlay files to the base set + 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, &transitions, "transition") || + !applyFileOverlay(bundle, assets, &scenes, "scene") || + !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) { + 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) { + hasErrors = true; + } + } else { + hasErrors = true; + } + } + + 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) { + hasErrors = true; + } + } + + if (anims != NULL) { + err = makeFileResources(bundle, assets, &table, anims, "anim"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (animators != NULL) { + err = makeFileResources(bundle, assets, &table, animators, "animator"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (transitions != NULL) { + err = makeFileResources(bundle, assets, &table, transitions, "transition"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (scenes != NULL) { + err = makeFileResources(bundle, assets, &table, scenes, "scene"); + 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) { + hasErrors = true; + } + } + + if (raws != NULL) { + err = makeFileResources(bundle, assets, &table, raws, "raw"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + // compile resources + current = assets; + while(current.get()) { + KeyedVector<String8, sp<ResourceTypeSet> > *resources = + current->getResources(); + + ssize_t index = resources->indexOfKey(String8("values")); + if (index >= 0) { + ResourceDirIterator it(resources->valueAt(index), String8("values")); + ssize_t res; + while ((res=it.next()) == NO_ERROR) { + sp<AaptFile> file = it.getFile(); + res = compileResourceFile(bundle, assets, file, it.getParams(), + (current!=assets), &table); + if (res != NO_ERROR) { + hasErrors = true; + } + } + } + current = current->getOverlay(); + } + + if (colors != NULL) { + err = makeFileResources(bundle, assets, &table, colors, "color"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (menus != NULL) { + err = makeFileResources(bundle, assets, &table, menus, "menu"); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + // -------------------------------------------------------------------- + // Assignment of resource IDs and initial generation of resource table. + // -------------------------------------------------------------------- + + if (table.hasResources()) { + sp<AaptFile> resFile(getResourceFile(assets)); + if (resFile == NULL) { + fprintf(stderr, "Error: unable to generate entry for resource data\n"); + return UNKNOWN_ERROR; + } + + err = table.assignResourceIds(); + if (err < NO_ERROR) { + return err; + } + } + + // -------------------------------------------------------------- + // Finally, we can now we can compile XML files, which may reference + // resources. + // -------------------------------------------------------------- + + if (layouts != NULL) { + ResourceDirIterator it(layouts, String8("layout")); + while ((err=it.next()) == NO_ERROR) { + String8 src = it.getFile()->getPrintableSource(); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + if (err == NO_ERROR) { + ResXMLTree block; + block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); + checkForIds(src, block); + } else { + hasErrors = true; + } + } + + if (err < NO_ERROR) { + hasErrors = true; + } + err = NO_ERROR; + } + + if (anims != NULL) { + ResourceDirIterator it(anims, String8("anim")); + 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 (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; + } + } + + if (err < NO_ERROR) { + hasErrors = true; + } + err = NO_ERROR; + } + + if (transitions != NULL) { + ResourceDirIterator it(transitions, String8("transition")); + 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 (scenes != NULL) { + ResourceDirIterator it(scenes, String8("scene")); + 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 (xmls != NULL) { + ResourceDirIterator it(xmls, String8("xml")); + 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 (drawables != NULL) { + err = postProcessImages(assets, &table, drawables); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (colors != NULL) { + ResourceDirIterator it(colors, String8("color")); + 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 (menus != NULL) { + ResourceDirIterator it(menus, String8("menu")); + while ((err=it.next()) == NO_ERROR) { + String8 src = it.getFile()->getPrintableSource(); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + if (err != NO_ERROR) { + hasErrors = true; + } + ResXMLTree block; + block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); + checkForIds(src, block); + } + + if (err < NO_ERROR) { + hasErrors = true; + } + err = NO_ERROR; + } + + if (table.validateLocalizations()) { + hasErrors = true; + } + + if (hasErrors) { + return UNKNOWN_ERROR; + } + + const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0)); + String8 manifestPath(manifestFile->getPrintableSource()); + + // Generate final compiled manifest file. + manifestFile->clearData(); + sp<XMLNode> 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<AaptFile> resFile; + + if (table.hasResources()) { + sp<AaptSymbols> 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. + sp<AaptFile> 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(outManifestFile->getData(), outManifestFile->getSize(), true); + String16 manifest16("manifest"); + String16 permission16("permission"); + String16 permission_group16("permission-group"); + String16 uses_permission16("uses-permission"); + String16 instrumentation16("instrumentation"); + String16 application16("application"); + String16 provider16("provider"); + String16 service16("service"); + String16 receiver16("receiver"); + String16 activity16("activity"); + String16 action16("action"); + String16 category16("category"); + String16 data16("scheme"); + const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789"; + const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-"; + const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$"; + const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:"; + const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;"; + const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+"; + const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-"; + ResXMLTree::event_code_t code; + sp<AaptSymbols> permissionSymbols; + sp<AaptSymbols> permissionGroupSymbols; + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code > ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + size_t len; + if (block.getElementNamespace(&len) != NULL) { + continue; + } + if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) { + 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, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", isGroup ? packageIdentCharsWithTheStupid + : packageIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + SourcePos srcPos(manifestPath, block.getLineNumber()); + sp<AaptSymbols> syms; + if (!isGroup) { + syms = permissionSymbols; + if (syms == NULL) { + sp<AaptSymbols> symbols = + assets->getSymbolsFor(String8("Manifest")); + syms = permissionSymbols = symbols->addNestedSymbol( + String8("permission"), srcPos); + } + } else { + syms = permissionGroupSymbols; + if (syms == NULL) { + sp<AaptSymbols> symbols = + assets->getSymbolsFor(String8("Manifest")); + syms = permissionGroupSymbols = symbols->addNestedSymbol( + String8("permission_group"), srcPos); + } + } + size_t len; + ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name"); + const uint16_t* id = block.getAttributeStringValue(index, &len); + if (id == NULL) { + fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", + manifestPath.string(), block.getLineNumber(), + String8(block.getElementName(&len)).string()); + hasErrors = true; + break; + } + String8 idStr(id); + char* p = idStr.lockBuffer(idStr.size()); + char* e = p + idStr.size(); + bool begins_with_digit = true; // init to true so an empty string fails + while (e > p) { + e--; + if (*e >= '0' && *e <= '9') { + begins_with_digit = true; + continue; + } + if ((*e >= 'a' && *e <= 'z') || + (*e >= 'A' && *e <= 'Z') || + (*e == '_')) { + begins_with_digit = false; + continue; + } + if (isGroup && (*e == '-')) { + *e = '_'; + begins_with_digit = false; + continue; + } + e++; + break; + } + idStr.unlockBuffer(); + // verify that we stopped because we hit a period or + // the beginning of the string, and that the + // identifier didn't begin with a digit. + if (begins_with_digit || (e != p && *(e-1) != '.')) { + fprintf(stderr, + "%s:%d: Permission name <%s> is not a valid Java symbol\n", + manifestPath.string(), block.getLineNumber(), idStr.string()); + hasErrors = true; + } + syms->addStringSymbol(String8(e), idStr, srcPos); + const uint16_t* cmt = block.getComment(&len); + if (cmt != NULL && *cmt != 0) { + //printf("Comment of %s: %s\n", String8(e).string(), + // String8(cmt).string()); + syms->appendComment(String8(e), String16(cmt), srcPos); + } else { + //printf("No comment for %s\n", String8(e).string()); + } + syms->makeSymbolPublic(String8(e), srcPos); + } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) { + 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, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + 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, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "permission", + packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "process", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + 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, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "authorities", + authoritiesIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "permission", + packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "process", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + } 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, finalResTable, block, RESOURCES_ANDROID_NAMESPACE, + "name", classIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "permission", + packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "process", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + 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, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "name", + packageIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) { + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "mimeType", + typeIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, finalResTable, block, + RESOURCES_ANDROID_NAMESPACE, "scheme", + schemeIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } + } + } + + if (resFile != NULL) { + // These resources are now considered to be a part of the included + // resources, for others to reference. + err = assets->addIncludedResources(resFile); + if (err < NO_ERROR) { + fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n"); + return err; + } + } + + return err; +} + +static const char* getIndentSpace(int indent) +{ +static const char whitespace[] = +" "; + + return whitespace + sizeof(whitespace) - 1 - indent*4; +} + +static String8 flattenSymbol(const String8& symbol) { + String8 result(symbol); + ssize_t first; + if ((first = symbol.find(":", 0)) >= 0 + || (first = symbol.find(".", 0)) >= 0) { + size_t size = symbol.size(); + char* buf = result.lockBuffer(size); + for (size_t i = first; i < size; i++) { + if (buf[i] == ':' || buf[i] == '.') { + buf[i] = '_'; + } + } + result.unlockBuffer(size); + } + return result; +} + +static String8 getSymbolPackage(const String8& symbol, const sp<AaptAssets>& assets, bool pub) { + ssize_t colon = symbol.find(":", 0); + if (colon >= 0) { + return String8(symbol.string(), colon); + } + return pub ? assets->getPackage() : assets->getSymbolsPrivatePackage(); +} + +static String8 getSymbolName(const String8& symbol) { + ssize_t colon = symbol.find(":", 0); + if (colon >= 0) { + return String8(symbol.string() + colon + 1); + } + return symbol; +} + +static String16 getAttributeComment(const sp<AaptAssets>& assets, + const String8& name, + String16* outTypeComment = NULL) +{ + sp<AaptSymbols> asym = assets->getSymbolsFor(String8("R")); + if (asym != NULL) { + //printf("Got R symbols!\n"); + asym = asym->getNestedSymbols().valueFor(String8("attr")); + if (asym != NULL) { + //printf("Got attrs symbols! comment %s=%s\n", + // name.string(), String8(asym->getComment(name)).string()); + if (outTypeComment != NULL) { + *outTypeComment = asym->getTypeComment(name); + } + return asym->getComment(name); + } + } + return String16(); +} + +static status_t writeLayoutClasses( + FILE* fp, const sp<AaptAssets>& assets, + const sp<AaptSymbols>& symbols, int indent, bool includePrivate) +{ + const char* indentStr = getIndentSpace(indent); + if (!includePrivate) { + fprintf(fp, "%s/** @doconly */\n", indentStr); + } + fprintf(fp, "%spublic static final class styleable {\n", indentStr); + indent++; + + String16 attr16("attr"); + String16 package16(assets->getPackage()); + + indentStr = getIndentSpace(indent); + bool hasErrors = false; + + size_t i; + size_t N = symbols->getNestedSymbols().size(); + for (i=0; i<N; i++) { + sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i); + String8 realClassName(symbols->getNestedSymbols().keyAt(i)); + String8 nclassName(flattenSymbol(realClassName)); + + SortedVector<uint32_t> idents; + Vector<uint32_t> origOrder; + Vector<bool> publicFlags; + + size_t a; + size_t NA = nsymbols->getSymbols().size(); + for (a=0; a<NA; a++) { + const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a)); + int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32 + ? sym.int32Val : 0; + bool isPublic = true; + if (code == 0) { + String16 name16(sym.name); + uint32_t typeSpecFlags; + code = assets->getIncludedResources().identifierForName( + name16.string(), name16.size(), + attr16.string(), attr16.size(), + package16.string(), package16.size(), &typeSpecFlags); + if (code == 0) { + fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n", + nclassName.string(), sym.name.string()); + hasErrors = true; + } + isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0; + } + idents.add(code); + origOrder.add(code); + publicFlags.add(isPublic); + } + + NA = idents.size(); + + bool deprecated = false; + + String16 comment = symbols->getComment(realClassName); + fprintf(fp, "%s/** ", indentStr); + if (comment.size() > 0) { + 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()); + } + bool hasTable = false; + for (a=0; a<NA; a++) { + ssize_t pos = idents.indexOf(origOrder.itemAt(a)); + if (pos >= 0) { + if (!hasTable) { + hasTable = true; + fprintf(fp, + "%s <p>Includes the following attributes:</p>\n" + "%s <table>\n" + "%s <colgroup align=\"left\" />\n" + "%s <colgroup align=\"left\" />\n" + "%s <tr><th>Attribute</th><th>Description</th></tr>\n", + indentStr, + indentStr, + indentStr, + indentStr, + indentStr); + } + const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a); + if (!publicFlags.itemAt(a) && !includePrivate) { + continue; + } + String8 name8(sym.name); + String16 comment(sym.comment); + if (comment.size() <= 0) { + comment = getAttributeComment(assets, name8); + } + if (comment.size() > 0) { + const char16_t* p = comment.string(); + while (*p != 0 && *p != '.') { + if (*p == '{') { + while (*p != 0 && *p != '}') { + p++; + } + } else { + p++; + } + } + if (*p == '.') { + p++; + } + comment = String16(comment.string(), p-comment.string()); + } + fprintf(fp, "%s <tr><td><code>{@link #%s_%s %s:%s}</code></td><td>%s</td></tr>\n", + indentStr, nclassName.string(), + flattenSymbol(name8).string(), + getSymbolPackage(name8, assets, true).string(), + getSymbolName(name8).string(), + String8(comment).string()); + } + } + if (hasTable) { + fprintf(fp, "%s </table>\n", indentStr); + } + for (a=0; a<NA; a++) { + ssize_t pos = idents.indexOf(origOrder.itemAt(a)); + if (pos >= 0) { + const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a); + if (!publicFlags.itemAt(a) && !includePrivate) { + continue; + } + fprintf(fp, "%s @see #%s_%s\n", + indentStr, nclassName.string(), + flattenSymbol(sym.name).string()); + } + } + fprintf(fp, "%s */\n", getIndentSpace(indent)); + + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", indentStr); + } + + fprintf(fp, + "%spublic static final int[] %s = {\n" + "%s", + indentStr, nclassName.string(), + getIndentSpace(indent+1)); + + for (a=0; a<NA; a++) { + if (a != 0) { + if ((a&3) == 0) { + fprintf(fp, ",\n%s", getIndentSpace(indent+1)); + } else { + fprintf(fp, ", "); + } + } + fprintf(fp, "0x%08x", idents[a]); + } + + fprintf(fp, "\n%s};\n", indentStr); + + for (a=0; a<NA; a++) { + ssize_t pos = idents.indexOf(origOrder.itemAt(a)); + if (pos >= 0) { + const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a); + if (!publicFlags.itemAt(a) && !includePrivate) { + continue; + } + String8 name8(sym.name); + String16 comment(sym.comment); + String16 typeComment; + if (comment.size() <= 0) { + comment = getAttributeComment(assets, name8, &typeComment); + } else { + getAttributeComment(assets, name8, &typeComment); + } + + uint32_t typeSpecFlags = 0; + String16 name16(sym.name); + assets->getIncludedResources().identifierForName( + name16.string(), name16.size(), + attr16.string(), attr16.size(), + package16.string(), package16.size(), &typeSpecFlags); + //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 <p>\n%s @attr description\n", indentStr, indentStr); + fprintf(fp, "%s %s\n", indentStr, cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } + } else { + fprintf(fp, + "%s <p>This symbol is the offset where the {@link %s.R.attr#%s}\n" + "%s attribute's value can be found in the {@link #%s} array.\n", + indentStr, + getSymbolPackage(name8, assets, pub).string(), + getSymbolName(name8).string(), + indentStr, nclassName.string()); + } + if (typeComment.size() > 0) { + 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) { + fprintf(fp, + "%s <p>This corresponds to the global attribute\n" + "%s resource symbol {@link %s.R.attr#%s}.\n", + indentStr, indentStr, + getSymbolPackage(name8, assets, true).string(), + getSymbolName(name8).string()); + } else { + fprintf(fp, + "%s <p>This is a private symbol.\n", indentStr); + } + } + fprintf(fp, "%s @attr name %s:%s\n", indentStr, + getSymbolPackage(name8, assets, pub).string(), + getSymbolName(name8).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(), + flattenSymbol(name8).string(), (int)pos); + } + } + } + + indent--; + fprintf(fp, "%s};\n", getIndentSpace(indent)); + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +static status_t writeTextLayoutClasses( + FILE* fp, const sp<AaptAssets>& assets, + const sp<AaptSymbols>& symbols, bool includePrivate) +{ + String16 attr16("attr"); + String16 package16(assets->getPackage()); + + bool hasErrors = false; + + size_t i; + size_t N = symbols->getNestedSymbols().size(); + for (i=0; i<N; i++) { + sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i); + String8 realClassName(symbols->getNestedSymbols().keyAt(i)); + String8 nclassName(flattenSymbol(realClassName)); + + SortedVector<uint32_t> idents; + Vector<uint32_t> origOrder; + Vector<bool> publicFlags; + + size_t a; + size_t NA = nsymbols->getSymbols().size(); + for (a=0; a<NA; a++) { + const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a)); + int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32 + ? sym.int32Val : 0; + bool isPublic = true; + if (code == 0) { + String16 name16(sym.name); + uint32_t typeSpecFlags; + code = assets->getIncludedResources().identifierForName( + name16.string(), name16.size(), + attr16.string(), attr16.size(), + package16.string(), package16.size(), &typeSpecFlags); + if (code == 0) { + fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n", + nclassName.string(), sym.name.string()); + hasErrors = true; + } + isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0; + } + idents.add(code); + origOrder.add(code); + publicFlags.add(isPublic); + } + + NA = idents.size(); + + fprintf(fp, "int[] styleable %s {", nclassName.string()); + + for (a=0; a<NA; a++) { + if (a != 0) { + fprintf(fp, ","); + } + fprintf(fp, " 0x%08x", idents[a]); + } + + fprintf(fp, " }\n"); + + for (a=0; a<NA; a++) { + ssize_t pos = idents.indexOf(origOrder.itemAt(a)); + if (pos >= 0) { + const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a); + if (!publicFlags.itemAt(a) && !includePrivate) { + continue; + } + String8 name8(sym.name); + String16 comment(sym.comment); + String16 typeComment; + if (comment.size() <= 0) { + comment = getAttributeComment(assets, name8, &typeComment); + } else { + getAttributeComment(assets, name8, &typeComment); + } + + uint32_t typeSpecFlags = 0; + String16 name16(sym.name); + assets->getIncludedResources().identifierForName( + name16.string(), name16.size(), + attr16.string(), attr16.size(), + package16.string(), package16.size(), &typeSpecFlags); + //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; + + fprintf(fp, + "int styleable %s_%s %d\n", + nclassName.string(), + flattenSymbol(name8).string(), (int)pos); + } + } + } + + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +static status_t writeSymbolClass( + FILE* fp, const sp<AaptAssets>& assets, bool includePrivate, + const sp<AaptSymbols>& symbols, const String8& className, int indent, + bool nonConstantId) +{ + fprintf(fp, "%spublic %sfinal class %s {\n", + getIndentSpace(indent), + indent != 0 ? "static " : "", className.string()); + indent++; + + 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; i<N; i++) { + const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); + if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) { + continue; + } + if (!assets->isJavaSymbol(sym, includePrivate)) { + continue; + } + String8 name8(sym.name); + 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), 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(), + String8(sym.name).string()); + } + String16 typeComment(sym.typeComment); + if (typeComment.size() > 0) { + String8 cmt(typeComment); + if (!haveComment) { + haveComment = true; + fprintf(fp, + "%s/** %s\n", getIndentSpace(indent), cmt.string()); + } else { + fprintf(fp, + "%s %s\n", getIndentSpace(indent), cmt.string()); + } + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } + } + if (haveComment) { + fprintf(fp,"%s */\n", getIndentSpace(indent)); + } + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); + } + fprintf(fp, id_format, + getIndentSpace(indent), + flattenSymbol(name8).string(), (int)sym.int32Val); + } + + for (i=0; i<N; i++) { + const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); + if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) { + continue; + } + if (!assets->isJavaSymbol(sym, includePrivate)) { + continue; + } + String8 name8(sym.name); + String16 comment(sym.comment); + bool deprecated = false; + if (comment.size() > 0) { + String8 cmt(comment); + fprintf(fp, + "%s/** %s\n" + "%s */\n", + 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), + flattenSymbol(name8).string(), sym.stringVal.string()); + } + + sp<AaptSymbols> styleableSymbols; + + N = symbols->getNestedSymbols().size(); + for (i=0; i<N; i++) { + sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i); + String8 nclassName(symbols->getNestedSymbols().keyAt(i)); + if (nclassName == "styleable") { + styleableSymbols = nsymbols; + } else { + err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId); + } + if (err != NO_ERROR) { + return err; + } + } + + if (styleableSymbols != NULL) { + err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate); + if (err != NO_ERROR) { + return err; + } + } + + indent--; + fprintf(fp, "%s}\n", getIndentSpace(indent)); + return NO_ERROR; +} + +static status_t writeTextSymbolClass( + FILE* fp, const sp<AaptAssets>& assets, bool includePrivate, + const sp<AaptSymbols>& symbols, const String8& className) +{ + size_t i; + status_t err = NO_ERROR; + + size_t N = symbols->getSymbols().size(); + for (i=0; i<N; i++) { + const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); + if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) { + continue; + } + + if (!assets->isJavaSymbol(sym, includePrivate)) { + continue; + } + + String8 name8(sym.name); + fprintf(fp, "int %s %s 0x%08x\n", + className.string(), + flattenSymbol(name8).string(), (int)sym.int32Val); + } + + N = symbols->getNestedSymbols().size(); + for (i=0; i<N; i++) { + sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i); + String8 nclassName(symbols->getNestedSymbols().keyAt(i)); + if (nclassName == "styleable") { + err = writeTextLayoutClasses(fp, assets, nsymbols, includePrivate); + } else { + err = writeTextSymbolClass(fp, assets, includePrivate, nsymbols, nclassName); + } + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, + const String8& package, bool includePrivate) +{ + if (!bundle->getRClassDir()) { + return NO_ERROR; + } + + const char* textSymbolsDest = bundle->getOutputTextSymbols(); + + String8 R("R"); + const size_t N = assets->getSymbols().size(); + for (size_t i=0; i<N; i++) { + sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i); + String8 className(assets->getSymbols().keyAt(i)); + String8 dest(bundle->getRClassDir()); + + if (bundle->getMakePackageDirs()) { + String8 pkg(package); + const char* last = pkg.string(); + const char* s = last-1; + do { + s++; + if (s > last && (*s == '.' || *s == 0)) { + String8 part(last, s-last); + dest.appendPath(part); +#ifdef HAVE_MS_C_RUNTIME + _mkdir(dest.string()); +#else + mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); +#endif + last = s+1; + } + } while (*s); + } + dest.appendPath(className); + dest.append(".java"); + FILE* fp = fopen(dest.string(), "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open class file %s: %s\n", + dest.string(), strerror(errno)); + return UNKNOWN_ERROR; + } + if (bundle->getVerbose()) { + printf(" Writing symbols for class %s.\n", className.string()); + } + + fprintf(fp, + "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" + " *\n" + " * This class was automatically generated by the\n" + " * aapt tool from the resource data it found. It\n" + " * should not be modified by hand.\n" + " */\n" + "\n" + "package %s;\n\n", package.string()); + + status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, + className, 0, bundle->getNonConstantId()); + if (err != NO_ERROR) { + return err; + } + fclose(fp); + + if (textSymbolsDest != NULL && R == className) { + String8 textDest(textSymbolsDest); + textDest.appendPath(className); + textDest.append(".txt"); + + FILE* fp = fopen(textDest.string(), "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open text symbol file %s: %s\n", + textDest.string(), strerror(errno)); + return UNKNOWN_ERROR; + } + if (bundle->getVerbose()) { + printf(" Writing text symbols for class %s.\n", className.string()); + } + + status_t err = writeTextSymbolClass(fp, assets, includePrivate, symbols, + className); + 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() && R == className) { + // Add this R.java to the dependency file + String8 dependencyFile(bundle->getRClassDir()); + dependencyFile.appendPath("R.java.d"); + + FILE *fp = fopen(dependencyFile.string(), "a"); + fprintf(fp,"%s \\\n", dest.string()); + fclose(fp); + } + } + + return NO_ERROR; +} + + +class ProguardKeepSet +{ +public: + // { rule --> { file locations } } + KeyedVector<String8, SortedVector<String8> > 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<String8>()); + } + 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 += " { <init>(...); }"; + + 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<AaptAssets>& assets) +{ + status_t err; + ResXMLTree tree; + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + bool inApplication = false; + String8 error; + sp<AaptGroup> assGroup; + sp<AaptFile> 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 <manifest> 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<AaptFile>& layoutFile, + const char* startTag, const KeyedVector<String8, Vector<NamespaceAttributePair> >* 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 Vector<NamespaceAttributePair>& nsAttrVector = tagAttrPairs->valueAt(tagIndex); + for (size_t i = 0; i < nsAttrVector.size(); i++) { + const NamespaceAttributePair& nsAttr = nsAttrVector[i]; + + 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<String8, Vector<NamespaceAttributePair> >* dest, + const char* tag, const char* ns, const char* attr) { + String8 tagStr(tag); + ssize_t index = dest->indexOfKey(tagStr); + + if (index < 0) { + Vector<NamespaceAttributePair> vector; + vector.add(NamespaceAttributePair(ns, attr)); + dest->add(tagStr, vector); + } else { + dest->editValueAt(index).add(NamespaceAttributePair(ns, attr)); + } +} + +status_t +writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) +{ + status_t err; + + // tag:attribute pairs that should be checked in layout files. + KeyedVector<String8, Vector<NamespaceAttributePair> > 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<String8, Vector<NamespaceAttributePair> > kXmlTagAttrPairs; + addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment"); + addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment"); + + const Vector<sp<AaptDir> >& dirs = assets->resDirs(); + const size_t K = dirs.size(); + for (size_t k=0; k<K; k++) { + const sp<AaptDir>& d = dirs.itemAt(k); + const String8& dirName = d->getLeaf(); + const char* startTag = NULL; + const KeyedVector<String8, Vector<NamespaceAttributePair> >* 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<String8,sp<AaptGroup> > groups = d->getFiles(); + const size_t N = groups.size(); + for (size_t i=0; i<N; i++) { + const sp<AaptGroup>& group = groups.valueAt(i); + const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles(); + const size_t M = files.size(); + for (size_t j=0; j<M; j++) { + err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs); + if (err < 0) { + return err; + } + } + } + } + // Handle the overlays + sp<AaptAssets> overlay = assets->getOverlay(); + if (overlay.get()) { + return writeProguardForLayouts(keep, overlay); + } + + return NO_ERROR; +} + +status_t +writeProguardFile(Bundle* bundle, const sp<AaptAssets>& 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<String8, SortedVector<String8> >& rules = keep.rules; + const size_t N = rules.size(); + for (size_t i=0; i<N; i++) { + const SortedVector<String8>& locations = rules.valueAt(i); + const size_t M = locations.size(); + for (size_t j=0; j<M; j++) { + fprintf(fp, "# %s\n", locations.itemAt(j).string()); + } + fprintf(fp, "%s\n\n", rules.keyAt(i).string()); + } + fclose(fp); + + return err; +} + +// Loops through the string paths and writes them to the file pointer +// Each file path is written on its own line with a terminating backslash. +status_t writePathsToFile(const sp<FilePathStore>& 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<AaptAssets>& assets, FILE* fp, bool includeRaw) +{ + status_t deps = -1; + deps += writePathsToFile(assets->getFullResPaths(), fp); + if (includeRaw) { + deps += writePathsToFile(assets->getFullAssetPaths(), fp); + } + return deps; +} diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp new file mode 100644 index 0000000..8cfd2a5 --- /dev/null +++ b/tools/aapt/ResourceFilter.cpp @@ -0,0 +1,112 @@ +// +// Copyright 2011 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "ResourceFilter.h" + +status_t +ResourceFilter::parse(const char* arg) +{ + if (arg == NULL) { + return 0; + } + + const char* p = arg; + const char* q; + + while (true) { + q = strchr(p, ','); + if (q == NULL) { + q = p + strlen(p); + } + + String8 part(p, q-p); + + if (part == "zz_ZZ") { + mContainsPseudo = true; + } + int axis; + uint32_t value; + if (AaptGroupEntry::parseNamePart(part, &axis, &value)) { + fprintf(stderr, "Invalid configuration: %s\n", arg); + fprintf(stderr, " "); + for (int i=0; i<p-arg; i++) { + fprintf(stderr, " "); + } + for (int i=0; i<q-p; i++) { + fprintf(stderr, "^"); + } + fprintf(stderr, "\n"); + return 1; + } + + ssize_t index = mData.indexOfKey(axis); + if (index < 0) { + mData.add(axis, SortedVector<uint32_t>()); + } + SortedVector<uint32_t>& sv = mData.editValueFor(axis); + sv.add(value); + // if it's a locale with a region, also match an unmodified locale of the + // same language + if (axis == AXIS_LANGUAGE) { + if (value & 0xffff0000) { + sv.add(value & 0x0000ffff); + } + } + p = q; + if (!*p) break; + p++; + } + + return NO_ERROR; +} + +bool +ResourceFilter::isEmpty() const +{ + return mData.size() == 0; +} + +bool +ResourceFilter::match(int axis, uint32_t value) const +{ + if (value == 0) { + // they didn't specify anything so take everything + return true; + } + ssize_t index = mData.indexOfKey(axis); + if (index < 0) { + // we didn't request anything on this axis so take everything + return true; + } + const SortedVector<uint32_t>& sv = mData.valueAt(index); + return sv.indexOf(value) >= 0; +} + +bool +ResourceFilter::match(int axis, const ResTable_config& config) const +{ + return match(axis, AaptGroupEntry::getConfigValueForAxis(config, axis)); +} + +bool +ResourceFilter::match(const ResTable_config& config) const +{ + for (int i=AXIS_START; i<=AXIS_END; i++) { + if (!match(i, AaptGroupEntry::getConfigValueForAxis(config, i))) { + return false; + } + } + return true; +} + +const SortedVector<uint32_t>* ResourceFilter::configsForAxis(int axis) const +{ + ssize_t index = mData.indexOfKey(axis); + if (index < 0) { + return NULL; + } + return &mData.valueAt(index); +} diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h new file mode 100644 index 0000000..647b7bb --- /dev/null +++ b/tools/aapt/ResourceFilter.h @@ -0,0 +1,33 @@ +// +// Copyright 2011 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#ifndef RESOURCE_FILTER_H +#define RESOURCE_FILTER_H + +#include "AaptAssets.h" + +/** + * Implements logic for parsing and handling "-c" and "--preferred-configurations" + * options. + */ +class ResourceFilter +{ +public: + ResourceFilter() : mData(), mContainsPseudo(false) {} + status_t parse(const char* arg); + bool isEmpty() const; + bool match(int axis, uint32_t value) const; + bool match(int axis, const ResTable_config& config) const; + bool match(const ResTable_config& config) const; + const SortedVector<uint32_t>* configsForAxis(int axis) const; + inline bool containsPseudo() const { return mContainsPseudo; } + +private: + KeyedVector<int,SortedVector<uint32_t> > mData; + bool mContainsPseudo; +}; + +#endif diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp new file mode 100644 index 0000000..e03f4f6 --- /dev/null +++ b/tools/aapt/ResourceIdCache.cpp @@ -0,0 +1,107 @@ +// +// Copyright 2012 The Android Open Source Project +// +// Manage a resource ID cache. + +#define LOG_TAG "ResourceIdCache" + +#include <utils/String16.h> +#include <utils/Log.h> +#include "ResourceIdCache.h" +#include <map> +using namespace std; + + +static size_t mHits = 0; +static size_t mMisses = 0; +static size_t mCollisions = 0; + +static const size_t MAX_CACHE_ENTRIES = 2048; +static const android::String16 TRUE16("1"); +static const android::String16 FALSE16("0"); + +struct CacheEntry { + // concatenation of the relevant strings into a single instance + android::String16 hashedName; + uint32_t id; + + CacheEntry() {} + CacheEntry(const android::String16& name, uint32_t resId) : hashedName(name), id(resId) { } +}; + +static map< uint32_t, CacheEntry > mIdMap; + + +// djb2; reasonable choice for strings when collisions aren't particularly important +static inline uint32_t hashround(uint32_t hash, int c) { + return ((hash << 5) + hash) + c; /* hash * 33 + c */ +} + +static uint32_t hash(const android::String16& hashableString) { + uint32_t hash = 5381; + const char16_t* str = hashableString.string(); + while (int c = *str++) hash = hashround(hash, c); + return hash; +} + +namespace android { + +static inline String16 makeHashableName(const android::String16& package, + const android::String16& type, + const android::String16& name, + bool onlyPublic) { + String16 hashable = String16(name); + hashable += type; + hashable += package; + hashable += (onlyPublic ? TRUE16 : FALSE16); + return hashable; +} + +uint32_t ResourceIdCache::lookup(const android::String16& package, + const android::String16& type, + const android::String16& name, + bool onlyPublic) { + const String16 hashedName = makeHashableName(package, type, name, onlyPublic); + const uint32_t hashcode = hash(hashedName); + map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode); + if (item == mIdMap.end()) { + // cache miss + mMisses++; + return 0; + } + + // legit match? + if (hashedName == (*item).second.hashedName) { + mHits++; + return (*item).second.id; + } + + // collision + mCollisions++; + mIdMap.erase(hashcode); + return 0; +} + +// returns the resource ID being stored, for callsite convenience +uint32_t ResourceIdCache::store(const android::String16& package, + const android::String16& type, + const android::String16& name, + bool onlyPublic, + uint32_t resId) { + if (mIdMap.size() < MAX_CACHE_ENTRIES) { + const String16 hashedName = makeHashableName(package, type, name, onlyPublic); + const uint32_t hashcode = hash(hashedName); + mIdMap[hashcode] = CacheEntry(hashedName, resId); + } + return resId; +} + +void ResourceIdCache::dump() { + printf("ResourceIdCache dump:\n"); + printf("Size: %ld\n", mIdMap.size()); + printf("Hits: %ld\n", mHits); + printf("Misses: %ld\n", mMisses); + printf("(Collisions: %ld)\n", mCollisions); +} + +} diff --git a/tools/aapt/ResourceIdCache.h b/tools/aapt/ResourceIdCache.h new file mode 100644 index 0000000..65f7781 --- /dev/null +++ b/tools/aapt/ResourceIdCache.h @@ -0,0 +1,30 @@ +// +// Copyright 2012 The Android Open Source Project +// +// Manage a resource ID cache. + +#ifndef RESOURCE_ID_CACHE_H +#define RESOURCE_ID_CACHE_H + +namespace android { +class android::String16; + +class ResourceIdCache { +public: + static uint32_t lookup(const android::String16& package, + const android::String16& type, + const android::String16& name, + bool onlyPublic); + + static uint32_t store(const android::String16& package, + const android::String16& type, + const android::String16& name, + bool onlyPublic, + uint32_t resId); + + static void dump(void); +}; + +} + +#endif diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp new file mode 100644 index 0000000..52ebaf0 --- /dev/null +++ b/tools/aapt/ResourceTable.cpp @@ -0,0 +1,3905 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "ResourceTable.h" + +#include "XMLNode.h" +#include "ResourceFilter.h" +#include "ResourceIdCache.h" + +#include <androidfw/ResourceTypes.h> +#include <utils/ByteOrder.h> +#include <stdarg.h> + +#define NOISY(x) //x + +status_t compileXmlFile(const sp<AaptAssets>& assets, + const sp<AaptFile>& target, + ResourceTable* table, + int options) +{ + sp<XMLNode> root = XMLNode::parse(target); + if (root == NULL) { + return UNKNOWN_ERROR; + } + + return compileXmlFile(assets, root, target, table, options); +} + +status_t compileXmlFile(const sp<AaptAssets>& assets, + const sp<AaptFile>& target, + const sp<AaptFile>& outTarget, + ResourceTable* table, + int options) +{ + sp<XMLNode> root = XMLNode::parse(target); + if (root == NULL) { + return UNKNOWN_ERROR; + } + + return compileXmlFile(assets, root, outTarget, table, options); +} + +status_t compileXmlFile(const sp<AaptAssets>& assets, + const sp<XMLNode>& root, + const sp<AaptFile>& target, + ResourceTable* table, + int options) +{ + if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) { + root->removeWhitespace(true, NULL); + } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) { + root->removeWhitespace(false, NULL); + } + + if ((options&XML_COMPILE_UTF8) != 0) { + root->setUTF8(true); + } + + bool hasErrors = false; + + if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { + status_t err = root->assignResourceIds(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + status_t err = root->parseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } + + if (hasErrors) { + return UNKNOWN_ERROR; + } + + NOISY(printf("Input XML Resource:\n")); + NOISY(root->print()); + err = root->flatten(target, + (options&XML_COMPILE_STRIP_COMMENTS) != 0, + (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); + if (err != NO_ERROR) { + return err; + } + + NOISY(printf("Output XML Resource:\n")); + NOISY(ResXMLTree tree; + tree.setTo(target->getData(), target->getSize()); + printXMLBlock(&tree)); + + target->setCompressionMethod(ZipEntry::kCompressDeflated); + + return err; +} + +#undef NOISY +#define NOISY(x) //x + +struct flag_entry +{ + const char16_t* name; + size_t nameLen; + uint32_t value; + const char* description; +}; + +static const char16_t referenceArray[] = + { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' }; +static const char16_t stringArray[] = + { 's', 't', 'r', 'i', 'n', 'g' }; +static const char16_t integerArray[] = + { 'i', 'n', 't', 'e', 'g', 'e', 'r' }; +static const char16_t booleanArray[] = + { 'b', 'o', 'o', 'l', 'e', 'a', 'n' }; +static const char16_t colorArray[] = + { 'c', 'o', 'l', 'o', 'r' }; +static const char16_t floatArray[] = + { 'f', 'l', 'o', 'a', 't' }; +static const char16_t dimensionArray[] = + { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' }; +static const char16_t fractionArray[] = + { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' }; +static const char16_t enumArray[] = + { 'e', 'n', 'u', 'm' }; +static const char16_t flagsArray[] = + { 'f', 'l', 'a', 'g', 's' }; + +static const flag_entry gFormatFlags[] = { + { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE, + "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n" + "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."}, + { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING, + "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." }, + { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER, + "an integer value, such as \"<code>100</code>\"." }, + { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN, + "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." }, + { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR, + "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n" + "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." }, + { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT, + "a floating point value, such as \"<code>1.2</code>\"."}, + { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION, + "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n" + "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n" + "in (inches), mm (millimeters)." }, + { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION, + "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n" + "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n" + "some parent container." }, + { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL }, + { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL }, + { NULL, 0, 0, NULL } +}; + +static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' }; + +static const flag_entry l10nRequiredFlags[] = { + { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL }, + { NULL, 0, 0, NULL } +}; + +static const char16_t nulStr[] = { 0 }; + +static uint32_t parse_flags(const char16_t* str, size_t len, + const flag_entry* flags, bool* outError = NULL) +{ + while (len > 0 && isspace(*str)) { + str++; + len--; + } + while (len > 0 && isspace(str[len-1])) { + len--; + } + + const char16_t* const end = str + len; + uint32_t value = 0; + + while (str < end) { + const char16_t* div = str; + while (div < end && *div != '|') { + div++; + } + + const flag_entry* cur = flags; + while (cur->name) { + if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) { + value |= cur->value; + break; + } + cur++; + } + + if (!cur->name) { + if (outError) *outError = true; + return 0; + } + + str = div < end ? div+1 : div; + } + + if (outError) *outError = false; + return value; +} + +static String16 mayOrMust(int type, int flags) +{ + if ((type&(~flags)) == 0) { + return String16("<p>Must"); + } + + return String16("<p>May"); +} + +static void appendTypeInfo(ResourceTable* outTable, const String16& pkg, + const String16& typeName, const String16& ident, int type, + const flag_entry* flags) +{ + bool hadType = false; + while (flags->name) { + if ((type&flags->value) != 0 && flags->description != NULL) { + String16 fullMsg(mayOrMust(type, flags->value)); + fullMsg.append(String16(" be ")); + fullMsg.append(String16(flags->description)); + outTable->appendTypeComment(pkg, typeName, ident, fullMsg); + hadType = true; + } + flags++; + } + if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) { + outTable->appendTypeComment(pkg, typeName, ident, + String16("<p>This may also be a reference to a resource (in the form\n" + "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n" + "theme attribute (in the form\n" + "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n" + "containing a value of this type.")); + } +} + +struct PendingAttribute +{ + const String16 myPackage; + const SourcePos sourcePos; + const bool appendComment; + int32_t type; + String16 ident; + String16 comment; + bool hasErrors; + bool added; + + PendingAttribute(String16 _package, const sp<AaptFile>& in, + ResXMLTree& block, bool _appendComment) + : myPackage(_package) + , sourcePos(in->getPrintableSource(), block.getLineNumber()) + , appendComment(_appendComment) + , type(ResTable_map::TYPE_ANY) + , hasErrors(false) + , added(false) + { + } + + status_t createIfNeeded(ResourceTable* outTable) + { + if (added || hasErrors) { + return NO_ERROR; + } + added = true; + + String16 attr16("attr"); + + if (outTable->hasBagOrEntry(myPackage, attr16, ident)) { + sourcePos.error("Attribute \"%s\" has already been defined\n", + String8(ident).string()); + hasErrors = true; + return UNKNOWN_ERROR; + } + + char numberStr[16]; + sprintf(numberStr, "%d", type); + status_t err = outTable->addBag(sourcePos, myPackage, + attr16, ident, String16(""), + String16("^type"), + String16(numberStr), NULL, NULL); + if (err != NO_ERROR) { + hasErrors = true; + return err; + } + outTable->appendComment(myPackage, attr16, ident, comment, appendComment); + //printf("Attribute %s comment: %s\n", String8(ident).string(), + // String8(comment).string()); + return err; + } +}; + +static status_t compileAttribute(const sp<AaptFile>& in, + ResXMLTree& block, + const String16& myPackage, + ResourceTable* outTable, + String16* outIdent = NULL, + bool inStyleable = false) +{ + PendingAttribute attr(myPackage, in, block, inStyleable); + + const String16 attr16("attr"); + const String16 id16("id"); + + // Attribute type constants. + const String16 enum16("enum"); + const String16 flag16("flag"); + + ResXMLTree::event_code_t code; + size_t len; + status_t err; + + ssize_t identIdx = block.indexOfAttribute(NULL, "name"); + if (identIdx >= 0) { + attr.ident = String16(block.getAttributeStringValue(identIdx, &len)); + if (outIdent) { + *outIdent = attr.ident; + } + } else { + attr.sourcePos.error("A 'name' attribute is required for <attr>\n"); + attr.hasErrors = true; + } + + attr.comment = String16( + block.getComment(&len) ? block.getComment(&len) : nulStr); + + ssize_t typeIdx = block.indexOfAttribute(NULL, "format"); + if (typeIdx >= 0) { + String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len)); + attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags); + if (attr.type == 0) { + attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n", + String8(typeStr).string()); + attr.hasErrors = true; + } + attr.createIfNeeded(outTable); + } else if (!inStyleable) { + // Attribute definitions outside of styleables always define the + // attribute as a generic value. + attr.createIfNeeded(outTable); + } + + //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type); + + ssize_t minIdx = block.indexOfAttribute(NULL, "min"); + if (minIdx >= 0) { + String16 val = String16(block.getAttributeStringValue(minIdx, &len)); + if (!ResTable::stringToInt(val.string(), val.size(), NULL)) { + attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n", + String8(val).string()); + attr.hasErrors = true; + } + attr.createIfNeeded(outTable); + if (!attr.hasErrors) { + err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident, + String16(""), String16("^min"), String16(val), NULL, NULL); + if (err != NO_ERROR) { + attr.hasErrors = true; + } + } + } + + ssize_t maxIdx = block.indexOfAttribute(NULL, "max"); + if (maxIdx >= 0) { + String16 val = String16(block.getAttributeStringValue(maxIdx, &len)); + if (!ResTable::stringToInt(val.string(), val.size(), NULL)) { + attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n", + String8(val).string()); + attr.hasErrors = true; + } + attr.createIfNeeded(outTable); + if (!attr.hasErrors) { + err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident, + String16(""), String16("^max"), String16(val), NULL, NULL); + attr.hasErrors = true; + } + } + + if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) { + attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n"); + attr.hasErrors = true; + } + + ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization"); + if (l10nIdx >= 0) { + const uint16_t* str = block.getAttributeStringValue(l10nIdx, &len); + bool error; + uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error); + if (error) { + attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n", + String8(str).string()); + attr.hasErrors = true; + } + attr.createIfNeeded(outTable); + if (!attr.hasErrors) { + char buf[11]; + sprintf(buf, "%d", l10n_required); + err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident, + String16(""), String16("^l10n"), String16(buf), NULL, NULL); + if (err != NO_ERROR) { + attr.hasErrors = true; + } + } + } + + String16 enumOrFlagsComment; + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + uint32_t localType = 0; + if (strcmp16(block.getElementName(&len), enum16.string()) == 0) { + localType = ResTable_map::TYPE_ENUM; + } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) { + localType = ResTable_map::TYPE_FLAGS; + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n", + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + + attr.createIfNeeded(outTable); + + if (attr.type == ResTable_map::TYPE_ANY) { + // No type was explicitly stated, so supplying enum tags + // implicitly creates an enum or flag. + attr.type = 0; + } + + if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) { + // Wasn't originally specified as an enum, so update its type. + attr.type |= localType; + if (!attr.hasErrors) { + char numberStr[16]; + sprintf(numberStr, "%d", attr.type); + err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), + myPackage, attr16, attr.ident, String16(""), + String16("^type"), String16(numberStr), NULL, NULL, true); + if (err != NO_ERROR) { + attr.hasErrors = true; + } + } + } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) { + if (localType == ResTable_map::TYPE_ENUM) { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("<enum> attribute can not be used inside a flags format\n"); + attr.hasErrors = true; + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("<flag> attribute can not be used inside a enum format\n"); + attr.hasErrors = true; + } + } + + String16 itemIdent; + ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name"); + if (itemIdentIdx >= 0) { + itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len)); + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("A 'name' attribute is required for <enum> or <flag>\n"); + attr.hasErrors = true; + } + + String16 value; + ssize_t valueIdx = block.indexOfAttribute(NULL, "value"); + if (valueIdx >= 0) { + value = String16(block.getAttributeStringValue(valueIdx, &len)); + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("A 'value' attribute is required for <enum> or <flag>\n"); + attr.hasErrors = true; + } + if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("Tag <enum> or <flag> 'value' attribute must be a number," + " not \"%s\"\n", + String8(value).string()); + attr.hasErrors = true; + } + + // Make sure an id is defined for this enum/flag identifier... + if (!attr.hasErrors && !outTable->hasBagOrEntry(itemIdent, &id16, &myPackage)) { + err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), + myPackage, id16, itemIdent, String16(), NULL); + if (err != NO_ERROR) { + attr.hasErrors = true; + } + } + + if (!attr.hasErrors) { + if (enumOrFlagsComment.size() == 0) { + enumOrFlagsComment.append(mayOrMust(attr.type, + ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)); + enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM) + ? String16(" be one of the following constant values.") + : String16(" be one or more (separated by '|') of the following constant values.")); + enumOrFlagsComment.append(String16("</p>\n<table>\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>")); + } + + enumOrFlagsComment.append(String16("\n<tr><td><code>")); + enumOrFlagsComment.append(itemIdent); + enumOrFlagsComment.append(String16("</code></td><td>")); + enumOrFlagsComment.append(value); + enumOrFlagsComment.append(String16("</td><td>")); + if (block.getComment(&len)) { + enumOrFlagsComment.append(String16(block.getComment(&len))); + } + enumOrFlagsComment.append(String16("</td></tr>")); + + err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), + myPackage, + attr16, attr.ident, String16(""), + itemIdent, value, NULL, NULL, false, true); + if (err != NO_ERROR) { + attr.hasErrors = true; + } + } + } else if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), attr16.string()) == 0) { + break; + } + if ((attr.type&ResTable_map::TYPE_ENUM) != 0) { + if (strcmp16(block.getElementName(&len), enum16.string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("Found tag </%s> where </enum> is expected\n", + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + } else { + if (strcmp16(block.getElementName(&len), flag16.string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("Found tag </%s> where </flag> is expected\n", + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + } + } + } + + if (!attr.hasErrors && attr.added) { + appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags); + } + + if (!attr.hasErrors && enumOrFlagsComment.size() > 0) { + enumOrFlagsComment.append(String16("\n</table>")); + outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment); + } + + + return NO_ERROR; +} + +bool localeIsDefined(const ResTable_config& config) +{ + return config.locale == 0; +} + +status_t parseAndAddBag(Bundle* bundle, + const sp<AaptFile>& in, + ResXMLTree* block, + const ResTable_config& config, + const String16& myPackage, + const String16& curType, + const String16& ident, + const String16& parentIdent, + const String16& itemIdent, + int32_t curFormat, + bool isFormatted, + const String16& product, + bool pseudolocalize, + const bool overwrite, + ResourceTable* outTable) +{ + status_t err; + const String16 item16("item"); + + String16 str; + Vector<StringPool::entry_style_span> spans; + err = parseStyledString(bundle, in->getPrintableSource().string(), + block, item16, &str, &spans, isFormatted, + pseudolocalize); + if (err != NO_ERROR) { + return err; + } + + NOISY(printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d " + " pid=%s, bag=%s, id=%s: %s\n", + config.language[0], config.language[1], + config.country[0], config.country[1], + config.orientation, config.density, + String8(parentIdent).string(), + String8(ident).string(), + String8(itemIdent).string(), + String8(str).string())); + + err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()), + myPackage, curType, ident, parentIdent, itemIdent, str, + &spans, &config, overwrite, false, curFormat); + return err; +} + +/* + * Returns true if needle is one of the elements in the comma-separated list + * haystack, false otherwise. + */ +bool isInProductList(const String16& needle, const String16& haystack) { + const char16_t *needle2 = needle.string(); + const char16_t *haystack2 = haystack.string(); + size_t needlesize = needle.size(); + + while (*haystack2 != '\0') { + if (strncmp16(haystack2, needle2, needlesize) == 0) { + if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') { + return true; + } + } + + while (*haystack2 != '\0' && *haystack2 != ',') { + haystack2++; + } + if (*haystack2 == ',') { + haystack2++; + } + } + + return false; +} + +status_t parseAndAddEntry(Bundle* bundle, + const sp<AaptFile>& in, + ResXMLTree* block, + const ResTable_config& config, + const String16& myPackage, + const String16& curType, + const String16& ident, + const String16& curTag, + bool curIsStyled, + int32_t curFormat, + bool isFormatted, + const String16& product, + bool pseudolocalize, + const bool overwrite, + ResourceTable* outTable) +{ + status_t err; + + String16 str; + Vector<StringPool::entry_style_span> spans; + err = parseStyledString(bundle, in->getPrintableSource().string(), block, + curTag, &str, curIsStyled ? &spans : NULL, + isFormatted, pseudolocalize); + + if (err < NO_ERROR) { + return err; + } + + /* + * If a product type was specified on the command line + * and also in the string, and the two are not the same, + * return without adding the string. + */ + + const char *bundleProduct = bundle->getProduct(); + if (bundleProduct == NULL) { + bundleProduct = ""; + } + + if (product.size() != 0) { + /* + * If the command-line-specified product is empty, only "default" + * matches. Other variants are skipped. This is so generation + * of the R.java file when the product is not known is predictable. + */ + + if (bundleProduct[0] == '\0') { + if (strcmp16(String16("default").string(), product.string()) != 0) { + return NO_ERROR; + } + } else { + /* + * The command-line product is not empty. + * If the product for this string is on the command-line list, + * it matches. "default" also matches, but only if nothing + * else has matched already. + */ + + if (isInProductList(product, String16(bundleProduct))) { + ; + } else if (strcmp16(String16("default").string(), product.string()) == 0 && + !outTable->hasBagOrEntry(myPackage, curType, ident, config)) { + ; + } else { + return NO_ERROR; + } + } + } + + NOISY(printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n", + config.language[0], config.language[1], + config.country[0], config.country[1], + config.orientation, config.density, + String8(ident).string(), String8(str).string())); + + err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()), + myPackage, curType, ident, str, &spans, &config, + false, curFormat, overwrite); + + return err; +} + +status_t compileResourceFile(Bundle* bundle, + const sp<AaptAssets>& assets, + const sp<AaptFile>& in, + const ResTable_config& defParams, + const bool overwrite, + ResourceTable* outTable) +{ + ResXMLTree block; + status_t err = parseXMLResource(in, &block, false, true); + if (err != NO_ERROR) { + return err; + } + + // Top-level tag. + const String16 resources16("resources"); + + // Identifier declaration tags. + const String16 declare_styleable16("declare-styleable"); + const String16 attr16("attr"); + + // Data creation organizational tags. + const String16 string16("string"); + const String16 drawable16("drawable"); + const String16 color16("color"); + const String16 bool16("bool"); + const String16 integer16("integer"); + const String16 dimen16("dimen"); + const String16 fraction16("fraction"); + const String16 style16("style"); + const String16 plurals16("plurals"); + const String16 array16("array"); + const String16 string_array16("string-array"); + const String16 integer_array16("integer-array"); + const String16 public16("public"); + const String16 public_padding16("public-padding"); + const String16 private_symbols16("private-symbols"); + const String16 java_symbol16("java-symbol"); + const String16 add_resource16("add-resource"); + const String16 skip16("skip"); + const String16 eat_comment16("eat-comment"); + + // Data creation tags. + const String16 bag16("bag"); + const String16 item16("item"); + + // Attribute type constants. + const String16 enum16("enum"); + + // plural values + const String16 other16("other"); + const String16 quantityOther16("^other"); + const String16 zero16("zero"); + const String16 quantityZero16("^zero"); + const String16 one16("one"); + const String16 quantityOne16("^one"); + const String16 two16("two"); + const String16 quantityTwo16("^two"); + const String16 few16("few"); + const String16 quantityFew16("^few"); + const String16 many16("many"); + const String16 quantityMany16("^many"); + + // useful attribute names and special values + const String16 name16("name"); + const String16 translatable16("translatable"); + const String16 formatted16("formatted"); + const String16 false16("false"); + + const String16 myPackage(assets->getPackage()); + + bool hasErrors = false; + + bool fileIsTranslatable = true; + if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) { + fileIsTranslatable = false; + } + + DefaultKeyedVector<String16, uint32_t> nextPublicId(0); + + ResXMLTree::event_code_t code; + do { + code = block.next(); + } while (code == ResXMLTree::START_NAMESPACE); + + size_t len; + if (code != ResXMLTree::START_TAG) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "No start tag found\n"); + return UNKNOWN_ERROR; + } + if (strcmp16(block.getElementName(&len), resources16.string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Invalid start tag %s\n", String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + + ResTable_config curParams(defParams); + + ResTable_config pseudoParams(curParams); + pseudoParams.language[0] = 'z'; + pseudoParams.language[1] = 'z'; + pseudoParams.country[0] = 'Z'; + pseudoParams.country[1] = 'Z'; + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + const String16* curTag = NULL; + String16 curType; + int32_t curFormat = ResTable_map::TYPE_ANY; + bool curIsBag = false; + bool curIsBagReplaceOnOverwrite = false; + bool curIsStyled = false; + bool curIsPseudolocalizable = false; + bool curIsFormatted = fileIsTranslatable; + bool localHasErrors = false; + + if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 type; + ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); + if (typeIdx < 0) { + srcPos.error("A 'type' attribute is required for <public>\n"); + hasErrors = localHasErrors = true; + } + type = String16(block.getAttributeStringValue(typeIdx, &len)); + + String16 name; + ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); + if (nameIdx < 0) { + srcPos.error("A 'name' attribute is required for <public>\n"); + hasErrors = localHasErrors = true; + } + name = String16(block.getAttributeStringValue(nameIdx, &len)); + + uint32_t ident = 0; + ssize_t identIdx = block.indexOfAttribute(NULL, "id"); + if (identIdx >= 0) { + const char16_t* identStr = block.getAttributeStringValue(identIdx, &len); + Res_value identValue; + if (!ResTable::stringToInt(identStr, len, &identValue)) { + srcPos.error("Given 'id' attribute is not an integer: %s\n", + String8(block.getAttributeStringValue(identIdx, &len)).string()); + hasErrors = localHasErrors = true; + } else { + ident = identValue.data; + nextPublicId.replaceValueFor(type, ident+1); + } + } else if (nextPublicId.indexOfKey(type) < 0) { + srcPos.error("No 'id' attribute supplied <public>," + " and no previous id defined in this file.\n"); + hasErrors = localHasErrors = true; + } else if (!localHasErrors) { + ident = nextPublicId.valueFor(type); + nextPublicId.replaceValueFor(type, ident+1); + } + + if (!localHasErrors) { + err = outTable->addPublic(srcPos, myPackage, type, name, ident); + if (err < NO_ERROR) { + hasErrors = localHasErrors = true; + } + } + if (!localHasErrors) { + sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8(type), srcPos); + } + if (symbols != NULL) { + symbols->makeSymbolPublic(String8(name), srcPos); + String16 comment( + block.getComment(&len) ? block.getComment(&len) : nulStr); + symbols->appendComment(String8(name), comment, srcPos); + } else { + srcPos.error("Unable to create symbols!\n"); + hasErrors = localHasErrors = true; + } + } + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), public16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 type; + ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); + if (typeIdx < 0) { + srcPos.error("A 'type' attribute is required for <public-padding>\n"); + hasErrors = localHasErrors = true; + } + type = String16(block.getAttributeStringValue(typeIdx, &len)); + + String16 name; + ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); + if (nameIdx < 0) { + srcPos.error("A 'name' attribute is required for <public-padding>\n"); + hasErrors = localHasErrors = true; + } + name = String16(block.getAttributeStringValue(nameIdx, &len)); + + uint32_t start = 0; + ssize_t startIdx = block.indexOfAttribute(NULL, "start"); + if (startIdx >= 0) { + const char16_t* startStr = block.getAttributeStringValue(startIdx, &len); + Res_value startValue; + if (!ResTable::stringToInt(startStr, len, &startValue)) { + srcPos.error("Given 'start' attribute is not an integer: %s\n", + String8(block.getAttributeStringValue(startIdx, &len)).string()); + hasErrors = localHasErrors = true; + } else { + start = startValue.data; + } + } else if (nextPublicId.indexOfKey(type) < 0) { + srcPos.error("No 'start' attribute supplied <public-padding>," + " and no previous id defined in this file.\n"); + hasErrors = localHasErrors = true; + } else if (!localHasErrors) { + start = nextPublicId.valueFor(type); + } + + uint32_t end = 0; + ssize_t endIdx = block.indexOfAttribute(NULL, "end"); + if (endIdx >= 0) { + const char16_t* endStr = block.getAttributeStringValue(endIdx, &len); + Res_value endValue; + if (!ResTable::stringToInt(endStr, len, &endValue)) { + srcPos.error("Given 'end' attribute is not an integer: %s\n", + String8(block.getAttributeStringValue(endIdx, &len)).string()); + hasErrors = localHasErrors = true; + } else { + end = endValue.data; + } + } else { + srcPos.error("No 'end' attribute supplied <public-padding>\n"); + hasErrors = localHasErrors = true; + } + + if (end >= start) { + nextPublicId.replaceValueFor(type, end+1); + } else { + srcPos.error("Padding start '%ul' is after end '%ul'\n", + start, end); + hasErrors = localHasErrors = true; + } + + String16 comment( + block.getComment(&len) ? block.getComment(&len) : nulStr); + for (uint32_t curIdent=start; curIdent<=end; curIdent++) { + if (localHasErrors) { + break; + } + String16 curName(name); + char buf[64]; + sprintf(buf, "%d", (int)(end-curIdent+1)); + curName.append(String16(buf)); + + err = outTable->addEntry(srcPos, myPackage, type, curName, + String16("padding"), NULL, &curParams, false, + ResTable_map::TYPE_STRING, overwrite); + if (err < NO_ERROR) { + hasErrors = localHasErrors = true; + break; + } + err = outTable->addPublic(srcPos, myPackage, type, + curName, curIdent); + if (err < NO_ERROR) { + hasErrors = localHasErrors = true; + break; + } + sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8(type), srcPos); + } + if (symbols != NULL) { + symbols->makeSymbolPublic(String8(curName), srcPos); + symbols->appendComment(String8(curName), comment, srcPos); + } else { + srcPos.error("Unable to create symbols!\n"); + hasErrors = localHasErrors = true; + } + } + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) { + String16 pkg; + ssize_t pkgIdx = block.indexOfAttribute(NULL, "package"); + if (pkgIdx < 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'package' attribute is required for <private-symbols>\n"); + hasErrors = localHasErrors = true; + } + pkg = String16(block.getAttributeStringValue(pkgIdx, &len)); + if (!localHasErrors) { + assets->setSymbolsPrivatePackage(String8(pkg)); + } + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 type; + ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); + if (typeIdx < 0) { + srcPos.error("A 'type' attribute is required for <public>\n"); + hasErrors = localHasErrors = true; + } + type = String16(block.getAttributeStringValue(typeIdx, &len)); + + String16 name; + ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); + if (nameIdx < 0) { + srcPos.error("A 'name' attribute is required for <public>\n"); + hasErrors = localHasErrors = true; + } + name = String16(block.getAttributeStringValue(nameIdx, &len)); + + sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R")); + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8(type), srcPos); + } + if (symbols != NULL) { + symbols->makeSymbolJavaSymbol(String8(name), srcPos); + String16 comment( + block.getComment(&len) ? block.getComment(&len) : nulStr); + symbols->appendComment(String8(name), comment, srcPos); + } else { + srcPos.error("Unable to create symbols!\n"); + hasErrors = localHasErrors = true; + } + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) { + break; + } + } + } + continue; + + + } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 typeName; + ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); + if (typeIdx < 0) { + srcPos.error("A 'type' attribute is required for <add-resource>\n"); + hasErrors = localHasErrors = true; + } + typeName = String16(block.getAttributeStringValue(typeIdx, &len)); + + String16 name; + ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); + if (nameIdx < 0) { + srcPos.error("A 'name' attribute is required for <add-resource>\n"); + hasErrors = localHasErrors = true; + } + name = String16(block.getAttributeStringValue(nameIdx, &len)); + + outTable->canAddEntry(srcPos, myPackage, typeName, name); + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { + break; + } + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 ident; + ssize_t identIdx = block.indexOfAttribute(NULL, "name"); + if (identIdx < 0) { + srcPos.error("A 'name' attribute is required for <declare-styleable>\n"); + hasErrors = localHasErrors = true; + } + ident = String16(block.getAttributeStringValue(identIdx, &len)); + + sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); + if (!localHasErrors) { + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8("styleable"), srcPos); + } + sp<AaptSymbols> styleSymbols = symbols; + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8(ident), srcPos); + } + if (symbols == NULL) { + srcPos.error("Unable to create symbols!\n"); + return UNKNOWN_ERROR; + } + + String16 comment( + block.getComment(&len) ? block.getComment(&len) : nulStr); + styleSymbols->appendComment(String8(ident), comment, srcPos); + } else { + symbols = NULL; + } + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), skip16.string()) == 0) { + break; + } + } + } + continue; + } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) { + break; + } + } + } + continue; + } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n", + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + + String16 comment( + block.getComment(&len) ? block.getComment(&len) : nulStr); + String16 itemIdent; + err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true); + if (err != NO_ERROR) { + hasErrors = localHasErrors = true; + } + + if (symbols != NULL) { + SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber()); + symbols->addSymbol(String8(itemIdent), 0, srcPos); + symbols->appendComment(String8(itemIdent), comment, srcPos); + //printf("Attribute %s comment: %s\n", String8(itemIdent).string(), + // String8(comment).string()); + } + } else if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) { + break; + } + + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Found tag </%s> where </attr> is expected\n", + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + } + continue; + + } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) { + err = compileAttribute(in, block, myPackage, outTable, NULL); + if (err != NO_ERROR) { + hasErrors = true; + } + continue; + + } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) { + curTag = &item16; + ssize_t attri = block.indexOfAttribute(NULL, "type"); + if (attri >= 0) { + curType = String16(block.getAttributeStringValue(attri, &len)); + ssize_t formatIdx = block.indexOfAttribute(NULL, "format"); + if (formatIdx >= 0) { + String16 formatStr = String16(block.getAttributeStringValue( + formatIdx, &len)); + curFormat = parse_flags(formatStr.string(), formatStr.size(), + gFormatFlags); + if (curFormat == 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Tag <item> 'format' attribute value \"%s\" not valid\n", + String8(formatStr).string()); + hasErrors = localHasErrors = true; + } + } + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'type' attribute is required for <item>\n"); + hasErrors = localHasErrors = true; + } + curIsStyled = true; + } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) { + // Note the existence and locale of every string we process + char rawLocale[16]; + curParams.getLocale(rawLocale); + String8 locale(rawLocale); + String16 name; + String16 translatable; + String16 formatted; + + size_t n = block.getAttributeCount(); + for (size_t i = 0; i < n; i++) { + size_t length; + const uint16_t* attr = block.getAttributeName(i, &length); + if (strcmp16(attr, name16.string()) == 0) { + name.setTo(block.getAttributeStringValue(i, &length)); + } else if (strcmp16(attr, translatable16.string()) == 0) { + translatable.setTo(block.getAttributeStringValue(i, &length)); + } else if (strcmp16(attr, formatted16.string()) == 0) { + formatted.setTo(block.getAttributeStringValue(i, &length)); + } + } + + if (name.size() > 0) { + if (translatable == false16) { + curIsFormatted = false; + // Untranslatable strings must only exist in the default [empty] locale + if (locale.size() > 0) { + fprintf(stderr, "aapt: warning: string '%s' in %s marked untranslatable but exists" + " in locale '%s'\n", String8(name).string(), + bundle->getResourceSourceDirs()[0], + locale.string()); + // hasErrors = localHasErrors = true; + } else { + // Intentionally empty block: + // + // Don't add untranslatable strings to the localization table; that + // way if we later see localizations of them, they'll be flagged as + // having no default translation. + } + } else { + outTable->addLocalization(name, locale); + } + + if (formatted == false16) { + curIsFormatted = false; + } + } + + curTag = &string16; + curType = string16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; + curIsStyled = true; + curIsPseudolocalizable = true; + } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) { + curTag = &drawable16; + curType = drawable16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR; + } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) { + curTag = &color16; + curType = color16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR; + } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) { + curTag = &bool16; + curType = bool16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN; + } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) { + curTag = &integer16; + curType = integer16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER; + } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) { + curTag = &dimen16; + curType = dimen16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION; + } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) { + curTag = &fraction16; + curType = fraction16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION; + } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) { + curTag = &bag16; + curIsBag = true; + ssize_t attri = block.indexOfAttribute(NULL, "type"); + if (attri >= 0) { + curType = String16(block.getAttributeStringValue(attri, &len)); + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'type' attribute is required for <bag>\n"); + hasErrors = localHasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) { + curTag = &style16; + curType = style16; + curIsBag = true; + } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) { + curTag = &plurals16; + curType = plurals16; + curIsBag = true; + } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) { + curTag = &array16; + curType = array16; + curIsBag = true; + curIsBagReplaceOnOverwrite = true; + ssize_t formatIdx = block.indexOfAttribute(NULL, "format"); + if (formatIdx >= 0) { + String16 formatStr = String16(block.getAttributeStringValue( + formatIdx, &len)); + curFormat = parse_flags(formatStr.string(), formatStr.size(), + gFormatFlags); + if (curFormat == 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Tag <array> 'format' attribute value \"%s\" not valid\n", + String8(formatStr).string()); + hasErrors = localHasErrors = true; + } + } + } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) { + // Check whether these strings need valid formats. + // (simplified form of what string16 does above) + size_t n = block.getAttributeCount(); + for (size_t i = 0; i < n; i++) { + size_t length; + const uint16_t* attr = block.getAttributeName(i, &length); + if (strcmp16(attr, translatable16.string()) == 0 + || strcmp16(attr, formatted16.string()) == 0) { + const uint16_t* value = block.getAttributeStringValue(i, &length); + if (strcmp16(value, false16.string()) == 0) { + curIsFormatted = false; + break; + } + } + } + + curTag = &string_array16; + curType = array16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; + curIsBag = true; + curIsBagReplaceOnOverwrite = true; + curIsPseudolocalizable = true; + } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) { + curTag = &integer_array16; + curType = array16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER; + curIsBag = true; + curIsBagReplaceOnOverwrite = true; + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Found tag %s where item is expected\n", + String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + + String16 ident; + ssize_t identIdx = block.indexOfAttribute(NULL, "name"); + if (identIdx >= 0) { + ident = String16(block.getAttributeStringValue(identIdx, &len)); + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'name' attribute is required for <%s>\n", + String8(*curTag).string()); + hasErrors = localHasErrors = true; + } + + String16 product; + identIdx = block.indexOfAttribute(NULL, "product"); + if (identIdx >= 0) { + product = String16(block.getAttributeStringValue(identIdx, &len)); + } + + String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr); + + if (curIsBag) { + // Figure out the parent of this bag... + String16 parentIdent; + ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent"); + if (parentIdentIdx >= 0) { + parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len)); + } else { + ssize_t sep = ident.findLast('.'); + if (sep >= 0) { + parentIdent.setTo(ident, sep); + } + } + + if (!localHasErrors) { + err = outTable->startBag(SourcePos(in->getPrintableSource(), + block.getLineNumber()), myPackage, curType, ident, + parentIdent, &curParams, + overwrite, curIsBagReplaceOnOverwrite); + if (err != NO_ERROR) { + hasErrors = localHasErrors = true; + } + } + + ssize_t elmIndex = 0; + char elmIndexStr[14]; + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code != ResXMLTree::BAD_DOCUMENT) { + + if (code == ResXMLTree::START_TAG) { + if (strcmp16(block.getElementName(&len), item16.string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Tag <%s> can not appear inside <%s>, only <item>\n", + String8(block.getElementName(&len)).string(), + String8(*curTag).string()); + return UNKNOWN_ERROR; + } + + String16 itemIdent; + if (curType == array16) { + sprintf(elmIndexStr, "^index_%d", (int)elmIndex++); + itemIdent = String16(elmIndexStr); + } else if (curType == plurals16) { + ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity"); + if (itemIdentIdx >= 0) { + String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len)); + if (quantity16 == other16) { + itemIdent = quantityOther16; + } + else if (quantity16 == zero16) { + itemIdent = quantityZero16; + } + else if (quantity16 == one16) { + itemIdent = quantityOne16; + } + else if (quantity16 == two16) { + itemIdent = quantityTwo16; + } + else if (quantity16 == few16) { + itemIdent = quantityFew16; + } + else if (quantity16 == many16) { + itemIdent = quantityMany16; + } + else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Illegal 'quantity' attribute is <item> inside <plurals>\n"); + hasErrors = localHasErrors = true; + } + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'quantity' attribute is required for <item> inside <plurals>\n"); + hasErrors = localHasErrors = true; + } + } else { + ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name"); + if (itemIdentIdx >= 0) { + itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len)); + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'name' attribute is required for <item>\n"); + hasErrors = localHasErrors = true; + } + } + + ResXMLParser::ResXMLPosition parserPosition; + block.getPosition(&parserPosition); + + err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType, + ident, parentIdent, itemIdent, curFormat, curIsFormatted, + product, false, overwrite, outTable); + if (err == NO_ERROR) { + if (curIsPseudolocalizable && localeIsDefined(curParams) + && bundle->getPseudolocalize()) { + // pseudolocalize here +#if 1 + block.setPosition(parserPosition); + err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage, + curType, ident, parentIdent, itemIdent, curFormat, + curIsFormatted, product, true, overwrite, outTable); +#endif + } + } + if (err != NO_ERROR) { + hasErrors = localHasErrors = true; + } + } else if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), curTag->string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Found tag </%s> where </%s> is expected\n", + String8(block.getElementName(&len)).string(), + String8(*curTag).string()); + return UNKNOWN_ERROR; + } + break; + } + } + } else { + ResXMLParser::ResXMLPosition parserPosition; + block.getPosition(&parserPosition); + + err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident, + *curTag, curIsStyled, curFormat, curIsFormatted, + product, false, overwrite, outTable); + + if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR? + hasErrors = localHasErrors = true; + } + else if (err == NO_ERROR) { + if (curIsPseudolocalizable && localeIsDefined(curParams) + && bundle->getPseudolocalize()) { + // pseudolocalize here + block.setPosition(parserPosition); + err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType, + ident, *curTag, curIsStyled, curFormat, + curIsFormatted, product, + true, overwrite, outTable); + if (err != NO_ERROR) { + hasErrors = localHasErrors = true; + } + } + } + } + +#if 0 + if (comment.size() > 0) { + printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(), + String8(curType).string(), String8(ident).string(), + String8(comment).string()); + } +#endif + if (!localHasErrors) { + outTable->appendComment(myPackage, curType, ident, comment, false); + } + } + else if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), resources16.string()) != 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Unexpected end tag %s\n", String8(block.getElementName(&len)).string()); + return UNKNOWN_ERROR; + } + } + else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) { + } + else if (code == ResXMLTree::TEXT) { + if (isWhitespace(block.getText(&len))) { + continue; + } + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "Found text \"%s\" where item tag is expected\n", + String8(block.getText(&len)).string()); + return UNKNOWN_ERROR; + } + } + + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage) + : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false), + mIsAppPackage(!bundle->getExtending()), + mNumLocal(0), + mBundle(bundle) +{ +} + +status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets) +{ + status_t err = assets->buildIncludedResources(bundle); + if (err != NO_ERROR) { + return err; + } + + // For future reference to included resources. + mAssets = assets; + + const ResTable& incl = assets->getIncludedResources(); + + // Retrieve all the packages. + const size_t N = incl.getBasePackageCount(); + for (size_t phase=0; phase<2; phase++) { + for (size_t i=0; i<N; i++) { + String16 name(incl.getBasePackageName(i)); + uint32_t id = incl.getBasePackageId(i); + // First time through: only add base packages (id + // is not 0); second time through add the other + // packages. + if (phase != 0) { + if (id != 0) { + // Skip base packages -- already one. + id = 0; + } else { + // Assign a dynamic id. + id = mNextPackageId; + } + } else if (id != 0) { + if (id == 127) { + if (mHaveAppPackage) { + fprintf(stderr, "Included resources have two application packages!\n"); + return UNKNOWN_ERROR; + } + mHaveAppPackage = true; + } + if (mNextPackageId > id) { + fprintf(stderr, "Included base package ID %d already in use!\n", id); + return UNKNOWN_ERROR; + } + } + if (id != 0) { + NOISY(printf("Including package %s with ID=%d\n", + String8(name).string(), id)); + sp<Package> p = new Package(name, id); + mPackages.add(name, p); + mOrderedPackages.add(p); + + if (id >= mNextPackageId) { + mNextPackageId = id+1; + } + } + } + } + + // Every resource table always has one first entry, the bag attributes. + const SourcePos unknown(String8("????"), 0); + sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown); + + return NO_ERROR; +} + +status_t ResourceTable::addPublic(const SourcePos& sourcePos, + const String16& package, + const String16& type, + const String16& name, + const uint32_t ident) +{ + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size()); + if (rid != 0) { + sourcePos.error("Error declaring public resource %s/%s for included package %s\n", + String8(type).string(), String8(name).string(), + String8(package).string()); + return UNKNOWN_ERROR; + } + + sp<Type> t = getType(package, type, sourcePos); + if (t == NULL) { + return UNKNOWN_ERROR; + } + return t->addPublic(sourcePos, name, ident); +} + +status_t ResourceTable::addEntry(const SourcePos& sourcePos, + const String16& package, + const String16& type, + const String16& name, + const String16& value, + const Vector<StringPool::entry_style_span>* style, + const ResTable_config* params, + const bool doSetIndex, + const int32_t format, + const bool overwrite) +{ + // Check for adding entries in other packages... for now we do + // nothing. We need to do the right thing here to support skinning. + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size()); + if (rid != 0) { + return NO_ERROR; + } + +#if 0 + if (name == String16("left")) { + printf("Adding entry left: file=%s, line=%d, type=%s, value=%s\n", + sourcePos.file.string(), sourcePos.line, String8(type).string(), + String8(value).string()); + } +#endif + + sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite, + params, doSetIndex); + if (e == NULL) { + return UNKNOWN_ERROR; + } + status_t err = e->setItem(sourcePos, value, style, format, overwrite); + if (err == NO_ERROR) { + mNumLocal++; + } + return err; +} + +status_t ResourceTable::startBag(const SourcePos& sourcePos, + const String16& package, + const String16& type, + const String16& name, + const String16& bagParent, + const ResTable_config* params, + bool overlay, + bool replace, bool isId) +{ + status_t result = NO_ERROR; + + // Check for adding entries in other packages... for now we do + // nothing. We need to do the right thing here to support skinning. + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size()); + if (rid != 0) { + return NO_ERROR; + } + +#if 0 + if (name == String16("left")) { + printf("Adding bag left: file=%s, line=%d, type=%s\n", + sourcePos.file.striing(), sourcePos.line, String8(type).string()); + } +#endif + if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) { + bool canAdd = false; + sp<Package> p = mPackages.valueFor(package); + if (p != NULL) { + sp<Type> t = p->getTypes().valueFor(type); + if (t != NULL) { + if (t->getCanAddEntries().indexOf(name) >= 0) { + canAdd = true; + } + } + } + if (!canAdd) { + sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n", + String8(name).string()); + return UNKNOWN_ERROR; + } + } + sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params); + if (e == NULL) { + return UNKNOWN_ERROR; + } + + // If a parent is explicitly specified, set it. + if (bagParent.size() > 0) { + e->setParent(bagParent); + } + + if ((result = e->makeItABag(sourcePos)) != NO_ERROR) { + return result; + } + + if (overlay && replace) { + return e->emptyBag(sourcePos); + } + return result; +} + +status_t ResourceTable::addBag(const SourcePos& sourcePos, + const String16& package, + const String16& type, + const String16& name, + const String16& bagParent, + const String16& bagKey, + const String16& value, + const Vector<StringPool::entry_style_span>* style, + const ResTable_config* params, + bool replace, bool isId, const int32_t format) +{ + // Check for adding entries in other packages... for now we do + // nothing. We need to do the right thing here to support skinning. + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size()); + if (rid != 0) { + return NO_ERROR; + } + +#if 0 + if (name == String16("left")) { + printf("Adding bag left: file=%s, line=%d, type=%s\n", + sourcePos.file.striing(), sourcePos.line, String8(type).string()); + } +#endif + sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params); + if (e == NULL) { + return UNKNOWN_ERROR; + } + + // If a parent is explicitly specified, set it. + if (bagParent.size() > 0) { + e->setParent(bagParent); + } + + const bool first = e->getBag().indexOfKey(bagKey) < 0; + status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format); + if (err == NO_ERROR && first) { + mNumLocal++; + } + return err; +} + +bool ResourceTable::hasBagOrEntry(const String16& package, + const String16& type, + const String16& name) const +{ + // First look for this in the included resources... + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size()); + if (rid != 0) { + return true; + } + + sp<Package> p = mPackages.valueFor(package); + if (p != NULL) { + sp<Type> t = p->getTypes().valueFor(type); + if (t != NULL) { + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c != NULL) return true; + } + } + + return false; +} + +bool ResourceTable::hasBagOrEntry(const String16& package, + const String16& type, + const String16& name, + const ResTable_config& config) const +{ + // First look for this in the included resources... + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size()); + if (rid != 0) { + return true; + } + + sp<Package> p = mPackages.valueFor(package); + if (p != NULL) { + sp<Type> t = p->getTypes().valueFor(type); + if (t != NULL) { + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c != NULL) { + sp<Entry> e = c->getEntries().valueFor(config); + if (e != NULL) { + return true; + } + } + } + } + + return false; +} + +bool ResourceTable::hasBagOrEntry(const String16& ref, + const String16* defType, + const String16* defPackage) +{ + String16 package, type, name; + if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name, + defType, defPackage ? defPackage:&mAssetsPackage, NULL)) { + return false; + } + return hasBagOrEntry(package, type, name); +} + +bool ResourceTable::appendComment(const String16& package, + const String16& type, + const String16& name, + const String16& comment, + bool onlyIfEmpty) +{ + if (comment.size() <= 0) { + return true; + } + + sp<Package> p = mPackages.valueFor(package); + if (p != NULL) { + sp<Type> t = p->getTypes().valueFor(type); + if (t != NULL) { + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c != NULL) { + c->appendComment(comment, onlyIfEmpty); + return true; + } + } + } + return false; +} + +bool ResourceTable::appendTypeComment(const String16& package, + const String16& type, + const String16& name, + const String16& comment) +{ + if (comment.size() <= 0) { + return true; + } + + sp<Package> p = mPackages.valueFor(package); + if (p != NULL) { + sp<Type> t = p->getTypes().valueFor(type); + if (t != NULL) { + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c != NULL) { + c->appendTypeComment(comment); + return true; + } + } + } + return false; +} + +void ResourceTable::canAddEntry(const SourcePos& pos, + const String16& package, const String16& type, const String16& name) +{ + sp<Type> t = getType(package, type, pos); + if (t != NULL) { + t->canAddEntry(name); + } +} + +size_t ResourceTable::size() const { + return mPackages.size(); +} + +size_t ResourceTable::numLocalResources() const { + return mNumLocal; +} + +bool ResourceTable::hasResources() const { + return mNumLocal > 0; +} + +sp<AaptFile> ResourceTable::flatten(Bundle* bundle) +{ + sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8()); + status_t err = flatten(bundle, data); + return err == NO_ERROR ? data : NULL; +} + +inline uint32_t ResourceTable::getResId(const sp<Package>& p, + const sp<Type>& t, + uint32_t nameId) +{ + return makeResId(p->getAssignedId(), t->getIndex(), nameId); +} + +uint32_t ResourceTable::getResId(const String16& package, + const String16& type, + const String16& name, + bool onlyPublic) const +{ + uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic); + if (id != 0) return id; // cache hit + + sp<Package> p = mPackages.valueFor(package); + if (p == NULL) return 0; + + // First look for this in the included resources... + uint32_t specFlags = 0; + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + type.string(), type.size(), + package.string(), package.size(), + &specFlags); + if (rid != 0) { + if (onlyPublic) { + if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) { + return 0; + } + } + + if (Res_INTERNALID(rid)) { + return ResourceIdCache::store(package, type, name, onlyPublic, rid); + } + return ResourceIdCache::store(package, type, name, onlyPublic, + Res_MAKEID(p->getAssignedId()-1, Res_GETTYPE(rid), Res_GETENTRY(rid))); + } + + sp<Type> t = p->getTypes().valueFor(type); + if (t == NULL) return 0; + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c == NULL) return 0; + int32_t ei = c->getEntryIndex(); + if (ei < 0) return 0; + + return ResourceIdCache::store(package, type, name, onlyPublic, + getResId(p, t, ei)); +} + +uint32_t ResourceTable::getResId(const String16& ref, + const String16* defType, + const String16* defPackage, + const char** outErrorMsg, + bool onlyPublic) const +{ + String16 package, type, name; + bool refOnlyPublic = true; + if (!ResTable::expandResourceRef( + ref.string(), ref.size(), &package, &type, &name, + defType, defPackage ? defPackage:&mAssetsPackage, + outErrorMsg, &refOnlyPublic)) { + NOISY(printf("Expanding resource: ref=%s\n", + String8(ref).string())); + NOISY(printf("Expanding resource: defType=%s\n", + defType ? String8(*defType).string() : "NULL")); + NOISY(printf("Expanding resource: defPackage=%s\n", + defPackage ? String8(*defPackage).string() : "NULL")); + NOISY(printf("Expanding resource: ref=%s\n", String8(ref).string())); + NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n", + String8(package).string(), String8(type).string(), + String8(name).string())); + return 0; + } + uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic); + NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n", + String8(package).string(), String8(type).string(), + String8(name).string(), res)); + if (res == 0) { + if (outErrorMsg) + *outErrorMsg = "No resource found that matches the given name"; + } + return res; +} + +bool ResourceTable::isValidResourceName(const String16& s) +{ + const char16_t* p = s.string(); + bool first = true; + while (*p) { + if ((*p >= 'a' && *p <= 'z') + || (*p >= 'A' && *p <= 'Z') + || *p == '_' + || (!first && *p >= '0' && *p <= '9')) { + first = false; + p++; + continue; + } + return false; + } + return true; +} + +bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool, + const String16& str, + bool preserveSpaces, bool coerceType, + uint32_t attrID, + const Vector<StringPool::entry_style_span>* style, + String16* outStr, void* accessorCookie, + uint32_t attrType, const String8* configTypeName, + const ConfigDescription* config) +{ + String16 finalStr; + + bool res = true; + if (style == NULL || style->size() == 0) { + // Text is not styled so it can be any type... let's figure it out. + res = mAssets->getIncludedResources() + .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces, + coerceType, attrID, NULL, &mAssetsPackage, this, + accessorCookie, attrType); + } else { + // Styled text can only be a string, and while collecting the style + // information we have already processed that string! + outValue->size = sizeof(Res_value); + outValue->res0 = 0; + outValue->dataType = outValue->TYPE_STRING; + outValue->data = 0; + finalStr = str; + } + + if (!res) { + return false; + } + + if (outValue->dataType == outValue->TYPE_STRING) { + // Should do better merging styles. + if (pool) { + String8 configStr; + if (config != NULL) { + configStr = config->toString(); + } else { + configStr = "(null)"; + } + NOISY(printf("Adding to pool string style #%d config %s: %s\n", + style != NULL ? style->size() : 0, + configStr.string(), String8(finalStr).string())); + if (style != NULL && style->size() > 0) { + outValue->data = pool->add(finalStr, *style, configTypeName, config); + } else { + outValue->data = pool->add(finalStr, true, configTypeName, config); + } + } else { + // Caller will fill this in later. + outValue->data = 0; + } + + if (outStr) { + *outStr = finalStr; + } + + } + + return true; +} + +uint32_t ResourceTable::getCustomResource( + const String16& package, const String16& type, const String16& name) const +{ + //printf("getCustomResource: %s %s %s\n", String8(package).string(), + // String8(type).string(), String8(name).string()); + sp<Package> p = mPackages.valueFor(package); + if (p == NULL) return 0; + sp<Type> t = p->getTypes().valueFor(type); + if (t == NULL) return 0; + sp<ConfigList> c = t->getConfigs().valueFor(name); + if (c == NULL) return 0; + int32_t ei = c->getEntryIndex(); + if (ei < 0) return 0; + return getResId(p, t, ei); +} + +uint32_t ResourceTable::getCustomResourceWithCreation( + const String16& package, const String16& type, const String16& name, + const bool createIfNotFound) +{ + uint32_t resId = getCustomResource(package, type, name); + if (resId != 0 || !createIfNotFound) { + return resId; + } + String16 value("false"); + + status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true); + if (status == NO_ERROR) { + resId = getResId(package, type, name); + return resId; + } + return 0; +} + +uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const +{ + return origPackage; +} + +bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType) +{ + //printf("getAttributeType #%08x\n", attrID); + Res_value value; + if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) { + //printf("getAttributeType #%08x (%s): #%08x\n", attrID, + // String8(getEntry(attrID)->getName()).string(), value.data); + *outType = value.data; + return true; + } + return false; +} + +bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin) +{ + //printf("getAttributeMin #%08x\n", attrID); + Res_value value; + if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) { + *outMin = value.data; + return true; + } + return false; +} + +bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax) +{ + //printf("getAttributeMax #%08x\n", attrID); + Res_value value; + if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) { + *outMax = value.data; + return true; + } + return false; +} + +uint32_t ResourceTable::getAttributeL10N(uint32_t attrID) +{ + //printf("getAttributeL10N #%08x\n", attrID); + Res_value value; + if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) { + return value.data; + } + return ResTable_map::L10N_NOT_REQUIRED; +} + +bool ResourceTable::getLocalizationSetting() +{ + return mBundle->getRequireLocalization(); +} + +void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...) +{ + if (accessorCookie != NULL && fmt != NULL) { + AccessorCookie* ac = (AccessorCookie*)accessorCookie; + int retval=0; + char buf[1024]; + va_list ap; + va_start(ap, fmt); + retval = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n", + buf, ac->attr.string(), ac->value.string()); + } +} + +bool ResourceTable::getAttributeKeys( + uint32_t attrID, Vector<String16>* outKeys) +{ + sp<const Entry> e = getEntry(attrID); + if (e != NULL) { + const size_t N = e->getBag().size(); + for (size_t i=0; i<N; i++) { + const String16& key = e->getBag().keyAt(i); + if (key.size() > 0 && key.string()[0] != '^') { + outKeys->add(key); + } + } + return true; + } + return false; +} + +bool ResourceTable::getAttributeEnum( + uint32_t attrID, const char16_t* name, size_t nameLen, + Res_value* outValue) +{ + //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string()); + String16 nameStr(name, nameLen); + sp<const Entry> e = getEntry(attrID); + if (e != NULL) { + const size_t N = e->getBag().size(); + for (size_t i=0; i<N; i++) { + //printf("Comparing %s to %s\n", String8(name, nameLen).string(), + // String8(e->getBag().keyAt(i)).string()); + if (e->getBag().keyAt(i) == nameStr) { + return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue); + } + } + } + return false; +} + +bool ResourceTable::getAttributeFlags( + uint32_t attrID, const char16_t* name, size_t nameLen, + Res_value* outValue) +{ + outValue->dataType = Res_value::TYPE_INT_HEX; + outValue->data = 0; + + //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string()); + String16 nameStr(name, nameLen); + sp<const Entry> e = getEntry(attrID); + if (e != NULL) { + const size_t N = e->getBag().size(); + + const char16_t* end = name + nameLen; + const char16_t* pos = name; + while (pos < end) { + const char16_t* start = pos; + while (pos < end && *pos != '|') { + pos++; + } + + String16 nameStr(start, pos-start); + size_t i; + for (i=0; i<N; i++) { + //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(), + // String8(e->getBag().keyAt(i)).string()); + if (e->getBag().keyAt(i) == nameStr) { + Res_value val; + bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val); + if (!got) { + return false; + } + //printf("Got value: 0x%08x\n", val.data); + outValue->data |= val.data; + break; + } + } + + if (i >= N) { + // Didn't find this flag identifier. + return false; + } + pos++; + } + + return true; + } + return false; +} + +status_t ResourceTable::assignResourceIds() +{ + const size_t N = mOrderedPackages.size(); + size_t pi; + status_t firstError = NO_ERROR; + + // First generate all bag attributes and assign indices. + for (pi=0; pi<N; pi++) { + sp<Package> p = mOrderedPackages.itemAt(pi); + if (p == NULL || p->getTypes().size() == 0) { + // Empty, skip! + continue; + } + + status_t err = p->applyPublicTypeOrder(); + if (err != NO_ERROR && firstError == NO_ERROR) { + firstError = err; + } + + // Generate attributes... + const size_t N = p->getOrderedTypes().size(); + size_t ti; + for (ti=0; ti<N; ti++) { + sp<Type> t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + const size_t N = t->getOrderedConfigs().size(); + for (size_t ci=0; ci<N; ci++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } + const size_t N = c->getEntries().size(); + for (size_t ei=0; ei<N; ei++) { + sp<Entry> e = c->getEntries().valueAt(ei); + if (e == NULL) { + continue; + } + status_t err = e->generateAttributes(this, p->getName()); + if (err != NO_ERROR && firstError == NO_ERROR) { + firstError = err; + } + } + } + } + + const SourcePos unknown(String8("????"), 0); + sp<Type> attr = p->getType(String16("attr"), unknown); + + // Assign indices... + for (ti=0; ti<N; ti++) { + sp<Type> t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + err = t->applyPublicEntryOrder(); + if (err != NO_ERROR && firstError == NO_ERROR) { + firstError = err; + } + + const size_t N = t->getOrderedConfigs().size(); + t->setIndex(ti+1); + + LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t, + "First type is not attr!"); + + for (size_t ei=0; ei<N; ei++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei); + if (c == NULL) { + continue; + } + c->setEntryIndex(ei); + } + } + + // Assign resource IDs to keys in bags... + for (ti=0; ti<N; ti++) { + sp<Type> t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + const size_t N = t->getOrderedConfigs().size(); + for (size_t ci=0; ci<N; ci++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); + //printf("Ordered config #%d: %p\n", ci, c.get()); + const size_t N = c->getEntries().size(); + for (size_t ei=0; ei<N; ei++) { + sp<Entry> e = c->getEntries().valueAt(ei); + if (e == NULL) { + continue; + } + status_t err = e->assignResourceIds(this, p->getName()); + if (err != NO_ERROR && firstError == NO_ERROR) { + firstError = err; + } + } + } + } + } + return firstError; +} + +status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) { + const size_t N = mOrderedPackages.size(); + size_t pi; + + for (pi=0; pi<N; pi++) { + sp<Package> p = mOrderedPackages.itemAt(pi); + if (p->getTypes().size() == 0) { + // Empty, skip! + continue; + } + + const size_t N = p->getOrderedTypes().size(); + size_t ti; + + for (ti=0; ti<N; ti++) { + sp<Type> t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + const size_t N = t->getOrderedConfigs().size(); + sp<AaptSymbols> typeSymbols; + typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); + for (size_t ci=0; ci<N; ci++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } + uint32_t rid = getResId(p, t, ci); + if (rid == 0) { + return UNKNOWN_ERROR; + } + if (Res_GETPACKAGE(rid) == (size_t)(p->getAssignedId()-1)) { + typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos()); + + String16 comment(c->getComment()); + typeSymbols->appendComment(String8(c->getName()), comment, c->getPos()); + //printf("Type symbol %s comment: %s\n", String8(e->getName()).string(), + // String8(comment).string()); + comment = c->getTypeComment(); + typeSymbols->appendTypeComment(String8(c->getName()), comment); + } else { +#if 0 + printf("**** NO MATCH: 0x%08x vs 0x%08x\n", + Res_GETPACKAGE(rid), p->getAssignedId()); +#endif + } + } + } + } + return NO_ERROR; +} + + +void +ResourceTable::addLocalization(const String16& name, const String8& locale) +{ + mLocalizations[name].insert(locale); +} + + +/*! + * Flag various sorts of localization problems. '+' indicates checks already implemented; + * '-' indicates checks that will be implemented in the future. + * + * + A localized string for which no default-locale version exists => warning + * + A string for which no version in an explicitly-requested locale exists => warning + * + A localized translation of an translateable="false" string => warning + * - A localized string not provided in every locale used by the table + */ +status_t +ResourceTable::validateLocalizations(void) +{ + status_t err = NO_ERROR; + const String8 defaultLocale; + + // For all strings... + for (map<String16, set<String8> >::iterator nameIter = mLocalizations.begin(); + nameIter != mLocalizations.end(); + nameIter++) { + const set<String8>& configSet = nameIter->second; // naming convenience + + // Look for strings with no default localization + if (configSet.count(defaultLocale) == 0) { + fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:", + String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]); + for (set<String8>::const_iterator locales = configSet.begin(); + locales != configSet.end(); + locales++) { + fprintf(stdout, " %s", (*locales).string()); + } + fprintf(stdout, "\n"); + // !!! TODO: throw an error here in some circumstances + } + + // Check that all requested localizations are present for this string + if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) { + const char* allConfigs = mBundle->getConfigurations(); + const char* start = allConfigs; + const char* comma; + + do { + String8 config; + comma = strchr(start, ','); + if (comma != NULL) { + config.setTo(start, comma - start); + start = comma + 1; + } else { + config.setTo(start); + } + + // don't bother with the pseudolocale "zz_ZZ" + if (config != "zz_ZZ") { + if (configSet.find(config) == configSet.end()) { + // okay, no specific localization found. it's possible that we are + // requiring a specific regional localization [e.g. de_DE] but there is an + // available string in the generic language localization [e.g. de]; + // consider that string to have fulfilled the localization requirement. + String8 region(config.string(), 2); + if (configSet.find(region) == configSet.end()) { + if (configSet.count(defaultLocale) == 0) { + fprintf(stdout, "aapt: warning: " + "**** string '%s' has no default or required localization " + "for '%s' in %s\n", + String8(nameIter->first).string(), + config.string(), + mBundle->getResourceSourceDirs()[0]); + } + } + } + } + } while (comma != NULL); + } + } + + return err; +} + +status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) +{ + ResourceFilter filter; + status_t err = filter.parse(bundle->getConfigurations()); + if (err != NO_ERROR) { + return err; + } + + const ConfigDescription nullConfig; + + const size_t N = mOrderedPackages.size(); + size_t pi; + + const static String16 mipmap16("mipmap"); + + bool useUTF8 = !bundle->getUTF16StringsOption(); + + // Iterate through all data, collecting all values (strings, + // references, etc). + StringPool valueStrings(useUTF8); + Vector<sp<Entry> > allEntries; + for (pi=0; pi<N; pi++) { + sp<Package> p = mOrderedPackages.itemAt(pi); + if (p->getTypes().size() == 0) { + // Empty, skip! + continue; + } + + StringPool typeStrings(useUTF8); + StringPool keyStrings(useUTF8); + + const size_t N = p->getOrderedTypes().size(); + for (size_t ti=0; ti<N; ti++) { + sp<Type> t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + typeStrings.add(String16("<empty>"), false); + continue; + } + const String16 typeName(t->getName()); + typeStrings.add(typeName, false); + + // This is a hack to tweak the sorting order of the final strings, + // to put stuff that is generally not language-specific first. + String8 configTypeName(typeName); + if (configTypeName == "drawable" || configTypeName == "layout" + || configTypeName == "color" || configTypeName == "anim" + || configTypeName == "interpolator" || configTypeName == "animator" + || configTypeName == "xml" || configTypeName == "menu" + || configTypeName == "mipmap" || configTypeName == "raw") { + configTypeName = "1complex"; + } else { + configTypeName = "2value"; + } + + const bool filterable = (typeName != mipmap16); + + const size_t N = t->getOrderedConfigs().size(); + for (size_t ci=0; ci<N; ci++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } + const size_t N = c->getEntries().size(); + for (size_t ei=0; ei<N; ei++) { + ConfigDescription config = c->getEntries().keyAt(ei); + if (filterable && !filter.match(config)) { + continue; + } + sp<Entry> e = c->getEntries().valueAt(ei); + if (e == NULL) { + continue; + } + e->setNameIndex(keyStrings.add(e->getName(), true)); + + // If this entry has no values for other configs, + // and is the default config, then it is special. Otherwise + // we want to add it with the config info. + ConfigDescription* valueConfig = NULL; + if (N != 1 || config == nullConfig) { + valueConfig = &config; + } + + status_t err = e->prepareFlatten(&valueStrings, this, + &configTypeName, &config); + if (err != NO_ERROR) { + return err; + } + allEntries.add(e); + } + } + } + + p->setTypeStrings(typeStrings.createStringBlock()); + p->setKeyStrings(keyStrings.createStringBlock()); + } + + if (bundle->getOutputAPKFile() != NULL) { + // Now we want to sort the value strings for better locality. This will + // cause the positions of the strings to change, so we need to go back + // through out resource entries and update them accordingly. Only need + // to do this if actually writing the output file. + valueStrings.sortByConfig(); + for (pi=0; pi<allEntries.size(); pi++) { + allEntries[pi]->remapStringValue(&valueStrings); + } + } + + ssize_t strAmt = 0; + + // Now build the array of package chunks. + Vector<sp<AaptFile> > flatPackages; + for (pi=0; pi<N; pi++) { + sp<Package> p = mOrderedPackages.itemAt(pi); + if (p->getTypes().size() == 0) { + // Empty, skip! + continue; + } + + const size_t N = p->getTypeStrings().size(); + + const size_t baseSize = sizeof(ResTable_package); + + // Start the package data. + sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8()); + ResTable_package* header = (ResTable_package*)data->editData(baseSize); + if (header == NULL) { + fprintf(stderr, "ERROR: out of memory creating ResTable_package\n"); + return NO_MEMORY; + } + memset(header, 0, sizeof(*header)); + header->header.type = htods(RES_TABLE_PACKAGE_TYPE); + header->header.headerSize = htods(sizeof(*header)); + header->id = htodl(p->getAssignedId()); + strcpy16_htod(header->name, p->getName().string()); + + // Write the string blocks. + const size_t typeStringsStart = data->getSize(); + sp<AaptFile> strFile = p->getTypeStringsData(); + ssize_t amt = data->writeData(strFile->getData(), strFile->getSize()); + #if PRINT_STRING_METRICS + fprintf(stderr, "**** type strings: %d\n", amt); + #endif + strAmt += amt; + if (amt < 0) { + return amt; + } + const size_t keyStringsStart = data->getSize(); + strFile = p->getKeyStringsData(); + amt = data->writeData(strFile->getData(), strFile->getSize()); + #if PRINT_STRING_METRICS + fprintf(stderr, "**** key strings: %d\n", amt); + #endif + strAmt += amt; + if (amt < 0) { + return amt; + } + + // Build the type chunks inside of this package. + for (size_t ti=0; ti<N; ti++) { + // Retrieve them in the same order as the type string block. + size_t len; + String16 typeName(p->getTypeStrings().stringAt(ti, &len)); + sp<Type> t = p->getTypes().valueFor(typeName); + LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"), + "Type name %s not found", + String8(typeName).string()); + + const bool filterable = (typeName != mipmap16); + + const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0; + + // Until a non-NO_ENTRY value has been written for a resource, + // that resource is invalid; validResources[i] represents + // the item at t->getOrderedConfigs().itemAt(i). + Vector<bool> validResources; + validResources.insertAt(false, 0, N); + + // First write the typeSpec chunk, containing information about + // each resource entry in this type. + { + const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N; + const size_t typeSpecStart = data->getSize(); + ResTable_typeSpec* tsHeader = (ResTable_typeSpec*) + (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart); + if (tsHeader == NULL) { + fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n"); + return NO_MEMORY; + } + memset(tsHeader, 0, sizeof(*tsHeader)); + tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE); + tsHeader->header.headerSize = htods(sizeof(*tsHeader)); + tsHeader->header.size = htodl(typeSpecSize); + tsHeader->id = ti+1; + tsHeader->entryCount = htodl(N); + + uint32_t* typeSpecFlags = (uint32_t*) + (((uint8_t*)data->editData()) + + typeSpecStart + sizeof(ResTable_typeSpec)); + memset(typeSpecFlags, 0, sizeof(uint32_t)*N); + + for (size_t ei=0; ei<N; ei++) { + sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); + if (cl->getPublic()) { + typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC); + } + const size_t CN = cl->getEntries().size(); + for (size_t ci=0; ci<CN; ci++) { + if (filterable && !filter.match(cl->getEntries().keyAt(ci))) { + continue; + } + for (size_t cj=ci+1; cj<CN; cj++) { + if (filterable && !filter.match(cl->getEntries().keyAt(cj))) { + continue; + } + typeSpecFlags[ei] |= htodl( + cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj))); + } + } + } + } + + // We need to write one type chunk for each configuration for + // which we have entries in this type. + const size_t NC = t->getUniqueConfigs().size(); + + const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N; + + for (size_t ci=0; ci<NC; ci++) { + ConfigDescription config = t->getUniqueConfigs().itemAt(ci); + + NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c " + "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " + "sw%ddp w%ddp h%ddp dir:%d\n", + ti+1, + config.mcc, config.mnc, + config.language[0] ? config.language[0] : '-', + config.language[1] ? config.language[1] : '-', + config.country[0] ? config.country[0] : '-', + config.country[1] ? config.country[1] : '-', + config.orientation, + config.uiMode, + config.touchscreen, + config.density, + config.keyboard, + config.inputFlags, + config.navigation, + config.screenWidth, + config.screenHeight, + config.smallestScreenWidthDp, + config.screenWidthDp, + config.screenHeightDp, + config.layoutDirection)); + + if (filterable && !filter.match(config)) { + continue; + } + + const size_t typeStart = data->getSize(); + + ResTable_type* tHeader = (ResTable_type*) + (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart); + if (tHeader == NULL) { + fprintf(stderr, "ERROR: out of memory creating ResTable_type\n"); + return NO_MEMORY; + } + + memset(tHeader, 0, sizeof(*tHeader)); + tHeader->header.type = htods(RES_TABLE_TYPE_TYPE); + tHeader->header.headerSize = htods(sizeof(*tHeader)); + tHeader->id = ti+1; + tHeader->entryCount = htodl(N); + tHeader->entriesStart = htodl(typeSize); + tHeader->config = config; + NOISY(printf("Writing type %d config: imsi:%d/%d lang:%c%c cnt:%c%c " + "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " + "sw%ddp w%ddp h%ddp dir:%d\n", + ti+1, + tHeader->config.mcc, tHeader->config.mnc, + tHeader->config.language[0] ? tHeader->config.language[0] : '-', + tHeader->config.language[1] ? tHeader->config.language[1] : '-', + tHeader->config.country[0] ? tHeader->config.country[0] : '-', + tHeader->config.country[1] ? tHeader->config.country[1] : '-', + tHeader->config.orientation, + tHeader->config.uiMode, + tHeader->config.touchscreen, + tHeader->config.density, + tHeader->config.keyboard, + tHeader->config.inputFlags, + tHeader->config.navigation, + tHeader->config.screenWidth, + tHeader->config.screenHeight, + tHeader->config.smallestScreenWidthDp, + tHeader->config.screenWidthDp, + tHeader->config.screenHeightDp, + tHeader->config.layoutDirection)); + tHeader->config.swapHtoD(); + + // Build the entries inside of this type. + for (size_t ei=0; ei<N; ei++) { + sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); + sp<Entry> e = cl->getEntries().valueFor(config); + + // Set the offset for this entry in its type. + uint32_t* index = (uint32_t*) + (((uint8_t*)data->editData()) + + typeStart + sizeof(ResTable_type)); + if (e != NULL) { + index[ei] = htodl(data->getSize()-typeStart-typeSize); + + // Create the entry. + ssize_t amt = e->flatten(bundle, data, cl->getPublic()); + if (amt < 0) { + return amt; + } + validResources.editItemAt(ei) = true; + } else { + index[ei] = htodl(ResTable_type::NO_ENTRY); + } + } + + // Fill in the rest of the type information. + tHeader = (ResTable_type*) + (((uint8_t*)data->editData()) + typeStart); + tHeader->header.size = htodl(data->getSize()-typeStart); + } + + for (size_t i = 0; i < N; ++i) { + if (!validResources[i]) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(i); + fprintf(stderr, "warning: no entries written for %s/%s\n", + String8(typeName).string(), String8(c->getName()).string()); + } + } + } + + // Fill in the rest of the package information. + header = (ResTable_package*)data->editData(); + header->header.size = htodl(data->getSize()); + header->typeStrings = htodl(typeStringsStart); + header->lastPublicType = htodl(p->getTypeStrings().size()); + header->keyStrings = htodl(keyStringsStart); + header->lastPublicKey = htodl(p->getKeyStrings().size()); + + flatPackages.add(data); + } + + // And now write out the final chunks. + const size_t dataStart = dest->getSize(); + + { + // blah + ResTable_header header; + memset(&header, 0, sizeof(header)); + header.header.type = htods(RES_TABLE_TYPE); + header.header.headerSize = htods(sizeof(header)); + header.packageCount = htodl(flatPackages.size()); + status_t err = dest->writeData(&header, sizeof(header)); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating ResTable_header\n"); + return err; + } + } + + ssize_t strStart = dest->getSize(); + err = valueStrings.writeStringBlock(dest); + if (err != NO_ERROR) { + return err; + } + + ssize_t amt = (dest->getSize()-strStart); + strAmt += amt; + #if PRINT_STRING_METRICS + fprintf(stderr, "**** value strings: %d\n", amt); + fprintf(stderr, "**** total strings: %d\n", strAmt); + #endif + + for (pi=0; pi<flatPackages.size(); pi++) { + err = dest->writeData(flatPackages[pi]->getData(), + flatPackages[pi]->getSize()); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n"); + return err; + } + } + + ResTable_header* header = (ResTable_header*) + (((uint8_t*)dest->getData()) + dataStart); + header->header.size = htodl(dest->getSize() - dataStart); + + NOISY(aout << "Resource table:" + << HexDump(dest->getData(), dest->getSize()) << endl); + + #if PRINT_STRING_METRICS + fprintf(stderr, "**** total resource table size: %d / %d%% strings\n", + dest->getSize(), (strAmt*100)/dest->getSize()); + #endif + + return NO_ERROR; +} + +void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp) +{ + fprintf(fp, + "<!-- This file contains <public> resource definitions for all\n" + " resources that were generated from the source data. -->\n" + "\n" + "<resources>\n"); + + writePublicDefinitions(package, fp, true); + writePublicDefinitions(package, fp, false); + + fprintf(fp, + "\n" + "</resources>\n"); +} + +void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub) +{ + bool didHeader = false; + + sp<Package> pkg = mPackages.valueFor(package); + if (pkg != NULL) { + const size_t NT = pkg->getOrderedTypes().size(); + for (size_t i=0; i<NT; i++) { + sp<Type> t = pkg->getOrderedTypes().itemAt(i); + if (t == NULL) { + continue; + } + + bool didType = false; + + const size_t NC = t->getOrderedConfigs().size(); + for (size_t j=0; j<NC; j++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(j); + if (c == NULL) { + continue; + } + + if (c->getPublic() != pub) { + continue; + } + + if (!didType) { + fprintf(fp, "\n"); + didType = true; + } + if (!didHeader) { + if (pub) { + fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n"); + fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n"); + } else { + fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n"); + fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n"); + } + didHeader = true; + } + if (!pub) { + const size_t NE = c->getEntries().size(); + for (size_t k=0; k<NE; k++) { + const SourcePos& pos = c->getEntries().valueAt(k)->getPos(); + if (pos.file != "") { + fprintf(fp," <!-- Declared at %s:%d -->\n", + pos.file.string(), pos.line); + } + } + } + fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n", + String8(t->getName()).string(), + String8(c->getName()).string(), + getResId(pkg, t, c->getEntryIndex())); + } + } + } +} + +ResourceTable::Item::Item(const SourcePos& _sourcePos, + bool _isId, + const String16& _value, + const Vector<StringPool::entry_style_span>* _style, + int32_t _format) + : sourcePos(_sourcePos) + , isId(_isId) + , value(_value) + , format(_format) + , bagKeyId(0) + , evaluating(false) +{ + if (_style) { + style = *_style; + } +} + +status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos) +{ + if (mType == TYPE_BAG) { + return NO_ERROR; + } + if (mType == TYPE_UNKNOWN) { + mType = TYPE_BAG; + return NO_ERROR; + } + sourcePos.error("Resource entry %s is already defined as a single item.\n" + "%s:%d: Originally defined here.\n", + String8(mName).string(), + mItem.sourcePos.file.string(), mItem.sourcePos.line); + return UNKNOWN_ERROR; +} + +status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos, + const String16& value, + const Vector<StringPool::entry_style_span>* style, + int32_t format, + const bool overwrite) +{ + Item item(sourcePos, false, value, style); + + if (mType == TYPE_BAG) { + const Item& item(mBag.valueAt(0)); + sourcePos.error("Resource entry %s is already defined as a bag.\n" + "%s:%d: Originally defined here.\n", + String8(mName).string(), + item.sourcePos.file.string(), item.sourcePos.line); + return UNKNOWN_ERROR; + } + if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) { + sourcePos.error("Resource entry %s is already defined.\n" + "%s:%d: Originally defined here.\n", + String8(mName).string(), + mItem.sourcePos.file.string(), mItem.sourcePos.line); + return UNKNOWN_ERROR; + } + + mType = TYPE_ITEM; + mItem = item; + mItemFormat = format; + return NO_ERROR; +} + +status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos, + const String16& key, const String16& value, + const Vector<StringPool::entry_style_span>* style, + bool replace, bool isId, int32_t format) +{ + status_t err = makeItABag(sourcePos); + if (err != NO_ERROR) { + return err; + } + + Item item(sourcePos, isId, value, style, format); + + // XXX NOTE: there is an error if you try to have a bag with two keys, + // one an attr and one an id, with the same name. Not something we + // currently ever have to worry about. + ssize_t origKey = mBag.indexOfKey(key); + if (origKey >= 0) { + if (!replace) { + const Item& item(mBag.valueAt(origKey)); + sourcePos.error("Resource entry %s already has bag item %s.\n" + "%s:%d: Originally defined here.\n", + String8(mName).string(), String8(key).string(), + item.sourcePos.file.string(), item.sourcePos.line); + return UNKNOWN_ERROR; + } + //printf("Replacing %s with %s\n", + // String8(mBag.valueFor(key).value).string(), String8(value).string()); + mBag.replaceValueFor(key, item); + } + + mBag.add(key, item); + return NO_ERROR; +} + +status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos) +{ + status_t err = makeItABag(sourcePos); + if (err != NO_ERROR) { + return err; + } + + mBag.clear(); + return NO_ERROR; +} + +status_t ResourceTable::Entry::generateAttributes(ResourceTable* table, + const String16& package) +{ + const String16 attr16("attr"); + const String16 id16("id"); + const size_t N = mBag.size(); + for (size_t i=0; i<N; i++) { + const String16& key = mBag.keyAt(i); + const Item& it = mBag.valueAt(i); + if (it.isId) { + if (!table->hasBagOrEntry(key, &id16, &package)) { + String16 value("false"); + status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package, + id16, key, value); + if (err != NO_ERROR) { + return err; + } + } + } else if (!table->hasBagOrEntry(key, &attr16, &package)) { + +#if 1 +// fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n", +// String8(key).string()); +// const Item& item(mBag.valueAt(i)); +// fprintf(stderr, "Referenced from file %s line %d\n", +// item.sourcePos.file.string(), item.sourcePos.line); +// return UNKNOWN_ERROR; +#else + char numberStr[16]; + sprintf(numberStr, "%d", ResTable_map::TYPE_ANY); + status_t err = table->addBag(SourcePos("<generated>", 0), package, + attr16, key, String16(""), + String16("^type"), + String16(numberStr), NULL, NULL); + if (err != NO_ERROR) { + return err; + } +#endif + } + } + return NO_ERROR; +} + +status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table, + const String16& package) +{ + bool hasErrors = false; + + if (mType == TYPE_BAG) { + const char* errorMsg; + const String16 style16("style"); + const String16 attr16("attr"); + const String16 id16("id"); + mParentId = 0; + if (mParent.size() > 0) { + mParentId = table->getResId(mParent, &style16, NULL, &errorMsg); + if (mParentId == 0) { + mPos.error("Error retrieving parent for item: %s '%s'.\n", + errorMsg, String8(mParent).string()); + hasErrors = true; + } + } + const size_t N = mBag.size(); + for (size_t i=0; i<N; i++) { + const String16& key = mBag.keyAt(i); + Item& it = mBag.editValueAt(i); + it.bagKeyId = table->getResId(key, + it.isId ? &id16 : &attr16, NULL, &errorMsg); + //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId); + if (it.bagKeyId == 0) { + it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg, + String8(it.isId ? id16 : attr16).string(), + String8(key).string()); + hasErrors = true; + } + } + } + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table, + const String8* configTypeName, const ConfigDescription* config) +{ + if (mType == TYPE_ITEM) { + Item& it = mItem; + AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value)); + if (!table->stringToValue(&it.parsedValue, strings, + it.value, false, true, 0, + &it.style, NULL, &ac, mItemFormat, + configTypeName, config)) { + return UNKNOWN_ERROR; + } + } else if (mType == TYPE_BAG) { + const size_t N = mBag.size(); + for (size_t i=0; i<N; i++) { + const String16& key = mBag.keyAt(i); + Item& it = mBag.editValueAt(i); + AccessorCookie ac(it.sourcePos, String8(key), String8(it.value)); + if (!table->stringToValue(&it.parsedValue, strings, + it.value, false, true, it.bagKeyId, + &it.style, NULL, &ac, it.format, + configTypeName, config)) { + return UNKNOWN_ERROR; + } + } + } else { + mPos.error("Error: entry %s is not a single item or a bag.\n", + String8(mName).string()); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +status_t ResourceTable::Entry::remapStringValue(StringPool* strings) +{ + if (mType == TYPE_ITEM) { + Item& it = mItem; + if (it.parsedValue.dataType == Res_value::TYPE_STRING) { + it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); + } + } else if (mType == TYPE_BAG) { + const size_t N = mBag.size(); + for (size_t i=0; i<N; i++) { + Item& it = mBag.editValueAt(i); + if (it.parsedValue.dataType == Res_value::TYPE_STRING) { + it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data); + } + } + } else { + mPos.error("Error: entry %s is not a single item or a bag.\n", + String8(mName).string()); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic) +{ + size_t amt = 0; + ResTable_entry header; + memset(&header, 0, sizeof(header)); + header.size = htods(sizeof(header)); + const type ty = this != NULL ? mType : TYPE_ITEM; + if (this != NULL) { + if (ty == TYPE_BAG) { + header.flags |= htods(header.FLAG_COMPLEX); + } + if (isPublic) { + header.flags |= htods(header.FLAG_PUBLIC); + } + header.key.index = htodl(mNameIndex); + } + if (ty != TYPE_BAG) { + status_t err = data->writeData(&header, sizeof(header)); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n"); + return err; + } + + const Item& it = mItem; + Res_value par; + memset(&par, 0, sizeof(par)); + par.size = htods(it.parsedValue.size); + par.dataType = it.parsedValue.dataType; + par.res0 = it.parsedValue.res0; + par.data = htodl(it.parsedValue.data); + #if 0 + printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n", + String8(mName).string(), it.parsedValue.dataType, + it.parsedValue.data, par.res0); + #endif + err = data->writeData(&par, it.parsedValue.size); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating Res_value\n"); + return err; + } + amt += it.parsedValue.size; + } else { + size_t N = mBag.size(); + size_t i; + // Create correct ordering of items. + KeyedVector<uint32_t, const Item*> items; + for (i=0; i<N; i++) { + const Item& it = mBag.valueAt(i); + items.add(it.bagKeyId, &it); + } + N = items.size(); + + ResTable_map_entry mapHeader; + memcpy(&mapHeader, &header, sizeof(header)); + mapHeader.size = htods(sizeof(mapHeader)); + mapHeader.parent.ident = htodl(mParentId); + mapHeader.count = htodl(N); + status_t err = data->writeData(&mapHeader, sizeof(mapHeader)); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n"); + return err; + } + + for (i=0; i<N; i++) { + const Item& it = *items.valueAt(i); + ResTable_map map; + map.name.ident = htodl(it.bagKeyId); + map.value.size = htods(it.parsedValue.size); + map.value.dataType = it.parsedValue.dataType; + map.value.res0 = it.parsedValue.res0; + map.value.data = htodl(it.parsedValue.data); + err = data->writeData(&map, sizeof(map)); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating Res_value\n"); + return err; + } + amt += sizeof(map); + } + } + return amt; +} + +void ResourceTable::ConfigList::appendComment(const String16& comment, + bool onlyIfEmpty) +{ + if (comment.size() <= 0) { + return; + } + if (onlyIfEmpty && mComment.size() > 0) { + return; + } + if (mComment.size() > 0) { + mComment.append(String16("\n")); + } + mComment.append(comment); +} + +void ResourceTable::ConfigList::appendTypeComment(const String16& comment) +{ + if (comment.size() <= 0) { + return; + } + if (mTypeComment.size() > 0) { + mTypeComment.append(String16("\n")); + } + mTypeComment.append(comment); +} + +status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos, + const String16& name, + const uint32_t ident) +{ + #if 0 + int32_t entryIdx = Res_GETENTRY(ident); + if (entryIdx < 0) { + sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n", + String8(mName).string(), String8(name).string(), ident); + return UNKNOWN_ERROR; + } + #endif + + int32_t typeIdx = Res_GETTYPE(ident); + if (typeIdx >= 0) { + typeIdx++; + if (mPublicIndex > 0 && mPublicIndex != typeIdx) { + sourcePos.error("Public resource %s/%s has conflicting type codes for its" + " public identifiers (0x%x vs 0x%x).\n", + String8(mName).string(), String8(name).string(), + mPublicIndex, typeIdx); + return UNKNOWN_ERROR; + } + mPublicIndex = typeIdx; + } + + if (mFirstPublicSourcePos == NULL) { + mFirstPublicSourcePos = new SourcePos(sourcePos); + } + + if (mPublic.indexOfKey(name) < 0) { + mPublic.add(name, Public(sourcePos, String16(), ident)); + } else { + Public& p = mPublic.editValueFor(name); + if (p.ident != ident) { + sourcePos.error("Public resource %s/%s has conflicting public identifiers" + " (0x%08x vs 0x%08x).\n" + "%s:%d: Originally defined here.\n", + String8(mName).string(), String8(name).string(), p.ident, ident, + p.sourcePos.file.string(), p.sourcePos.line); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +void ResourceTable::Type::canAddEntry(const String16& name) +{ + mCanAddEntries.add(name); +} + +sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry, + const SourcePos& sourcePos, + const ResTable_config* config, + bool doSetIndex, + bool overlay, + bool autoAddOverlay) +{ + int pos = -1; + sp<ConfigList> c = mConfigs.valueFor(entry); + if (c == NULL) { + if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) { + sourcePos.error("Resource at %s appears in overlay but not" + " in the base package; use <add-resource> to add.\n", + String8(entry).string()); + return NULL; + } + c = new ConfigList(entry, sourcePos); + mConfigs.add(entry, c); + pos = (int)mOrderedConfigs.size(); + mOrderedConfigs.add(c); + if (doSetIndex) { + c->setEntryIndex(pos); + } + } + + ConfigDescription cdesc; + if (config) cdesc = *config; + + sp<Entry> e = c->getEntries().valueFor(cdesc); + if (e == NULL) { + if (config != NULL) { + NOISY(printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c " + "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d " + "sw%ddp w%ddp h%ddp dir:%d\n", + sourcePos.file.string(), sourcePos.line, + config->mcc, config->mnc, + config->language[0] ? config->language[0] : '-', + config->language[1] ? config->language[1] : '-', + config->country[0] ? config->country[0] : '-', + config->country[1] ? config->country[1] : '-', + config->orientation, + config->touchscreen, + config->density, + config->keyboard, + config->inputFlags, + config->navigation, + config->screenWidth, + config->screenHeight, + config->smallestScreenWidthDp, + config->screenWidthDp, + config->screenHeightDp, + config->layoutDirection)); + } else { + NOISY(printf("New entry at %s:%d: NULL config\n", + sourcePos.file.string(), sourcePos.line)); + } + e = new Entry(entry, sourcePos); + c->addEntry(cdesc, e); + /* + if (doSetIndex) { + if (pos < 0) { + for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) { + if (mOrderedConfigs[pos] == c) { + break; + } + } + if (pos >= (int)mOrderedConfigs.size()) { + sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry"); + return NULL; + } + } + e->setEntryIndex(pos); + } + */ + } + + mUniqueConfigs.add(cdesc); + + return e; +} + +status_t ResourceTable::Type::applyPublicEntryOrder() +{ + size_t N = mOrderedConfigs.size(); + Vector<sp<ConfigList> > origOrder(mOrderedConfigs); + bool hasError = false; + + size_t i; + for (i=0; i<N; i++) { + mOrderedConfigs.replaceAt(NULL, i); + } + + const size_t NP = mPublic.size(); + //printf("Ordering %d configs from %d public defs\n", N, NP); + size_t j; + for (j=0; j<NP; j++) { + const String16& name = mPublic.keyAt(j); + const Public& p = mPublic.valueAt(j); + int32_t idx = Res_GETENTRY(p.ident); + //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n", + // String8(mName).string(), String8(name).string(), p.ident, N); + bool found = false; + for (i=0; i<N; i++) { + sp<ConfigList> e = origOrder.itemAt(i); + //printf("#%d: \"%s\"\n", i, String8(e->getName()).string()); + if (e->getName() == name) { + if (idx >= (int32_t)mOrderedConfigs.size()) { + p.sourcePos.error("Public entry identifier 0x%x entry index " + "is larger than available symbols (index %d, total symbols %d).\n", + p.ident, idx, mOrderedConfigs.size()); + hasError = true; + } else if (mOrderedConfigs.itemAt(idx) == NULL) { + e->setPublic(true); + e->setPublicSourcePos(p.sourcePos); + mOrderedConfigs.replaceAt(e, idx); + origOrder.removeAt(i); + N--; + found = true; + break; + } else { + sp<ConfigList> oe = mOrderedConfigs.itemAt(idx); + + p.sourcePos.error("Multiple entry names declared for public entry" + " identifier 0x%x in type %s (%s vs %s).\n" + "%s:%d: Originally defined here.", + idx+1, String8(mName).string(), + String8(oe->getName()).string(), + String8(name).string(), + oe->getPublicSourcePos().file.string(), + oe->getPublicSourcePos().line); + hasError = true; + } + } + } + + if (!found) { + p.sourcePos.error("Public symbol %s/%s declared here is not defined.", + String8(mName).string(), String8(name).string()); + hasError = true; + } + } + + //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size()); + + if (N != origOrder.size()) { + printf("Internal error: remaining private symbol count mismatch\n"); + N = origOrder.size(); + } + + j = 0; + for (i=0; i<N; i++) { + sp<ConfigList> e = origOrder.itemAt(i); + // There will always be enough room for the remaining entries. + while (mOrderedConfigs.itemAt(j) != NULL) { + j++; + } + mOrderedConfigs.replaceAt(e, j); + j++; + } + + return hasError ? UNKNOWN_ERROR : NO_ERROR; +} + +ResourceTable::Package::Package(const String16& name, ssize_t includedId) + : mName(name), mIncludedId(includedId), + mTypeStringsMapping(0xffffffff), + mKeyStringsMapping(0xffffffff) +{ +} + +sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type, + const SourcePos& sourcePos, + bool doSetIndex) +{ + sp<Type> t = mTypes.valueFor(type); + if (t == NULL) { + t = new Type(type, sourcePos); + mTypes.add(type, t); + mOrderedTypes.add(t); + if (doSetIndex) { + // For some reason the type's index is set to one plus the index + // in the mOrderedTypes list, rather than just the index. + t->setIndex(mOrderedTypes.size()); + } + } + return t; +} + +status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data) +{ + mTypeStringsData = data; + status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: Type string data is corrupt!\n"); + } + return err; +} + +status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data) +{ + mKeyStringsData = data; + status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: Key string data is corrupt!\n"); + } + return err; +} + +status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data, + ResStringPool* strings, + DefaultKeyedVector<String16, uint32_t>* mappings) +{ + if (data->getData() == NULL) { + return UNKNOWN_ERROR; + } + + NOISY(aout << "Setting restable string pool: " + << HexDump(data->getData(), data->getSize()) << endl); + + status_t err = strings->setTo(data->getData(), data->getSize()); + if (err == NO_ERROR) { + const size_t N = strings->size(); + for (size_t i=0; i<N; i++) { + size_t len; + mappings->add(String16(strings->stringAt(i, &len)), i); + } + } + return err; +} + +status_t ResourceTable::Package::applyPublicTypeOrder() +{ + size_t N = mOrderedTypes.size(); + Vector<sp<Type> > origOrder(mOrderedTypes); + + size_t i; + for (i=0; i<N; i++) { + mOrderedTypes.replaceAt(NULL, i); + } + + for (i=0; i<N; i++) { + sp<Type> t = origOrder.itemAt(i); + int32_t idx = t->getPublicIndex(); + if (idx > 0) { + idx--; + while (idx >= (int32_t)mOrderedTypes.size()) { + mOrderedTypes.add(); + } + if (mOrderedTypes.itemAt(idx) != NULL) { + sp<Type> ot = mOrderedTypes.itemAt(idx); + t->getFirstPublicSourcePos().error("Multiple type names declared for public type" + " identifier 0x%x (%s vs %s).\n" + "%s:%d: Originally defined here.", + idx, String8(ot->getName()).string(), + String8(t->getName()).string(), + ot->getFirstPublicSourcePos().file.string(), + ot->getFirstPublicSourcePos().line); + return UNKNOWN_ERROR; + } + mOrderedTypes.replaceAt(t, idx); + origOrder.removeAt(i); + i--; + N--; + } + } + + size_t j=0; + for (i=0; i<N; i++) { + sp<Type> t = origOrder.itemAt(i); + // There will always be enough room for the remaining types. + while (mOrderedTypes.itemAt(j) != NULL) { + j++; + } + mOrderedTypes.replaceAt(t, j); + } + + return NO_ERROR; +} + +sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) +{ + sp<Package> p = mPackages.valueFor(package); + if (p == NULL) { + if (mIsAppPackage) { + if (mHaveAppPackage) { + fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n" + "Use -x to create extended resources.\n"); + return NULL; + } + mHaveAppPackage = true; + p = new Package(package, 127); + } else { + p = new Package(package, mNextPackageId); + } + //printf("*** NEW PACKAGE: \"%s\" id=%d\n", + // String8(package).string(), p->getAssignedId()); + mPackages.add(package, p); + mOrderedPackages.add(p); + mNextPackageId++; + } + return p; +} + +sp<ResourceTable::Type> ResourceTable::getType(const String16& package, + const String16& type, + const SourcePos& sourcePos, + bool doSetIndex) +{ + sp<Package> p = getPackage(package); + if (p == NULL) { + return NULL; + } + return p->getType(type, sourcePos, doSetIndex); +} + +sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package, + const String16& type, + const String16& name, + const SourcePos& sourcePos, + bool overlay, + const ResTable_config* config, + bool doSetIndex) +{ + sp<Type> t = getType(package, type, sourcePos, doSetIndex); + if (t == NULL) { + return NULL; + } + return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay()); +} + +sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID, + const ResTable_config* config) const +{ + int pid = Res_GETPACKAGE(resID)+1; + const size_t N = mOrderedPackages.size(); + size_t i; + sp<Package> p; + for (i=0; i<N; i++) { + sp<Package> check = mOrderedPackages[i]; + if (check->getAssignedId() == pid) { + p = check; + break; + } + + } + if (p == NULL) { + fprintf(stderr, "warning: Package not found for resource #%08x\n", resID); + return NULL; + } + + int tid = Res_GETTYPE(resID); + if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) { + fprintf(stderr, "warning: Type not found for resource #%08x\n", resID); + return NULL; + } + sp<Type> t = p->getOrderedTypes()[tid]; + + int eid = Res_GETENTRY(resID); + if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) { + fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID); + return NULL; + } + + sp<ConfigList> c = t->getOrderedConfigs()[eid]; + if (c == NULL) { + fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID); + return NULL; + } + + ConfigDescription cdesc; + if (config) cdesc = *config; + sp<Entry> e = c->getEntries().valueFor(cdesc); + if (c == NULL) { + fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID); + return NULL; + } + + return e; +} + +const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const +{ + sp<const Entry> e = getEntry(resID); + if (e == NULL) { + return NULL; + } + + const size_t N = e->getBag().size(); + for (size_t i=0; i<N; i++) { + const Item& it = e->getBag().valueAt(i); + if (it.bagKeyId == 0) { + fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n", + String8(e->getName()).string(), + String8(e->getBag().keyAt(i)).string()); + } + if (it.bagKeyId == attrID) { + return ⁢ + } + } + + return NULL; +} + +bool ResourceTable::getItemValue( + uint32_t resID, uint32_t attrID, Res_value* outValue) +{ + const Item* item = getItem(resID, attrID); + + bool res = false; + if (item != NULL) { + if (item->evaluating) { + sp<const Entry> e = getEntry(resID); + const size_t N = e->getBag().size(); + size_t i; + for (i=0; i<N; i++) { + if (&e->getBag().valueAt(i) == item) { + break; + } + } + fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n", + String8(e->getName()).string(), + String8(e->getBag().keyAt(i)).string()); + return false; + } + item->evaluating = true; + res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId); + NOISY( + if (res) { + printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n", + resID, attrID, String8(getEntry(resID)->getName()).string(), + outValue->dataType, outValue->data); + } else { + printf("getItemValue of #%08x[#%08x]: failed\n", + resID, attrID); + } + ); + item->evaluating = false; + } + return res; +} diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h new file mode 100644 index 0000000..a3e0666 --- /dev/null +++ b/tools/aapt/ResourceTable.h @@ -0,0 +1,557 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#ifndef RESOURCE_TABLE_H +#define RESOURCE_TABLE_H + +#include "StringPool.h" +#include "SourcePos.h" + +#include <set> +#include <map> + +using namespace std; + +class XMLNode; +class ResourceTable; + +enum { + XML_COMPILE_STRIP_COMMENTS = 1<<0, + XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1, + XML_COMPILE_COMPACT_WHITESPACE = 1<<2, + XML_COMPILE_STRIP_WHITESPACE = 1<<3, + XML_COMPILE_STRIP_RAW_VALUES = 1<<4, + XML_COMPILE_UTF8 = 1<<5, + + XML_COMPILE_STANDARD_RESOURCE = + XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS + | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES +}; + +status_t compileXmlFile(const sp<AaptAssets>& assets, + const sp<AaptFile>& target, + ResourceTable* table, + int options = XML_COMPILE_STANDARD_RESOURCE); + +status_t compileXmlFile(const sp<AaptAssets>& assets, + const sp<AaptFile>& target, + const sp<AaptFile>& outTarget, + ResourceTable* table, + int options = XML_COMPILE_STANDARD_RESOURCE); + +status_t compileXmlFile(const sp<AaptAssets>& assets, + const sp<XMLNode>& xmlTree, + const sp<AaptFile>& target, + ResourceTable* table, + int options = XML_COMPILE_STANDARD_RESOURCE); + +status_t compileResourceFile(Bundle* bundle, + const sp<AaptAssets>& assets, + const sp<AaptFile>& in, + const ResTable_config& defParams, + const bool overwrite, + ResourceTable* outTable); + +struct AccessorCookie +{ + SourcePos sourcePos; + String8 attr; + String8 value; + + AccessorCookie(const SourcePos&p, const String8& a, const String8& v) + :sourcePos(p), + attr(a), + value(v) + { + } +}; + +class ResourceTable : public ResTable::Accessor +{ +public: + class Package; + class Type; + class Entry; + + struct ConfigDescription : public ResTable_config { + ConfigDescription() { + memset(this, 0, sizeof(*this)); + size = sizeof(ResTable_config); + } + ConfigDescription(const ResTable_config&o) { + *static_cast<ResTable_config*>(this) = o; + size = sizeof(ResTable_config); + } + ConfigDescription(const ConfigDescription&o) { + *static_cast<ResTable_config*>(this) = o; + } + + ConfigDescription& operator=(const ResTable_config& o) { + *static_cast<ResTable_config*>(this) = o; + size = sizeof(ResTable_config); + return *this; + } + ConfigDescription& operator=(const ConfigDescription& o) { + *static_cast<ResTable_config*>(this) = o; + return *this; + } + + inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } + inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } + inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } + inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } + inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } + inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } + }; + + ResourceTable(Bundle* bundle, const String16& assetsPackage); + + status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets); + + status_t addPublic(const SourcePos& pos, + const String16& package, + const String16& type, + const String16& name, + const uint32_t ident); + + status_t addEntry(const SourcePos& pos, + const String16& package, + const String16& type, + const String16& name, + const String16& value, + const Vector<StringPool::entry_style_span>* style = NULL, + const ResTable_config* params = NULL, + const bool doSetIndex = false, + const int32_t format = ResTable_map::TYPE_ANY, + const bool overwrite = false); + + status_t startBag(const SourcePos& pos, + const String16& package, + const String16& type, + const String16& name, + const String16& bagParent, + const ResTable_config* params = NULL, + bool overlay = false, + bool replace = false, + bool isId = false); + + status_t addBag(const SourcePos& pos, + const String16& package, + const String16& type, + const String16& name, + const String16& bagParent, + const String16& bagKey, + const String16& value, + const Vector<StringPool::entry_style_span>* style = NULL, + const ResTable_config* params = NULL, + bool replace = false, + bool isId = false, + const int32_t format = ResTable_map::TYPE_ANY); + + bool hasBagOrEntry(const String16& package, + const String16& type, + const String16& name) const; + + bool hasBagOrEntry(const String16& package, + const String16& type, + const String16& name, + const ResTable_config& config) const; + + bool hasBagOrEntry(const String16& ref, + const String16* defType = NULL, + const String16* defPackage = NULL); + + bool appendComment(const String16& package, + const String16& type, + const String16& name, + const String16& comment, + bool onlyIfEmpty = false); + + bool appendTypeComment(const String16& package, + const String16& type, + const String16& name, + const String16& comment); + + void canAddEntry(const SourcePos& pos, + const String16& package, const String16& type, const String16& name); + + size_t size() const; + size_t numLocalResources() const; + bool hasResources() const; + + sp<AaptFile> flatten(Bundle*); + + static inline uint32_t makeResId(uint32_t packageId, + uint32_t typeId, + uint32_t nameId) + { + return nameId | (typeId<<16) | (packageId<<24); + } + + static inline uint32_t getResId(const sp<Package>& p, + const sp<Type>& t, + uint32_t nameId); + + uint32_t getResId(const String16& package, + const String16& type, + const String16& name, + bool onlyPublic = true) const; + + uint32_t getResId(const String16& ref, + const String16* defType = NULL, + const String16* defPackage = NULL, + const char** outErrorMsg = NULL, + bool onlyPublic = true) const; + + static bool isValidResourceName(const String16& s); + + bool stringToValue(Res_value* outValue, StringPool* pool, + const String16& str, + bool preserveSpaces, bool coerceType, + uint32_t attrID, + const Vector<StringPool::entry_style_span>* style = NULL, + String16* outStr = NULL, void* accessorCookie = NULL, + uint32_t attrType = ResTable_map::TYPE_ANY, + const String8* configTypeName = NULL, + const ConfigDescription* config = NULL); + + status_t assignResourceIds(); + status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL); + void addLocalization(const String16& name, const String8& locale); + status_t validateLocalizations(void); + + status_t flatten(Bundle*, const sp<AaptFile>& dest); + + void writePublicDefinitions(const String16& package, FILE* fp); + + virtual uint32_t getCustomResource(const String16& package, + const String16& type, + const String16& name) const; + virtual uint32_t getCustomResourceWithCreation(const String16& package, + const String16& type, + const String16& name, + const bool createIfNeeded); + virtual uint32_t getRemappedPackage(uint32_t origPackage) const; + virtual bool getAttributeType(uint32_t attrID, uint32_t* outType); + virtual bool getAttributeMin(uint32_t attrID, uint32_t* outMin); + virtual bool getAttributeMax(uint32_t attrID, uint32_t* outMax); + virtual bool getAttributeKeys(uint32_t attrID, Vector<String16>* outKeys); + virtual bool getAttributeEnum(uint32_t attrID, + const char16_t* name, size_t nameLen, + Res_value* outValue); + virtual bool getAttributeFlags(uint32_t attrID, + const char16_t* name, size_t nameLen, + Res_value* outValue); + virtual uint32_t getAttributeL10N(uint32_t attrID); + + virtual bool getLocalizationSetting(); + virtual void reportError(void* accessorCookie, const char* fmt, ...); + + void setCurrentXmlPos(const SourcePos& pos) { mCurrentXmlPos = pos; } + + class Item { + public: + Item() : isId(false), format(ResTable_map::TYPE_ANY), bagKeyId(0), evaluating(false) + { memset(&parsedValue, 0, sizeof(parsedValue)); } + Item(const SourcePos& pos, + bool _isId, + const String16& _value, + const Vector<StringPool::entry_style_span>* _style = NULL, + int32_t format = ResTable_map::TYPE_ANY); + Item(const Item& o) : sourcePos(o.sourcePos), + isId(o.isId), value(o.value), style(o.style), + format(o.format), bagKeyId(o.bagKeyId), evaluating(false) { + memset(&parsedValue, 0, sizeof(parsedValue)); + } + ~Item() { } + + Item& operator=(const Item& o) { + sourcePos = o.sourcePos; + isId = o.isId; + value = o.value; + style = o.style; + format = o.format; + bagKeyId = o.bagKeyId; + parsedValue = o.parsedValue; + return *this; + } + + SourcePos sourcePos; + mutable bool isId; + String16 value; + Vector<StringPool::entry_style_span> style; + int32_t format; + uint32_t bagKeyId; + mutable bool evaluating; + Res_value parsedValue; + }; + + class Entry : public RefBase { + public: + Entry(const String16& name, const SourcePos& pos) + : mName(name), mType(TYPE_UNKNOWN), + mItemFormat(ResTable_map::TYPE_ANY), mNameIndex(-1), mPos(pos) + { } + virtual ~Entry() { } + + enum type { + TYPE_UNKNOWN = 0, + TYPE_ITEM, + TYPE_BAG + }; + + String16 getName() const { return mName; } + type getType() const { return mType; } + + void setParent(const String16& parent) { mParent = parent; } + String16 getParent() const { return mParent; } + + status_t makeItABag(const SourcePos& sourcePos); + + status_t emptyBag(const SourcePos& sourcePos); + + status_t setItem(const SourcePos& pos, + const String16& value, + const Vector<StringPool::entry_style_span>* style = NULL, + int32_t format = ResTable_map::TYPE_ANY, + const bool overwrite = false); + + status_t addToBag(const SourcePos& pos, + const String16& key, const String16& value, + const Vector<StringPool::entry_style_span>* style = NULL, + bool replace=false, bool isId = false, + int32_t format = ResTable_map::TYPE_ANY); + + // Index of the entry's name string in the key pool. + int32_t getNameIndex() const { return mNameIndex; } + void setNameIndex(int32_t index) { mNameIndex = index; } + + const Item* getItem() const { return mType == TYPE_ITEM ? &mItem : NULL; } + const KeyedVector<String16, Item>& getBag() const { return mBag; } + + status_t generateAttributes(ResourceTable* table, + const String16& package); + + status_t assignResourceIds(ResourceTable* table, + const String16& package); + + status_t prepareFlatten(StringPool* strings, ResourceTable* table, + const String8* configTypeName, const ConfigDescription* config); + + status_t remapStringValue(StringPool* strings); + + ssize_t flatten(Bundle*, const sp<AaptFile>& data, bool isPublic); + + const SourcePos& getPos() const { return mPos; } + + private: + String16 mName; + String16 mParent; + type mType; + Item mItem; + int32_t mItemFormat; + KeyedVector<String16, Item> mBag; + int32_t mNameIndex; + uint32_t mParentId; + SourcePos mPos; + }; + + class ConfigList : public RefBase { + public: + ConfigList(const String16& name, const SourcePos& pos) + : mName(name), mPos(pos), mPublic(false), mEntryIndex(-1) { } + virtual ~ConfigList() { } + + String16 getName() const { return mName; } + const SourcePos& getPos() const { return mPos; } + + void appendComment(const String16& comment, bool onlyIfEmpty = false); + const String16& getComment() const { return mComment; } + + void appendTypeComment(const String16& comment); + const String16& getTypeComment() const { return mTypeComment; } + + // Index of this entry in its Type. + int32_t getEntryIndex() const { return mEntryIndex; } + void setEntryIndex(int32_t index) { mEntryIndex = index; } + + void setPublic(bool pub) { mPublic = pub; } + bool getPublic() const { return mPublic; } + void setPublicSourcePos(const SourcePos& pos) { mPublicSourcePos = pos; } + const SourcePos& getPublicSourcePos() { return mPublicSourcePos; } + + void addEntry(const ResTable_config& config, const sp<Entry>& entry) { + mEntries.add(config, entry); + } + + const DefaultKeyedVector<ConfigDescription, sp<Entry> >& getEntries() const { return mEntries; } + private: + const String16 mName; + const SourcePos mPos; + String16 mComment; + String16 mTypeComment; + bool mPublic; + SourcePos mPublicSourcePos; + int32_t mEntryIndex; + DefaultKeyedVector<ConfigDescription, sp<Entry> > mEntries; + }; + + class Public { + public: + Public() : sourcePos(), ident(0) { } + Public(const SourcePos& pos, + const String16& _comment, + uint32_t _ident) + : sourcePos(pos), + comment(_comment), ident(_ident) { } + Public(const Public& o) : sourcePos(o.sourcePos), + comment(o.comment), ident(o.ident) { } + ~Public() { } + + Public& operator=(const Public& o) { + sourcePos = o.sourcePos; + comment = o.comment; + ident = o.ident; + return *this; + } + + SourcePos sourcePos; + String16 comment; + uint32_t ident; + }; + + class Type : public RefBase { + public: + Type(const String16& name, const SourcePos& pos) + : mName(name), mFirstPublicSourcePos(NULL), mPublicIndex(-1), mIndex(-1), mPos(pos) + { } + virtual ~Type() { delete mFirstPublicSourcePos; } + + status_t addPublic(const SourcePos& pos, + const String16& name, + const uint32_t ident); + + void canAddEntry(const String16& name); + + String16 getName() const { return mName; } + sp<Entry> getEntry(const String16& entry, + const SourcePos& pos, + const ResTable_config* config = NULL, + bool doSetIndex = false, + bool overlay = false, + bool autoAddOverlay = false); + + const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; } + + int32_t getPublicIndex() const { return mPublicIndex; } + + int32_t getIndex() const { return mIndex; } + void setIndex(int32_t index) { mIndex = index; } + + status_t applyPublicEntryOrder(); + + const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; } + + const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; } + const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; } + + const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; } + + const SourcePos& getPos() const { return mPos; } + private: + String16 mName; + SourcePos* mFirstPublicSourcePos; + DefaultKeyedVector<String16, Public> mPublic; + SortedVector<ConfigDescription> mUniqueConfigs; + DefaultKeyedVector<String16, sp<ConfigList> > mConfigs; + Vector<sp<ConfigList> > mOrderedConfigs; + SortedVector<String16> mCanAddEntries; + int32_t mPublicIndex; + int32_t mIndex; + SourcePos mPos; + }; + + class Package : public RefBase { + public: + Package(const String16& name, ssize_t includedId=-1); + virtual ~Package() { } + + String16 getName() const { return mName; } + sp<Type> getType(const String16& type, + const SourcePos& pos, + bool doSetIndex = false); + + ssize_t getAssignedId() const { return mIncludedId; } + + const ResStringPool& getTypeStrings() const { return mTypeStrings; } + uint32_t indexOfTypeString(const String16& s) const { return mTypeStringsMapping.valueFor(s); } + const sp<AaptFile> getTypeStringsData() const { return mTypeStringsData; } + status_t setTypeStrings(const sp<AaptFile>& data); + + const ResStringPool& getKeyStrings() const { return mKeyStrings; } + uint32_t indexOfKeyString(const String16& s) const { return mKeyStringsMapping.valueFor(s); } + const sp<AaptFile> getKeyStringsData() const { return mKeyStringsData; } + status_t setKeyStrings(const sp<AaptFile>& data); + + status_t applyPublicTypeOrder(); + + const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; } + const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; } + + private: + status_t setStrings(const sp<AaptFile>& data, + ResStringPool* strings, + DefaultKeyedVector<String16, uint32_t>* mappings); + + const String16 mName; + const ssize_t mIncludedId; + DefaultKeyedVector<String16, sp<Type> > mTypes; + Vector<sp<Type> > mOrderedTypes; + sp<AaptFile> mTypeStringsData; + sp<AaptFile> mKeyStringsData; + ResStringPool mTypeStrings; + ResStringPool mKeyStrings; + DefaultKeyedVector<String16, uint32_t> mTypeStringsMapping; + DefaultKeyedVector<String16, uint32_t> mKeyStringsMapping; + }; + +private: + void writePublicDefinitions(const String16& package, FILE* fp, bool pub); + sp<Package> getPackage(const String16& package); + sp<Type> getType(const String16& package, + const String16& type, + const SourcePos& pos, + bool doSetIndex = false); + sp<Entry> getEntry(const String16& package, + const String16& type, + const String16& name, + const SourcePos& pos, + bool overlay, + const ResTable_config* config = NULL, + bool doSetIndex = false); + sp<const Entry> getEntry(uint32_t resID, + const ResTable_config* config = NULL) const; + const Item* getItem(uint32_t resID, uint32_t attrID) const; + bool getItemValue(uint32_t resID, uint32_t attrID, + Res_value* outValue); + + + String16 mAssetsPackage; + sp<AaptAssets> mAssets; + DefaultKeyedVector<String16, sp<Package> > mPackages; + Vector<sp<Package> > mOrderedPackages; + uint32_t mNextPackageId; + bool mHaveAppPackage; + bool mIsAppPackage; + size_t mNumLocal; + SourcePos mCurrentXmlPos; + Bundle* mBundle; + + // key = string resource name, value = set of locales in which that name is defined + map<String16, set<String8> > mLocalizations; +}; + +#endif diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp new file mode 100644 index 0000000..e2a921c --- /dev/null +++ b/tools/aapt/SourcePos.cpp @@ -0,0 +1,171 @@ +#include "SourcePos.h" + +#include <stdarg.h> +#include <vector> + +using namespace std; + + +// ErrorPos +// ============================================================================= +struct ErrorPos +{ + String8 file; + int line; + String8 error; + bool fatal; + + ErrorPos(); + ErrorPos(const ErrorPos& that); + ErrorPos(const String8& file, int line, const String8& error, bool fatal); + ~ErrorPos(); + bool operator<(const ErrorPos& rhs) const; + bool operator==(const ErrorPos& rhs) const; + ErrorPos& operator=(const ErrorPos& rhs); + + void print(FILE* to) const; +}; + +static vector<ErrorPos> g_errors; + +ErrorPos::ErrorPos() + :line(-1), fatal(false) +{ +} + +ErrorPos::ErrorPos(const ErrorPos& that) + :file(that.file), + line(that.line), + error(that.error), + fatal(that.fatal) +{ +} + +ErrorPos::ErrorPos(const String8& f, int l, const String8& e, bool fat) + :file(f), + line(l), + error(e), + fatal(fat) +{ +} + +ErrorPos::~ErrorPos() +{ +} + +bool +ErrorPos::operator<(const ErrorPos& rhs) const +{ + if (this->file < rhs.file) return true; + if (this->file == rhs.file) { + if (this->line < rhs.line) return true; + if (this->line == rhs.line) { + if (this->error < rhs.error) return true; + } + } + return false; +} + +bool +ErrorPos::operator==(const ErrorPos& rhs) const +{ + return this->file == rhs.file + && this->line == rhs.line + && this->error == rhs.error; +} + +ErrorPos& +ErrorPos::operator=(const ErrorPos& rhs) +{ + this->file = rhs.file; + this->line = rhs.line; + this->error = rhs.error; + return *this; +} + +void +ErrorPos::print(FILE* to) const +{ + const char* type = fatal ? "error:" : "warning:"; + + if (this->line >= 0) { + fprintf(to, "%s:%d: %s %s\n", this->file.string(), this->line, type, this->error.string()); + } else { + fprintf(to, "%s: %s %s\n", this->file.string(), type, this->error.string()); + } +} + +// SourcePos +// ============================================================================= +SourcePos::SourcePos(const String8& f, int l) + : file(f), line(l) +{ +} + +SourcePos::SourcePos(const SourcePos& that) + : file(that.file), line(that.line) +{ +} + +SourcePos::SourcePos() + : file("???", 0), line(-1) +{ +} + +SourcePos::~SourcePos() +{ +} + +int +SourcePos::error(const char* fmt, ...) const +{ + int retval=0; + char buf[1024]; + va_list ap; + va_start(ap, fmt); + retval = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + char* p = buf + retval - 1; + while (p > buf && *p == '\n') { + *p = '\0'; + p--; + } + g_errors.push_back(ErrorPos(this->file, this->line, String8(buf), true)); + return retval; +} + +int +SourcePos::warning(const char* fmt, ...) const +{ + int retval=0; + char buf[1024]; + va_list ap; + va_start(ap, fmt); + retval = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + char* p = buf + retval - 1; + while (p > buf && *p == '\n') { + *p = '\0'; + p--; + } + ErrorPos(this->file, this->line, String8(buf), false).print(stderr); + return retval; +} + +bool +SourcePos::hasErrors() +{ + return g_errors.size() > 0; +} + +void +SourcePos::printErrors(FILE* to) +{ + vector<ErrorPos>::const_iterator it; + for (it=g_errors.begin(); it!=g_errors.end(); it++) { + it->print(to); + } +} + + + diff --git a/tools/aapt/SourcePos.h b/tools/aapt/SourcePos.h new file mode 100644 index 0000000..33f72a9 --- /dev/null +++ b/tools/aapt/SourcePos.h @@ -0,0 +1,28 @@ +#ifndef SOURCEPOS_H +#define SOURCEPOS_H + +#include <utils/String8.h> +#include <stdio.h> + +using namespace android; + +class SourcePos +{ +public: + String8 file; + int line; + + SourcePos(const String8& f, int l); + SourcePos(const SourcePos& that); + SourcePos(); + ~SourcePos(); + + int error(const char* fmt, ...) const; + int warning(const char* fmt, ...) const; + + static bool hasErrors(); + static void printErrors(FILE* to); +}; + + +#endif // SOURCEPOS_H diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp new file mode 100644 index 0000000..158b391 --- /dev/null +++ b/tools/aapt/StringPool.cpp @@ -0,0 +1,574 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "StringPool.h" +#include "ResourceTable.h" + +#include <utils/ByteOrder.h> +#include <utils/SortedVector.h> +#include "qsort_r_compat.h" + +#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 + +void strcpy16_htod(uint16_t* dst, const uint16_t* src) +{ + while (*src) { + char16_t s = htods(*src); + *dst++ = s; + src++; + } + *dst = 0; +} + +void printStringPool(const ResStringPool* pool) +{ + SortedVector<const void*> uniqueStrings; + const size_t N = pool->size(); + for (size_t i=0; i<N; i++) { + size_t len; + if (pool->isUTF8()) { + uniqueStrings.add(pool->string8At(i, &len)); + } else { + uniqueStrings.add(pool->stringAt(i, &len)); + } + } + + printf("String pool of " ZD " unique %s %s strings, " ZD " entries and " + ZD " styles using " ZD " bytes:\n", + (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16", + pool->isSorted() ? "sorted" : "non-sorted", + (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes()); + + const size_t NS = pool->size(); + for (size_t s=0; s<NS; s++) { + String8 str = pool->string8ObjectAt(s); + printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string()); + } +} + +String8 StringPool::entry::makeConfigsString() const { + String8 configStr(configTypeName); + if (configStr.size() > 0) configStr.append(" "); + if (configs.size() > 0) { + for (size_t j=0; j<configs.size(); j++) { + if (j > 0) configStr.append(", "); + configStr.append(configs[j].toString()); + } + } else { + configStr = "(none)"; + } + return configStr; +} + +int StringPool::entry::compare(const entry& o) const { + // Strings with styles go first, to reduce the size of the styles array. + // We don't care about the relative order of these strings. + if (hasStyles) { + return o.hasStyles ? 0 : -1; + } + if (o.hasStyles) { + return 1; + } + + // Sort unstyled strings by type, then by logical configuration. + int comp = configTypeName.compare(o.configTypeName); + if (comp != 0) { + return comp; + } + const size_t LHN = configs.size(); + const size_t RHN = o.configs.size(); + size_t i=0; + while (i < LHN && i < RHN) { + comp = configs[i].compareLogical(o.configs[i]); + if (comp != 0) { + return comp; + } + i++; + } + if (LHN < RHN) return -1; + else if (LHN > RHN) return 1; + return 0; +} + +StringPool::StringPool(bool utf8) : + mUTF8(utf8), mValues(-1) +{ +} + +ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans, + const String8* configTypeName, const ResTable_config* config) +{ + ssize_t res = add(value, false, configTypeName, config); + if (res >= 0) { + addStyleSpans(res, spans); + } + return res; +} + +ssize_t StringPool::add(const String16& value, + bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config) +{ + ssize_t vidx = mValues.indexOfKey(value); + ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1; + ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1; + if (eidx < 0) { + eidx = mEntries.add(entry(value)); + if (eidx < 0) { + fprintf(stderr, "Failure adding string %s\n", String8(value).string()); + return eidx; + } + } + + if (configTypeName != NULL) { + entry& ent = mEntries.editItemAt(eidx); + NOISY(printf("*** adding config type name %s, was %s\n", + configTypeName->string(), ent.configTypeName.string())); + if (ent.configTypeName.size() <= 0) { + ent.configTypeName = *configTypeName; + } else if (ent.configTypeName != *configTypeName) { + ent.configTypeName = " "; + } + } + + if (config != NULL) { + // Add this to the set of configs associated with the string. + entry& ent = mEntries.editItemAt(eidx); + size_t addPos; + for (addPos=0; addPos<ent.configs.size(); addPos++) { + int cmp = ent.configs.itemAt(addPos).compareLogical(*config); + if (cmp >= 0) { + if (cmp > 0) { + NOISY(printf("*** inserting config: %s\n", config->toString().string())); + ent.configs.insertAt(*config, addPos); + } + break; + } + } + if (addPos >= ent.configs.size()) { + NOISY(printf("*** adding config: %s\n", config->toString().string())); + ent.configs.add(*config); + } + } + + const bool first = vidx < 0; + const bool styled = (pos >= 0 && (size_t)pos < mEntryStyleArray.size()) ? + mEntryStyleArray[pos].spans.size() : 0; + if (first || styled || !mergeDuplicates) { + pos = mEntryArray.add(eidx); + if (first) { + vidx = mValues.add(value, pos); + } + entry& ent = mEntries.editItemAt(eidx); + ent.indices.add(pos); + } + + NOISY(printf("Adding string %s to pool: pos=%d eidx=%d vidx=%d\n", + String8(value).string(), pos, eidx, vidx)); + + return pos; +} + +status_t StringPool::addStyleSpan(size_t idx, const String16& name, + uint32_t start, uint32_t end) +{ + entry_style_span span; + span.name = name; + span.span.firstChar = start; + span.span.lastChar = end; + return addStyleSpan(idx, span); +} + +status_t StringPool::addStyleSpans(size_t idx, const Vector<entry_style_span>& spans) +{ + const size_t N=spans.size(); + for (size_t i=0; i<N; i++) { + status_t err = addStyleSpan(idx, spans[i]); + if (err != NO_ERROR) { + return err; + } + } + return NO_ERROR; +} + +status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span) +{ + // Place blank entries in the span array up to this index. + while (mEntryStyleArray.size() <= idx) { + mEntryStyleArray.add(); + } + + entry_style& style = mEntryStyleArray.editItemAt(idx); + style.spans.add(span); + mEntries.editItemAt(mEntryArray[idx]).hasStyles = true; + return NO_ERROR; +} + +int StringPool::config_sort(void* state, const void* lhs, const void* rhs) +{ + StringPool* pool = (StringPool*)state; + const entry& lhe = pool->mEntries[pool->mEntryArray[*static_cast<const size_t*>(lhs)]]; + const entry& rhe = pool->mEntries[pool->mEntryArray[*static_cast<const size_t*>(rhs)]]; + return lhe.compare(rhe); +} + +void StringPool::sortByConfig() +{ + LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted."); + + const size_t N = mEntryArray.size(); + + // This is a vector that starts out with a 1:1 mapping to entries + // in the array, which we will sort to come up with the desired order. + // At that point it maps from the new position in the array to the + // original position the entry appeared. + Vector<size_t> newPosToOriginalPos; + newPosToOriginalPos.setCapacity(N); + for (size_t i=0; i < N; i++) { + newPosToOriginalPos.add(i); + } + + // Sort the array. + NOISY(printf("SORTING STRINGS BY CONFIGURATION...\n")); + // Vector::sort uses insertion sort, which is very slow for this data set. + // Use quicksort instead because we don't need a stable sort here. + qsort_r_compat(newPosToOriginalPos.editArray(), N, sizeof(size_t), this, config_sort); + //newPosToOriginalPos.sort(config_sort, this); + NOISY(printf("DONE SORTING STRINGS BY CONFIGURATION.\n")); + + // Create the reverse mapping from the original position in the array + // to the new position where it appears in the sorted array. This is + // so that clients can re-map any positions they had previously stored. + mOriginalPosToNewPos = newPosToOriginalPos; + for (size_t i=0; i<N; i++) { + mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i; + } + +#if 0 + SortedVector<entry> entries; + + for (size_t i=0; i<N; i++) { + printf("#%d was %d: %s\n", i, newPosToOriginalPos[i], + mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string()); + entries.add(mEntries[mEntryArray[i]]); + } + + for (size_t i=0; i<entries.size(); i++) { + printf("Sorted config #%d: %s\n", i, + entries[i].makeConfigsString().string()); + } +#endif + + // Now we rebuild the arrays. + Vector<entry> newEntries; + Vector<size_t> newEntryArray; + Vector<entry_style> newEntryStyleArray; + DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset; + + for (size_t i=0; i<N; i++) { + // We are filling in new offset 'i'; oldI is where we can find it + // in the original data structure. + size_t oldI = newPosToOriginalPos[i]; + // This is the actual entry associated with the old offset. + const entry& oldEnt = mEntries[mEntryArray[oldI]]; + // This is the same entry the last time we added it to the + // new entry array, if any. + ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI); + size_t newOffset; + if (newIndexOfOffset < 0) { + // This is the first time we have seen the entry, so add + // it. + newOffset = newEntries.add(oldEnt); + newEntries.editItemAt(newOffset).indices.clear(); + } else { + // We have seen this entry before, use the existing one + // instead of adding it again. + newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset); + } + // Update the indices to include this new position. + newEntries.editItemAt(newOffset).indices.add(i); + // And add the offset of the entry to the new entry array. + newEntryArray.add(newOffset); + // Add any old style to the new style array. + if (mEntryStyleArray.size() > 0) { + if (oldI < mEntryStyleArray.size()) { + newEntryStyleArray.add(mEntryStyleArray[oldI]); + } else { + newEntryStyleArray.add(entry_style()); + } + } + } + + // Now trim any entries at the end of the new style array that are + // not needed. + for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) { + const entry_style& style = newEntryStyleArray[i]; + if (style.spans.size() > 0) { + // That's it. + break; + } + // This one is not needed; remove. + newEntryStyleArray.removeAt(i); + } + + // All done, install the new data structures and upate mValues with + // the new positions. + mEntries = newEntries; + mEntryArray = newEntryArray; + mEntryStyleArray = newEntryStyleArray; + mValues.clear(); + for (size_t i=0; i<mEntries.size(); i++) { + const entry& ent = mEntries[i]; + mValues.add(ent.value, ent.indices[0]); + } + +#if 0 + printf("FINAL SORTED STRING CONFIGS:\n"); + for (size_t i=0; i<mEntries.size(); i++) { + const entry& ent = mEntries[i]; + printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(), + String8(ent.value).string()); + } +#endif +} + +sp<AaptFile> StringPool::createStringBlock() +{ + sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(), + String8()); + status_t err = writeStringBlock(pool); + return err == NO_ERROR ? pool : NULL; +} + +#define ENCODE_LENGTH(str, chrsz, strSize) \ +{ \ + size_t maxMask = 1 << ((chrsz*8)-1); \ + size_t maxSize = maxMask-1; \ + if (strSize > maxSize) { \ + *str++ = maxMask | ((strSize>>(chrsz*8))&maxSize); \ + } \ + *str++ = strSize; \ +} + +status_t StringPool::writeStringBlock(const sp<AaptFile>& pool) +{ + // Allow appending. Sorry this is a little wacky. + if (pool->getSize() > 0) { + sp<AaptFile> block = createStringBlock(); + if (block == NULL) { + return UNKNOWN_ERROR; + } + ssize_t res = pool->writeData(block->getData(), block->getSize()); + return (res >= 0) ? (status_t)NO_ERROR : res; + } + + // First we need to add all style span names to the string pool. + // We do this now (instead of when the span is added) so that these + // will appear at the end of the pool, not disrupting the order + // our client placed their own strings in it. + + const size_t STYLES = mEntryStyleArray.size(); + size_t i; + + for (i=0; i<STYLES; i++) { + entry_style& style = mEntryStyleArray.editItemAt(i); + const size_t N = style.spans.size(); + for (size_t i=0; i<N; i++) { + entry_style_span& span = style.spans.editItemAt(i); + ssize_t idx = add(span.name, true); + if (idx < 0) { + fprintf(stderr, "Error adding span for style tag '%s'\n", + String8(span.name).string()); + return idx; + } + span.span.name.index = (uint32_t)idx; + } + } + + const size_t ENTRIES = mEntryArray.size(); + + // Now build the pool of unique strings. + + const size_t STRINGS = mEntries.size(); + const size_t preSize = sizeof(ResStringPool_header) + + (sizeof(uint32_t)*ENTRIES) + + (sizeof(uint32_t)*STYLES); + if (pool->editData(preSize) == NULL) { + fprintf(stderr, "ERROR: Out of memory for string pool\n"); + return NO_MEMORY; + } + + const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t); + + size_t strPos = 0; + for (i=0; i<STRINGS; i++) { + entry& ent = mEntries.editItemAt(i); + const size_t strSize = (ent.value.size()); + const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ? + charSize*2 : charSize; + + String8 encStr; + if (mUTF8) { + encStr = String8(ent.value); + } + + const size_t encSize = mUTF8 ? encStr.size() : 0; + const size_t encLenSize = mUTF8 ? + (encSize > (size_t)(1<<((charSize*8)-1))-1 ? + charSize*2 : charSize) : 0; + + ent.offset = strPos; + + const size_t totalSize = lenSize + encLenSize + + ((mUTF8 ? encSize : strSize)+1)*charSize; + + void* dat = (void*)pool->editData(preSize + strPos + totalSize); + if (dat == NULL) { + fprintf(stderr, "ERROR: Out of memory for string pool\n"); + return NO_MEMORY; + } + dat = (uint8_t*)dat + preSize + strPos; + if (mUTF8) { + uint8_t* strings = (uint8_t*)dat; + + ENCODE_LENGTH(strings, sizeof(uint8_t), strSize) + + ENCODE_LENGTH(strings, sizeof(uint8_t), encSize) + + strncpy((char*)strings, encStr, encSize+1); + } else { + uint16_t* strings = (uint16_t*)dat; + + ENCODE_LENGTH(strings, sizeof(uint16_t), strSize) + + strcpy16_htod(strings, ent.value); + } + + strPos += totalSize; + } + + // Pad ending string position up to a uint32_t boundary. + + if (strPos&0x3) { + size_t padPos = ((strPos+3)&~0x3); + uint8_t* dat = (uint8_t*)pool->editData(preSize + padPos); + if (dat == NULL) { + fprintf(stderr, "ERROR: Out of memory padding string pool\n"); + return NO_MEMORY; + } + memset(dat+preSize+strPos, 0, padPos-strPos); + strPos = padPos; + } + + // Build the pool of style spans. + + size_t styPos = strPos; + for (i=0; i<STYLES; i++) { + entry_style& ent = mEntryStyleArray.editItemAt(i); + const size_t N = ent.spans.size(); + const size_t totalSize = (N*sizeof(ResStringPool_span)) + + sizeof(ResStringPool_ref); + + ent.offset = styPos-strPos; + uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + totalSize); + if (dat == NULL) { + fprintf(stderr, "ERROR: Out of memory for string styles\n"); + return NO_MEMORY; + } + ResStringPool_span* span = (ResStringPool_span*)(dat+preSize+styPos); + for (size_t i=0; i<N; i++) { + span->name.index = htodl(ent.spans[i].span.name.index); + span->firstChar = htodl(ent.spans[i].span.firstChar); + span->lastChar = htodl(ent.spans[i].span.lastChar); + span++; + } + span->name.index = htodl(ResStringPool_span::END); + + styPos += totalSize; + } + + if (STYLES > 0) { + // Add full terminator at the end (when reading we validate that + // the end of the pool is fully terminated to simplify error + // checking). + size_t extra = sizeof(ResStringPool_span)-sizeof(ResStringPool_ref); + uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + extra); + if (dat == NULL) { + fprintf(stderr, "ERROR: Out of memory for string styles\n"); + return NO_MEMORY; + } + uint32_t* p = (uint32_t*)(dat+preSize+styPos); + while (extra > 0) { + *p++ = htodl(ResStringPool_span::END); + extra -= sizeof(uint32_t); + } + styPos += extra; + } + + // Write header. + + ResStringPool_header* header = + (ResStringPool_header*)pool->padData(sizeof(uint32_t)); + if (header == NULL) { + fprintf(stderr, "ERROR: Out of memory for string pool\n"); + return NO_MEMORY; + } + memset(header, 0, sizeof(*header)); + header->header.type = htods(RES_STRING_POOL_TYPE); + header->header.headerSize = htods(sizeof(*header)); + header->header.size = htodl(pool->getSize()); + header->stringCount = htodl(ENTRIES); + header->styleCount = htodl(STYLES); + if (mUTF8) { + header->flags |= htodl(ResStringPool_header::UTF8_FLAG); + } + header->stringsStart = htodl(preSize); + header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0); + + // Write string index array. + + uint32_t* index = (uint32_t*)(header+1); + for (i=0; i<ENTRIES; i++) { + entry& ent = mEntries.editItemAt(mEntryArray[i]); + *index++ = htodl(ent.offset); + NOISY(printf("Writing entry #%d: \"%s\" ent=%d off=%d\n", i, + String8(ent.value).string(), + mEntryArray[i], ent.offset)); + } + + // Write style index array. + + for (i=0; i<STYLES; i++) { + *index++ = htodl(mEntryStyleArray[i].offset); + } + + return NO_ERROR; +} + +ssize_t StringPool::offsetForString(const String16& val) const +{ + const Vector<size_t>* indices = offsetsForString(val); + ssize_t res = indices != NULL && indices->size() > 0 ? indices->itemAt(0) : -1; + NOISY(printf("Offset for string %s: %d (%s)\n", String8(val).string(), res, + res >= 0 ? String8(mEntries[mEntryArray[res]].value).string() : String8())); + return res; +} + +const Vector<size_t>* StringPool::offsetsForString(const String16& val) const +{ + ssize_t pos = mValues.valueFor(val); + if (pos < 0) { + return NULL; + } + return &mEntries[mEntryArray[pos]].indices; +} diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h new file mode 100644 index 0000000..1b3abfd --- /dev/null +++ b/tools/aapt/StringPool.h @@ -0,0 +1,183 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#ifndef STRING_POOL_H +#define STRING_POOL_H + +#include "Main.h" +#include "AaptAssets.h" + +#include <androidfw/ResourceTypes.h> +#include <utils/String16.h> +#include <utils/TypeHelpers.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> + +#include <libexpat/expat.h> + +using namespace android; + +#define PRINT_STRING_METRICS 0 + +void strcpy16_htod(uint16_t* dst, const uint16_t* src); + +void printStringPool(const ResStringPool* pool); + +/** + * The StringPool class is used as an intermediate representation for + * generating the string pool resource data structure that can be parsed with + * ResStringPool in include/utils/ResourceTypes.h. + */ +class StringPool +{ +public: + struct entry { + entry() : offset(0) { } + entry(const String16& _value) : value(_value), offset(0), hasStyles(false) { } + entry(const entry& o) : value(o.value), offset(o.offset), + hasStyles(o.hasStyles), indices(o.indices), + configTypeName(o.configTypeName), configs(o.configs) { } + + String16 value; + size_t offset; + bool hasStyles; + Vector<size_t> indices; + String8 configTypeName; + Vector<ResTable_config> configs; + + String8 makeConfigsString() const; + + int compare(const entry& o) const; + + inline bool operator<(const entry& o) const { return compare(o) < 0; } + inline bool operator<=(const entry& o) const { return compare(o) <= 0; } + inline bool operator==(const entry& o) const { return compare(o) == 0; } + inline bool operator!=(const entry& o) const { return compare(o) != 0; } + inline bool operator>=(const entry& o) const { return compare(o) >= 0; } + inline bool operator>(const entry& o) const { return compare(o) > 0; } + }; + + struct entry_style_span { + String16 name; + ResStringPool_span span; + }; + + struct entry_style { + entry_style() : offset(0) { } + + entry_style(const entry_style& o) : offset(o.offset), spans(o.spans) { } + + size_t offset; + Vector<entry_style_span> spans; + }; + + /** + * If 'utf8' is true, strings will be encoded with UTF-8 instead of + * left in Java's native UTF-16. + */ + explicit StringPool(bool utf8 = false); + + /** + * Add a new string to the pool. If mergeDuplicates is true, thenif + * the string already exists the existing entry for it will be used; + * otherwise, or if the value doesn't already exist, a new entry is + * created. + * + * Returns the index in the entry array of the new string entry. + */ + ssize_t add(const String16& value, bool mergeDuplicates = false, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); + + ssize_t add(const String16& value, const Vector<entry_style_span>& spans, + const String8* configTypeName = NULL, const ResTable_config* config = NULL); + + status_t addStyleSpan(size_t idx, const String16& name, + uint32_t start, uint32_t end); + status_t addStyleSpans(size_t idx, const Vector<entry_style_span>& spans); + status_t addStyleSpan(size_t idx, const entry_style_span& span); + + // Sort the contents of the string block by the configuration associated + // with each item. After doing this you can use mapOriginalPosToNewPos() + // to find out the new position given the position originally returned by + // add(). + void sortByConfig(); + + // For use after sortByConfig() to map from the original position of + // a string to its new sorted position. + size_t mapOriginalPosToNewPos(size_t originalPos) const { + return mOriginalPosToNewPos.itemAt(originalPos); + } + + sp<AaptFile> createStringBlock(); + + status_t writeStringBlock(const sp<AaptFile>& pool); + + /** + * Find out an offset in the pool for a particular string. If the string + * pool is sorted, this can not be called until after createStringBlock() + * or writeStringBlock() has been called + * (which determines the offsets). In the case of a string that appears + * multiple times in the pool, the first offset will be returned. Returns + * -1 if the string does not exist. + */ + ssize_t offsetForString(const String16& val) const; + + /** + * Find all of the offsets in the pool for a particular string. If the + * string pool is sorted, this can not be called until after + * createStringBlock() or writeStringBlock() has been called + * (which determines the offsets). Returns NULL if the string does not exist. + */ + const Vector<size_t>* offsetsForString(const String16& val) const; + +private: + static int config_sort(void* state, const void* lhs, const void* rhs); + + const bool mUTF8; + + // The following data structures represent the actual structures + // that will be generated for the final string pool. + + // Raw array of unique strings, in some arbitrary order. This is the + // actual strings that appear in the final string pool, in the order + // that they will be written. + Vector<entry> mEntries; + // Array of indices into mEntries, in the order they were + // added to the pool. This can be different than mEntries + // if the same string was added multiple times (it will appear + // once in mEntries, with multiple occurrences in this array). + // This is the lookup array that will be written for finding + // the string for each offset/position in the string pool. + Vector<size_t> mEntryArray; + // Optional style span information associated with each index of + // mEntryArray. + Vector<entry_style> mEntryStyleArray; + + // The following data structures are used for book-keeping as the + // string pool is constructed. + + // Unique set of all the strings added to the pool, mapped to + // the first index of mEntryArray where the value was added. + DefaultKeyedVector<String16, ssize_t> mValues; + // This array maps from the original position a string was placed at + // in mEntryArray to its new position after being sorted with sortByConfig(). + Vector<size_t> mOriginalPosToNewPos; +}; + +// The entry types are trivially movable because all fields they contain, including +// the vectors and strings, are trivially movable. +namespace android { + ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry); + ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry_style_span); + ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry_style); +}; + +#endif + diff --git a/tools/aapt/WorkQueue.cpp b/tools/aapt/WorkQueue.cpp new file mode 100644 index 0000000..24a962f --- /dev/null +++ b/tools/aapt/WorkQueue.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2012 The Android Open Source 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. + */ + +// #define LOG_NDEBUG 0 +#define LOG_TAG "WorkQueue" + +#include <utils/Log.h> +#include "WorkQueue.h" + +namespace android { + +// --- WorkQueue --- + +WorkQueue::WorkQueue(size_t maxThreads, bool canCallJava) : + mMaxThreads(maxThreads), mCanCallJava(canCallJava), + mCanceled(false), mFinished(false), mIdleThreads(0) { +} + +WorkQueue::~WorkQueue() { + if (!cancel()) { + finish(); + } +} + +status_t WorkQueue::schedule(WorkUnit* workUnit, size_t backlog) { + AutoMutex _l(mLock); + + if (mFinished || mCanceled) { + return INVALID_OPERATION; + } + + if (mWorkThreads.size() < mMaxThreads + && mIdleThreads < mWorkUnits.size() + 1) { + sp<WorkThread> workThread = new WorkThread(this, mCanCallJava); + status_t status = workThread->run("WorkQueue::WorkThread"); + if (status) { + return status; + } + mWorkThreads.add(workThread); + mIdleThreads += 1; + } else if (backlog) { + while (mWorkUnits.size() >= mMaxThreads * backlog) { + mWorkDequeuedCondition.wait(mLock); + if (mFinished || mCanceled) { + return INVALID_OPERATION; + } + } + } + + mWorkUnits.add(workUnit); + mWorkChangedCondition.broadcast(); + return OK; +} + +status_t WorkQueue::cancel() { + AutoMutex _l(mLock); + + return cancelLocked(); +} + +status_t WorkQueue::cancelLocked() { + if (mFinished) { + return INVALID_OPERATION; + } + + if (!mCanceled) { + mCanceled = true; + + size_t count = mWorkUnits.size(); + for (size_t i = 0; i < count; i++) { + delete mWorkUnits.itemAt(i); + } + mWorkUnits.clear(); + mWorkChangedCondition.broadcast(); + mWorkDequeuedCondition.broadcast(); + } + return OK; +} + +status_t WorkQueue::finish() { + { // acquire lock + AutoMutex _l(mLock); + + if (mFinished) { + return INVALID_OPERATION; + } + + mFinished = true; + mWorkChangedCondition.broadcast(); + } // release lock + + // It is not possible for the list of work threads to change once the mFinished + // flag has been set, so we can access mWorkThreads outside of the lock here. + size_t count = mWorkThreads.size(); + for (size_t i = 0; i < count; i++) { + mWorkThreads.itemAt(i)->join(); + } + mWorkThreads.clear(); + return OK; +} + +bool WorkQueue::threadLoop() { + WorkUnit* workUnit; + { // acquire lock + AutoMutex _l(mLock); + + for (;;) { + if (mCanceled) { + return false; + } + + if (!mWorkUnits.isEmpty()) { + workUnit = mWorkUnits.itemAt(0); + mWorkUnits.removeAt(0); + mIdleThreads -= 1; + mWorkDequeuedCondition.broadcast(); + break; + } + + if (mFinished) { + return false; + } + + mWorkChangedCondition.wait(mLock); + } + } // release lock + + bool shouldContinue = workUnit->run(); + delete workUnit; + + { // acquire lock + AutoMutex _l(mLock); + + mIdleThreads += 1; + + if (!shouldContinue) { + cancelLocked(); + return false; + } + } // release lock + + return true; +} + +// --- WorkQueue::WorkThread --- + +WorkQueue::WorkThread::WorkThread(WorkQueue* workQueue, bool canCallJava) : + Thread(canCallJava), mWorkQueue(workQueue) { +} + +WorkQueue::WorkThread::~WorkThread() { +} + +bool WorkQueue::WorkThread::threadLoop() { + return mWorkQueue->threadLoop(); +} + +}; // namespace android diff --git a/tools/aapt/WorkQueue.h b/tools/aapt/WorkQueue.h new file mode 100644 index 0000000..d38f05d --- /dev/null +++ b/tools/aapt/WorkQueue.h @@ -0,0 +1,119 @@ +/*] + * Copyright (C) 2012 The Android Open Source 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 AAPT_WORK_QUEUE_H +#define AAPT_WORK_QUEUE_H + +#include <utils/Errors.h> +#include <utils/Vector.h> +#include <utils/threads.h> + +namespace android { + +/* + * A threaded work queue. + * + * This class is designed to make it easy to run a bunch of isolated work + * units in parallel, using up to the specified number of threads. + * To use it, write a loop to post work units to the work queue, then synchronize + * on the queue at the end. + */ +class WorkQueue { +public: + class WorkUnit { + public: + WorkUnit() { } + virtual ~WorkUnit() { } + + /* + * Runs the work unit. + * If the result is 'true' then the work queue continues scheduling work as usual. + * If the result is 'false' then the work queue is canceled. + */ + virtual bool run() = 0; + }; + + /* Creates a work queue with the specified maximum number of work threads. */ + WorkQueue(size_t maxThreads, bool canCallJava = true); + + /* Destroys the work queue. + * Cancels pending work and waits for all remaining threads to complete. + */ + ~WorkQueue(); + + /* Posts a work unit to run later. + * If the work queue has been canceled or is already finished, returns INVALID_OPERATION + * and does not take ownership of the work unit (caller must destroy it itself). + * Otherwise, returns OK and takes ownership of the work unit (the work queue will + * destroy it automatically). + * + * For flow control, this method blocks when the size of the pending work queue is more + * 'backlog' times the number of threads. This condition reduces the rate of entry into + * the pending work queue and prevents it from growing much more rapidly than the + * work threads can actually handle. + * + * If 'backlog' is 0, then no throttle is applied. + */ + status_t schedule(WorkUnit* workUnit, size_t backlog = 2); + + /* Cancels all pending work. + * If the work queue is already finished, returns INVALID_OPERATION. + * If the work queue is already canceled, returns OK and does nothing else. + * Otherwise, returns OK, discards all pending work units and prevents additional + * work units from being scheduled. + * + * Call finish() after cancel() to wait for all remaining work to complete. + */ + status_t cancel(); + + /* Waits for all work to complete. + * If the work queue is already finished, returns INVALID_OPERATION. + * Otherwise, waits for all work to complete and returns OK. + */ + status_t finish(); + +private: + class WorkThread : public Thread { + public: + WorkThread(WorkQueue* workQueue, bool canCallJava); + virtual ~WorkThread(); + + private: + virtual bool threadLoop(); + + WorkQueue* const mWorkQueue; + }; + + status_t cancelLocked(); + bool threadLoop(); // called from each work thread + + const size_t mMaxThreads; + const bool mCanCallJava; + + Mutex mLock; + Condition mWorkChangedCondition; + Condition mWorkDequeuedCondition; + + bool mCanceled; + bool mFinished; + size_t mIdleThreads; + Vector<sp<WorkThread> > mWorkThreads; + Vector<WorkUnit*> mWorkUnits; +}; + +}; // namespace android + +#endif // AAPT_WORK_QUEUE_H diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp new file mode 100644 index 0000000..a663ad5 --- /dev/null +++ b/tools/aapt/XMLNode.cpp @@ -0,0 +1,1510 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "XMLNode.h" +#include "ResourceTable.h" +#include "pseudolocalize.h" + +#include <utils/ByteOrder.h> +#include <errno.h> +#include <string.h> + +#ifndef HAVE_MS_C_RUNTIME +#define O_BINARY 0 +#endif + +#define NOISY(x) //x +#define NOISY_PARSE(x) //x + +const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/"; +const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"; +const char* const RESOURCES_AUTO_PACKAGE_NAMESPACE = "http://schemas.android.com/apk/res-auto"; +const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/"; + +const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2"; +const char* const ALLOWED_XLIFF_ELEMENTS[] = { + "bpt", + "ept", + "it", + "ph", + "g", + "bx", + "ex", + "x" + }; + +bool isWhitespace(const char16_t* str) +{ + while (*str != 0 && *str < 128 && isspace(*str)) { + str++; + } + return *str == 0; +} + +static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE); +static const String16 RESOURCES_PREFIX_AUTO_PACKAGE(RESOURCES_AUTO_PACKAGE_NAMESPACE); +static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE); +static const String16 RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools"); + +String16 getNamespaceResourcePackage(String16 appPackage, String16 namespaceUri, bool* outIsPublic) +{ + //printf("%s starts with %s?\n", String8(namespaceUri).string(), + // String8(RESOURCES_PREFIX).string()); + size_t prefixSize; + bool isPublic = true; + if(namespaceUri.startsWith(RESOURCES_PREFIX_AUTO_PACKAGE)) { + NOISY(printf("Using default application package: %s -> %s\n", String8(namespaceUri).string(), String8(appPackage).string())); + isPublic = true; + return appPackage; + } else if (namespaceUri.startsWith(RESOURCES_PREFIX)) { + prefixSize = RESOURCES_PREFIX.size(); + } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) { + isPublic = false; + prefixSize = RESOURCES_PRV_PREFIX.size(); + } else { + if (outIsPublic) *outIsPublic = isPublic; // = true + return String16(); + } + + //printf("YES!\n"); + //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string()); + if (outIsPublic) *outIsPublic = isPublic; + return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize); +} + +status_t hasSubstitutionErrors(const char* fileName, + ResXMLTree* inXml, + String16 str16) +{ + const char16_t* str = str16.string(); + const char16_t* p = str; + const char16_t* end = str + str16.size(); + + bool nonpositional = false; + int argCount = 0; + + while (p < end) { + /* + * Look for the start of a Java-style substitution sequence. + */ + if (*p == '%' && p + 1 < end) { + p++; + + // A literal percent sign represented by %% + if (*p == '%') { + p++; + continue; + } + + argCount++; + + if (*p >= '0' && *p <= '9') { + do { + p++; + } while (*p >= '0' && *p <= '9'); + if (*p != '$') { + // This must be a size specification instead of position. + nonpositional = true; + } + } else if (*p == '<') { + // Reusing last argument; bad idea since it can be re-arranged. + nonpositional = true; + p++; + + // Optionally '$' can be specified at the end. + if (p < end && *p == '$') { + p++; + } + } else { + nonpositional = true; + } + + // Ignore flags and widths + while (p < end && (*p == '-' || + *p == '#' || + *p == '+' || + *p == ' ' || + *p == ',' || + *p == '(' || + (*p >= '0' && *p <= '9'))) { + p++; + } + + /* + * This is a shortcut to detect strings that are going to Time.format() + * instead of String.format() + * + * Comparison of String.format() and Time.format() args: + * + * String: ABC E GH ST X abcdefgh nost x + * Time: DEFGHKMS W Za d hkm s w yz + * + * Therefore we know it's definitely Time if we have: + * DFKMWZkmwyz + */ + if (p < end) { + switch (*p) { + case 'D': + case 'F': + case 'K': + case 'M': + case 'W': + case 'Z': + case 'k': + case 'm': + case 'w': + case 'y': + case 'z': + return NO_ERROR; + } + } + } + + p++; + } + + /* + * If we have more than one substitution in this string and any of them + * are not in positional form, give the user an error. + */ + if (argCount > 1 && nonpositional) { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "Multiple substitutions specified in non-positional format; " + "did you mean to add the formatted=\"false\" attribute?\n"); + return NOT_ENOUGH_DATA; + } + + return NO_ERROR; +} + +status_t parseStyledString(Bundle* bundle, + const char* fileName, + ResXMLTree* inXml, + const String16& endTag, + String16* outString, + Vector<StringPool::entry_style_span>* outSpans, + bool isFormatted, + bool pseudolocalize) +{ + Vector<StringPool::entry_style_span> spanStack; + String16 curString; + String16 rawString; + const char* errorMsg; + int xliffDepth = 0; + bool firstTime = true; + + size_t len; + ResXMLTree::event_code_t code; + while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + + if (code == ResXMLTree::TEXT) { + String16 text(inXml->getText(&len)); + if (firstTime && text.size() > 0) { + firstTime = false; + if (text.string()[0] == '@') { + // If this is a resource reference, don't do the pseudoloc. + pseudolocalize = false; + } + } + if (xliffDepth == 0 && pseudolocalize) { + std::string orig(String8(text).string()); + std::string pseudo = pseudolocalize_string(orig); + curString.append(String16(String8(pseudo.c_str()))); + } else { + if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) { + return UNKNOWN_ERROR; + } else { + curString.append(text); + } + } + } else if (code == ResXMLTree::START_TAG) { + const String16 element16(inXml->getElementName(&len)); + const String8 element8(element16); + + size_t nslen; + const uint16_t* ns = inXml->getElementNamespace(&nslen); + if (ns == NULL) { + ns = (const uint16_t*)"\0\0"; + nslen = 0; + } + const String8 nspace(String16(ns, nslen)); + if (nspace == XLIFF_XMLNS) { + const int N = sizeof(ALLOWED_XLIFF_ELEMENTS)/sizeof(ALLOWED_XLIFF_ELEMENTS[0]); + for (int i=0; i<N; i++) { + if (element8 == ALLOWED_XLIFF_ELEMENTS[i]) { + xliffDepth++; + // in this case, treat it like it was just text, in other words, do nothing + // here and silently drop this element + goto moveon; + } + } + { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "Found unsupported XLIFF tag <%s>\n", + element8.string()); + return UNKNOWN_ERROR; + } +moveon: + continue; + } + + if (outSpans == NULL) { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "Found style tag <%s> where styles are not allowed\n", element8.string()); + return UNKNOWN_ERROR; + } + + if (!ResTable::collectString(outString, curString.string(), + curString.size(), false, &errorMsg, true)) { + SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n", + errorMsg, String8(curString).string()); + return UNKNOWN_ERROR; + } + rawString.append(curString); + curString = String16(); + + StringPool::entry_style_span span; + span.name = element16; + for (size_t ai=0; ai<inXml->getAttributeCount(); ai++) { + span.name.append(String16(";")); + const char16_t* str = inXml->getAttributeName(ai, &len); + span.name.append(str, len); + span.name.append(String16("=")); + str = inXml->getAttributeStringValue(ai, &len); + span.name.append(str, len); + } + //printf("Span: %s\n", String8(span.name).string()); + span.span.firstChar = span.span.lastChar = outString->size(); + spanStack.push(span); + + } else if (code == ResXMLTree::END_TAG) { + size_t nslen; + const uint16_t* ns = inXml->getElementNamespace(&nslen); + if (ns == NULL) { + ns = (const uint16_t*)"\0\0"; + nslen = 0; + } + const String8 nspace(String16(ns, nslen)); + if (nspace == XLIFF_XMLNS) { + xliffDepth--; + continue; + } + if (!ResTable::collectString(outString, curString.string(), + curString.size(), false, &errorMsg, true)) { + SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n", + errorMsg, String8(curString).string()); + return UNKNOWN_ERROR; + } + rawString.append(curString); + curString = String16(); + + if (spanStack.size() == 0) { + if (strcmp16(inXml->getElementName(&len), endTag.string()) != 0) { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "Found tag %s where <%s> close is expected\n", + String8(inXml->getElementName(&len)).string(), + String8(endTag).string()); + return UNKNOWN_ERROR; + } + break; + } + StringPool::entry_style_span span = spanStack.top(); + String16 spanTag; + ssize_t semi = span.name.findFirst(';'); + if (semi >= 0) { + spanTag.setTo(span.name.string(), semi); + } else { + spanTag.setTo(span.name); + } + if (strcmp16(inXml->getElementName(&len), spanTag.string()) != 0) { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "Found close tag %s where close tag %s is expected\n", + String8(inXml->getElementName(&len)).string(), + String8(spanTag).string()); + return UNKNOWN_ERROR; + } + bool empty = true; + if (outString->size() > 0) { + span.span.lastChar = outString->size()-1; + if (span.span.lastChar >= span.span.firstChar) { + empty = false; + outSpans->add(span); + } + } + spanStack.pop(); + + /* + * This warning seems to be just an irritation to most people, + * since it is typically introduced by translators who then never + * see the warning. + */ + if (0 && empty) { + fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n", + fileName, inXml->getLineNumber(), + String8(spanTag).string(), String8(*outString).string()); + + } + } else if (code == ResXMLTree::START_NAMESPACE) { + // nothing + } + } + + if (code == ResXMLTree::BAD_DOCUMENT) { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "Error parsing XML\n"); + } + + if (outSpans != NULL && outSpans->size() > 0) { + if (curString.size() > 0) { + if (!ResTable::collectString(outString, curString.string(), + curString.size(), false, &errorMsg, true)) { + SourcePos(String8(fileName), inXml->getLineNumber()).error( + "%s (in %s)\n", + errorMsg, String8(curString).string()); + return UNKNOWN_ERROR; + } + } + } else { + // There is no style information, so string processing will happen + // later as part of the overall type conversion. Return to the + // client the raw unprocessed text. + rawString.append(curString); + outString->setTo(rawString); + } + + return NO_ERROR; +} + +struct namespace_entry { + String8 prefix; + String8 uri; +}; + +static String8 make_prefix(int depth) +{ + String8 prefix; + int i; + for (i=0; i<depth; i++) { + prefix.append(" "); + } + return prefix; +} + +static String8 build_namespace(const Vector<namespace_entry>& namespaces, + const uint16_t* ns) +{ + String8 str; + if (ns != NULL) { + str = String8(ns); + const size_t N = namespaces.size(); + for (size_t i=0; i<N; i++) { + const namespace_entry& ne = namespaces.itemAt(i); + if (ne.uri == str) { + str = ne.prefix; + break; + } + } + str.append(":"); + } + return str; +} + +void printXMLBlock(ResXMLTree* block) +{ + block->restart(); + + Vector<namespace_entry> namespaces; + + ResXMLTree::event_code_t code; + int depth = 0; + while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + String8 prefix = make_prefix(depth); + int i; + if (code == ResXMLTree::START_TAG) { + size_t len; + const uint16_t* ns16 = block->getElementNamespace(&len); + String8 elemNs = build_namespace(namespaces, ns16); + const uint16_t* com16 = block->getComment(&len); + if (com16) { + printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string()); + } + printf("%sE: %s%s (line=%d)\n", prefix.string(), elemNs.string(), + String8(block->getElementName(&len)).string(), + block->getLineNumber()); + int N = block->getAttributeCount(); + depth++; + prefix = make_prefix(depth); + for (i=0; i<N; i++) { + uint32_t res = block->getAttributeNameResID(i); + ns16 = block->getAttributeNamespace(i, &len); + String8 ns = build_namespace(namespaces, ns16); + String8 name(block->getAttributeName(i, &len)); + printf("%sA: ", prefix.string()); + if (res) { + printf("%s%s(0x%08x)", ns.string(), name.string(), res); + } else { + printf("%s%s", ns.string(), name.string()); + } + Res_value value; + block->getAttributeValue(i, &value); + if (value.dataType == Res_value::TYPE_NULL) { + printf("=(null)"); + } else if (value.dataType == Res_value::TYPE_REFERENCE) { + printf("=@0x%x", (int)value.data); + } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) { + printf("=?0x%x", (int)value.data); + } else if (value.dataType == Res_value::TYPE_STRING) { + printf("=\"%s\"", + ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i, + &len)).string()).string()); + } else { + printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data); + } + const char16_t* val = block->getAttributeStringValue(i, &len); + if (val != NULL) { + printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()). + string()); + } + printf("\n"); + } + } else if (code == ResXMLTree::END_TAG) { + depth--; + } else if (code == ResXMLTree::START_NAMESPACE) { + namespace_entry ns; + size_t len; + const uint16_t* prefix16 = block->getNamespacePrefix(&len); + if (prefix16) { + ns.prefix = String8(prefix16); + } else { + ns.prefix = "<DEF>"; + } + ns.uri = String8(block->getNamespaceUri(&len)); + namespaces.push(ns); + printf("%sN: %s=%s\n", prefix.string(), ns.prefix.string(), + ns.uri.string()); + depth++; + } else if (code == ResXMLTree::END_NAMESPACE) { + depth--; + const namespace_entry& ns = namespaces.top(); + size_t len; + const uint16_t* prefix16 = block->getNamespacePrefix(&len); + String8 pr; + if (prefix16) { + pr = String8(prefix16); + } else { + pr = "<DEF>"; + } + if (ns.prefix != pr) { + prefix = make_prefix(depth); + printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n", + prefix.string(), pr.string(), ns.prefix.string()); + } + String8 uri = String8(block->getNamespaceUri(&len)); + if (ns.uri != uri) { + prefix = make_prefix(depth); + printf("%s *** BAD END NS URI: found=%s, expected=%s\n", + prefix.string(), uri.string(), ns.uri.string()); + } + namespaces.pop(); + } else if (code == ResXMLTree::TEXT) { + size_t len; + printf("%sC: \"%s\"\n", prefix.string(), + ResTable::normalizeForOutput(String8(block->getText(&len)).string()).string()); + } + } + + block->restart(); +} + +status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree, + bool stripAll, bool keepComments, + const char** cDataTags) +{ + sp<XMLNode> root = XMLNode::parse(file); + if (root == NULL) { + return UNKNOWN_ERROR; + } + root->removeWhitespace(stripAll, cDataTags); + + NOISY(printf("Input XML from %s:\n", (const char*)file->getPrintableSource())); + NOISY(root->print()); + sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8()); + status_t err = root->flatten(rsc, !keepComments, false); + if (err != NO_ERROR) { + return err; + } + err = outTree->setTo(rsc->getData(), rsc->getSize(), true); + if (err != NO_ERROR) { + return err; + } + + NOISY(printf("Output XML:\n")); + NOISY(printXMLBlock(outTree)); + + return NO_ERROR; +} + +sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file) +{ + char buf[16384]; + 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", + strerror(errno)); + return NULL; + } + + 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); + + ssize_t len; + bool done; + do { + len = read(fd, buf, sizeof(buf)); + done = len < (ssize_t)sizeof(buf); + if (len < 0) { + SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno)); + close(fd); + return NULL; + } + if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) { + SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error( + "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser))); + close(fd); + return NULL; + } + } while (!done); + + XML_ParserFree(parser); + if (state.root == NULL) { + SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing"); + } + close(fd); + return state.root; +} + +XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace) + : mNextAttributeIndex(0x80000000) + , mFilename(filename) + , mStartLineNumber(0) + , mEndLineNumber(0) + , mUTF8(false) +{ + if (isNamespace) { + mNamespacePrefix = s1; + mNamespaceUri = s2; + } else { + mNamespaceUri = s1; + mElementName = s2; + } +} + +XMLNode::XMLNode(const String8& filename) + : mFilename(filename) +{ + memset(&mCharsValue, 0, sizeof(mCharsValue)); +} + +XMLNode::type XMLNode::getType() const +{ + if (mElementName.size() != 0) { + return TYPE_ELEMENT; + } + if (mNamespaceUri.size() != 0) { + return TYPE_NAMESPACE; + } + return TYPE_CDATA; +} + +const String16& XMLNode::getNamespacePrefix() const +{ + return mNamespacePrefix; +} + +const String16& XMLNode::getNamespaceUri() const +{ + return mNamespaceUri; +} + +const String16& XMLNode::getElementNamespace() const +{ + return mNamespaceUri; +} + +const String16& XMLNode::getElementName() const +{ + return mElementName; +} + +const Vector<sp<XMLNode> >& XMLNode::getChildren() const +{ + return mChildren; +} + +const String8& XMLNode::getFilename() const +{ + return mFilename; +} + +const Vector<XMLNode::attribute_entry>& + XMLNode::getAttributes() const +{ + return mAttributes; +} + +const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns, + const String16& name) const +{ + for (size_t i=0; i<mAttributes.size(); i++) { + const attribute_entry& ae(mAttributes.itemAt(i)); + if (ae.ns == ns && ae.name == name) { + return &ae; + } + } + + return NULL; +} + +XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns, + const String16& name) +{ + for (size_t i=0; i<mAttributes.size(); i++) { + attribute_entry * ae = &mAttributes.editItemAt(i); + if (ae->ns == ns && ae->name == name) { + return ae; + } + } + + return NULL; +} + +const String16& XMLNode::getCData() const +{ + return mChars; +} + +const String16& XMLNode::getComment() const +{ + return mComment; +} + +int32_t XMLNode::getStartLineNumber() const +{ + return mStartLineNumber; +} + +int32_t XMLNode::getEndLineNumber() const +{ + return mEndLineNumber; +} + +sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16& tagName) +{ + if (getType() == XMLNode::TYPE_ELEMENT + && mNamespaceUri == tagNamespace + && mElementName == tagName) { + return this; + } + + for (size_t i=0; i<mChildren.size(); i++) { + sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName); + if (found != NULL) { + return found; + } + } + + return NULL; +} + +sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName) +{ + for (size_t i=0; i<mChildren.size(); i++) { + sp<XMLNode> child = mChildren.itemAt(i); + if (child->getType() == XMLNode::TYPE_ELEMENT + && child->mNamespaceUri == tagNamespace + && child->mElementName == tagName) { + return child; + } + } + + return NULL; +} + +status_t XMLNode::addChild(const sp<XMLNode>& child) +{ + if (getType() == TYPE_CDATA) { + SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node."); + return UNKNOWN_ERROR; + } + //printf("Adding child %p to parent %p\n", child.get(), this); + mChildren.add(child); + return NO_ERROR; +} + +status_t XMLNode::insertChildAt(const sp<XMLNode>& child, size_t index) +{ + if (getType() == TYPE_CDATA) { + SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node."); + return UNKNOWN_ERROR; + } + //printf("Adding child %p to parent %p\n", child.get(), this); + mChildren.insertAt(child, index); + return NO_ERROR; +} + +status_t XMLNode::addAttribute(const String16& ns, const String16& name, + const String16& value) +{ + if (getType() == TYPE_CDATA) { + SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node."); + return UNKNOWN_ERROR; + } + + if (ns != RESOURCES_TOOLS_NAMESPACE) { + attribute_entry e; + e.index = mNextAttributeIndex++; + e.ns = ns; + e.name = name; + e.string = value; + mAttributes.add(e); + mAttributeOrder.add(e.index, mAttributes.size()-1); + } + return NO_ERROR; +} + +void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId) +{ + attribute_entry& e = mAttributes.editItemAt(attrIdx); + if (e.nameResId) { + mAttributeOrder.removeItem(e.nameResId); + } else { + mAttributeOrder.removeItem(e.index); + } + NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n", + String8(getElementName()).string(), + String8(mAttributes.itemAt(attrIdx).name).string(), + String8(mAttributes.itemAt(attrIdx).string).string(), + resId)); + mAttributes.editItemAt(attrIdx).nameResId = resId; + mAttributeOrder.add(resId, attrIdx); +} + +status_t XMLNode::appendChars(const String16& chars) +{ + if (getType() != TYPE_CDATA) { + SourcePos(mFilename, getStartLineNumber()).error("Adding characters to element node."); + return UNKNOWN_ERROR; + } + mChars.append(chars); + return NO_ERROR; +} + +status_t XMLNode::appendComment(const String16& comment) +{ + if (mComment.size() > 0) { + mComment.append(String16("\n")); + } + mComment.append(comment); + return NO_ERROR; +} + +void XMLNode::setStartLineNumber(int32_t line) +{ + mStartLineNumber = line; +} + +void XMLNode::setEndLineNumber(int32_t line) +{ + mEndLineNumber = line; +} + +void XMLNode::removeWhitespace(bool stripAll, const char** cDataTags) +{ + //printf("Removing whitespace in %s\n", String8(mElementName).string()); + size_t N = mChildren.size(); + if (cDataTags) { + String8 tag(mElementName); + const char** p = cDataTags; + while (*p) { + if (tag == *p) { + stripAll = false; + break; + } + } + } + for (size_t i=0; i<N; i++) { + sp<XMLNode> node = mChildren.itemAt(i); + if (node->getType() == TYPE_CDATA) { + // This is a CDATA node... + const char16_t* p = node->mChars.string(); + while (*p != 0 && *p < 128 && isspace(*p)) { + p++; + } + //printf("Space ends at %d in \"%s\"\n", + // (int)(p-node->mChars.string()), + // String8(node->mChars).string()); + if (*p == 0) { + if (stripAll) { + // Remove this node! + mChildren.removeAt(i); + N--; + i--; + } else { + node->mChars = String16(" "); + } + } else { + // Compact leading/trailing whitespace. + const char16_t* e = node->mChars.string()+node->mChars.size()-1; + while (e > p && *e < 128 && isspace(*e)) { + e--; + } + if (p > node->mChars.string()) { + p--; + } + if (e < (node->mChars.string()+node->mChars.size()-1)) { + e++; + } + if (p > node->mChars.string() || + e < (node->mChars.string()+node->mChars.size()-1)) { + String16 tmp(p, e-p+1); + node->mChars = tmp; + } + } + } else { + node->removeWhitespace(stripAll, cDataTags); + } + } +} + +status_t XMLNode::parseValues(const sp<AaptAssets>& assets, + ResourceTable* table) +{ + bool hasErrors = false; + + if (getType() == TYPE_ELEMENT) { + const size_t N = mAttributes.size(); + String16 defPackage(assets->getPackage()); + for (size_t i=0; i<N; i++) { + attribute_entry& e = mAttributes.editItemAt(i); + AccessorCookie ac(SourcePos(mFilename, getStartLineNumber()), String8(e.name), + String8(e.string)); + table->setCurrentXmlPos(SourcePos(mFilename, getStartLineNumber())); + if (!assets->getIncludedResources() + .stringToValue(&e.value, &e.string, + e.string.string(), e.string.size(), true, true, + e.nameResId, NULL, &defPackage, table, &ac)) { + hasErrors = true; + } + NOISY(printf("Attr %s: type=0x%x, str=%s\n", + String8(e.name).string(), e.value.dataType, + String8(e.string).string())); + } + } + const size_t N = mChildren.size(); + for (size_t i=0; i<N; i++) { + status_t err = mChildren.itemAt(i)->parseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } + } + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets, + const ResourceTable* table) +{ + bool hasErrors = false; + + if (getType() == TYPE_ELEMENT) { + String16 attr("attr"); + const char* errorMsg; + const size_t N = mAttributes.size(); + for (size_t i=0; i<N; i++) { + const attribute_entry& e = mAttributes.itemAt(i); + if (e.ns.size() <= 0) continue; + bool nsIsPublic; + String16 pkg(getNamespaceResourcePackage(String16(assets->getPackage()), e.ns, &nsIsPublic)); + NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n", + String8(getElementName()).string(), + String8(e.name).string(), + String8(e.string).string(), + String8(e.ns).string(), + (nsIsPublic) ? "public" : "private", + String8(pkg).string())); + if (pkg.size() <= 0) continue; + uint32_t res = table != NULL + ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic) + : assets->getIncludedResources(). + identifierForName(e.name.string(), e.name.size(), + attr.string(), attr.size(), + pkg.string(), pkg.size()); + if (res != 0) { + NOISY(printf("XML attribute name %s: resid=0x%08x\n", + String8(e.name).string(), res)); + setAttributeResID(i, res); + } else { + SourcePos(mFilename, getStartLineNumber()).error( + "No resource identifier found for attribute '%s' in package '%s'\n", + String8(e.name).string(), String8(pkg).string()); + hasErrors = true; + } + } + } + const size_t N = mChildren.size(); + for (size_t i=0; i<N; i++) { + status_t err = mChildren.itemAt(i)->assignResourceIds(assets, table); + if (err < NO_ERROR) { + hasErrors = true; + } + } + + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +status_t XMLNode::flatten(const sp<AaptFile>& dest, + bool stripComments, bool stripRawValues) const +{ + StringPool strings(mUTF8); + Vector<uint32_t> resids; + + // First collect just the strings for attribute names that have a + // resource ID assigned to them. This ensures that the resource ID + // array is compact, and makes it easier to deal with attribute names + // in different namespaces (and thus with different resource IDs). + collect_resid_strings(&strings, &resids); + + // Next collect all remainibng strings. + collect_strings(&strings, &resids, stripComments, stripRawValues); + +#if 0 // No longer compiles + NOISY(printf("Found strings:\n"); + const size_t N = strings.size(); + for (size_t i=0; i<N; i++) { + printf("%s\n", String8(strings.entryAt(i).string).string()); + } + ); +#endif + + sp<AaptFile> stringPool = strings.createStringBlock(); + NOISY(aout << "String pool:" + << HexDump(stringPool->getData(), stringPool->getSize()) << endl); + + ResXMLTree_header header; + memset(&header, 0, sizeof(header)); + header.header.type = htods(RES_XML_TYPE); + header.header.headerSize = htods(sizeof(header)); + + const size_t basePos = dest->getSize(); + dest->writeData(&header, sizeof(header)); + dest->writeData(stringPool->getData(), stringPool->getSize()); + + // If we have resource IDs, write them. + if (resids.size() > 0) { + const size_t resIdsPos = dest->getSize(); + const size_t resIdsSize = + sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size()); + ResChunk_header* idsHeader = (ResChunk_header*) + (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos); + idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE); + idsHeader->headerSize = htods(sizeof(*idsHeader)); + idsHeader->size = htodl(resIdsSize); + uint32_t* ids = (uint32_t*)(idsHeader+1); + for (size_t i=0; i<resids.size(); i++) { + *ids++ = htodl(resids[i]); + } + } + + flatten_node(strings, dest, stripComments, stripRawValues); + + void* data = dest->editData(); + ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos); + size_t size = dest->getSize()-basePos; + hd->header.size = htodl(dest->getSize()-basePos); + + NOISY(aout << "XML resource:" + << HexDump(dest->getData(), dest->getSize()) << endl); + + #if PRINT_STRING_METRICS + fprintf(stderr, "**** total xml size: %d / %d%% strings (in %s)\n", + dest->getSize(), (stringPool->getSize()*100)/dest->getSize(), + dest->getPath().string()); + #endif + + return NO_ERROR; +} + +void XMLNode::print(int indent) +{ + String8 prefix; + int i; + for (i=0; i<indent; i++) { + prefix.append(" "); + } + if (getType() == TYPE_ELEMENT) { + String8 elemNs(getNamespaceUri()); + if (elemNs.size() > 0) { + elemNs.append(":"); + } + printf("%s E: %s%s", prefix.string(), + elemNs.string(), String8(getElementName()).string()); + int N = mAttributes.size(); + for (i=0; i<N; i++) { + ssize_t idx = mAttributeOrder.valueAt(i); + if (i == 0) { + printf(" / "); + } else { + printf(", "); + } + const attribute_entry& attr = mAttributes.itemAt(idx); + String8 attrNs(attr.ns); + if (attrNs.size() > 0) { + attrNs.append(":"); + } + if (attr.nameResId) { + printf("%s%s(0x%08x)", attrNs.string(), + String8(attr.name).string(), attr.nameResId); + } else { + printf("%s%s", attrNs.string(), String8(attr.name).string()); + } + printf("=%s", String8(attr.string).string()); + } + printf("\n"); + } else if (getType() == TYPE_NAMESPACE) { + printf("%s N: %s=%s\n", prefix.string(), + getNamespacePrefix().size() > 0 + ? String8(getNamespacePrefix()).string() : "<DEF>", + String8(getNamespaceUri()).string()); + } else { + printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string()); + } + int N = mChildren.size(); + for (i=0; i<N; i++) { + mChildren.itemAt(i)->print(indent+1); + } +} + +static void splitName(const char* name, String16* outNs, String16* outName) +{ + const char* p = name; + while (*p != 0 && *p != 1) { + p++; + } + if (*p == 0) { + *outNs = String16(); + *outName = String16(name); + } else { + *outNs = String16(name, (p-name)); + *outName = String16(p+1); + } +} + +void XMLCALL +XMLNode::startNamespace(void *userData, const char *prefix, const char *uri) +{ + NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix, uri)); + ParseState* st = (ParseState*)userData; + sp<XMLNode> node = XMLNode::newNamespace(st->filename, + String16(prefix != NULL ? prefix : ""), String16(uri)); + node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser)); + if (st->stack.size() > 0) { + st->stack.itemAt(st->stack.size()-1)->addChild(node); + } else { + st->root = node; + } + st->stack.push(node); +} + +void XMLCALL +XMLNode::startElement(void *userData, const char *name, const char **atts) +{ + NOISY_PARSE(printf("Start Element: %s\n", name)); + ParseState* st = (ParseState*)userData; + String16 ns16, name16; + splitName(name, &ns16, &name16); + sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16); + node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser)); + if (st->pendingComment.size() > 0) { + node->appendComment(st->pendingComment); + st->pendingComment = String16(); + } + if (st->stack.size() > 0) { + st->stack.itemAt(st->stack.size()-1)->addChild(node); + } else { + st->root = node; + } + st->stack.push(node); + + for (int i = 0; atts[i]; i += 2) { + splitName(atts[i], &ns16, &name16); + node->addAttribute(ns16, name16, String16(atts[i+1])); + } +} + +void XMLCALL +XMLNode::characterData(void *userData, const XML_Char *s, int len) +{ + NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s, len).string())); + ParseState* st = (ParseState*)userData; + sp<XMLNode> node = NULL; + if (st->stack.size() == 0) { + return; + } + sp<XMLNode> parent = st->stack.itemAt(st->stack.size()-1); + if (parent != NULL && parent->getChildren().size() > 0) { + node = parent->getChildren()[parent->getChildren().size()-1]; + if (node->getType() != TYPE_CDATA) { + // Last node is not CDATA, need to make a new node. + node = NULL; + } + } + + if (node == NULL) { + node = XMLNode::newCData(st->filename); + node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser)); + parent->addChild(node); + } + + node->appendChars(String16(s, len)); +} + +void XMLCALL +XMLNode::endElement(void *userData, const char *name) +{ + NOISY_PARSE(printf("End Element: %s\n", name)); + ParseState* st = (ParseState*)userData; + sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1); + node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser)); + if (st->pendingComment.size() > 0) { + node->appendComment(st->pendingComment); + st->pendingComment = String16(); + } + String16 ns16, name16; + splitName(name, &ns16, &name16); + LOG_ALWAYS_FATAL_IF(node->getElementNamespace() != ns16 + || node->getElementName() != name16, + "Bad end element %s", name); + st->stack.pop(); +} + +void XMLCALL +XMLNode::endNamespace(void *userData, const char *prefix) +{ + const char* nonNullPrefix = prefix != NULL ? prefix : ""; + NOISY_PARSE(printf("End Namespace: %s\n", prefix)); + ParseState* st = (ParseState*)userData; + sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1); + node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser)); + LOG_ALWAYS_FATAL_IF(node->getNamespacePrefix() != String16(nonNullPrefix), + "Bad end namespace %s", prefix); + st->stack.pop(); +} + +void XMLCALL +XMLNode::commentData(void *userData, const char *comment) +{ + NOISY_PARSE(printf("Comment: %s\n", comment)); + ParseState* st = (ParseState*)userData; + if (st->pendingComment.size() > 0) { + st->pendingComment.append(String16("\n")); + } + st->pendingComment.append(String16(comment)); +} + +status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds, + bool stripComments, bool stripRawValues) const +{ + collect_attr_strings(dest, outResIds, true); + + int i; + if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) { + if (mNamespacePrefix.size() > 0) { + dest->add(mNamespacePrefix, true); + } + if (mNamespaceUri.size() > 0) { + dest->add(mNamespaceUri, true); + } + } + if (mElementName.size() > 0) { + dest->add(mElementName, true); + } + + if (!stripComments && mComment.size() > 0) { + dest->add(mComment, true); + } + + const int NA = mAttributes.size(); + + for (i=0; i<NA; i++) { + const attribute_entry& ae = mAttributes.itemAt(i); + if (ae.ns.size() > 0) { + dest->add(ae.ns, true); + } + if (!stripRawValues || ae.needStringValue()) { + dest->add(ae.string, true); + } + /* + if (ae.value.dataType == Res_value::TYPE_NULL + || ae.value.dataType == Res_value::TYPE_STRING) { + dest->add(ae.string, true); + } + */ + } + + if (mElementName.size() == 0) { + // If not an element, include the CDATA, even if it is empty. + dest->add(mChars, true); + } + + const int NC = mChildren.size(); + + for (i=0; i<NC; i++) { + mChildren.itemAt(i)->collect_strings(dest, outResIds, + stripComments, stripRawValues); + } + + return NO_ERROR; +} + +status_t XMLNode::collect_attr_strings(StringPool* outPool, + Vector<uint32_t>* outResIds, bool allAttrs) const { + const int NA = mAttributes.size(); + + for (int i=0; i<NA; i++) { + const attribute_entry& attr = mAttributes.itemAt(i); + uint32_t id = attr.nameResId; + if (id || allAttrs) { + // See if we have already assigned this resource ID to a pooled + // string... + const Vector<size_t>* indices = outPool->offsetsForString(attr.name); + ssize_t idx = -1; + if (indices != NULL) { + const int NJ = indices->size(); + const size_t NR = outResIds->size(); + for (int j=0; j<NJ; j++) { + size_t strIdx = indices->itemAt(j); + if (strIdx >= NR) { + if (id == 0) { + // We don't need to assign a resource ID for this one. + idx = strIdx; + break; + } + // Just ignore strings that are out of range of + // the currently assigned resource IDs... we add + // strings as we assign the first ID. + } else if (outResIds->itemAt(strIdx) == id) { + idx = strIdx; + break; + } + } + } + if (idx < 0) { + idx = outPool->add(attr.name); + NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n", + String8(attr.name).string(), id, idx)); + if (id != 0) { + while ((ssize_t)outResIds->size() <= idx) { + outResIds->add(0); + } + outResIds->replaceAt(id, idx); + } + } + attr.namePoolIdx = idx; + NOISY(printf("String %s offset=0x%08x\n", + String8(attr.name).string(), idx)); + } + } + + return NO_ERROR; +} + +status_t XMLNode::collect_resid_strings(StringPool* outPool, + Vector<uint32_t>* outResIds) const +{ + collect_attr_strings(outPool, outResIds, false); + + const int NC = mChildren.size(); + + for (int i=0; i<NC; i++) { + mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds); + } + + return NO_ERROR; +} + +status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest, + bool stripComments, bool stripRawValues) const +{ + ResXMLTree_node node; + ResXMLTree_cdataExt cdataExt; + ResXMLTree_namespaceExt namespaceExt; + ResXMLTree_attrExt attrExt; + const void* extData = NULL; + size_t extSize = 0; + ResXMLTree_attribute attr; + bool writeCurrentNode = true; + + const size_t NA = mAttributes.size(); + const size_t NC = mChildren.size(); + size_t i; + + LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!"); + + const String16 id16("id"); + const String16 class16("class"); + const String16 style16("style"); + + const type type = getType(); + + memset(&node, 0, sizeof(node)); + memset(&attr, 0, sizeof(attr)); + node.header.headerSize = htods(sizeof(node)); + node.lineNumber = htodl(getStartLineNumber()); + if (!stripComments) { + node.comment.index = htodl( + mComment.size() > 0 ? strings.offsetForString(mComment) : -1); + //if (mComment.size() > 0) { + // printf("Flattening comment: %s\n", String8(mComment).string()); + //} + } else { + node.comment.index = htodl((uint32_t)-1); + } + if (type == TYPE_ELEMENT) { + node.header.type = htods(RES_XML_START_ELEMENT_TYPE); + extData = &attrExt; + extSize = sizeof(attrExt); + memset(&attrExt, 0, sizeof(attrExt)); + if (mNamespaceUri.size() > 0) { + attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri)); + } else { + attrExt.ns.index = htodl((uint32_t)-1); + } + attrExt.name.index = htodl(strings.offsetForString(mElementName)); + attrExt.attributeStart = htods(sizeof(attrExt)); + attrExt.attributeSize = htods(sizeof(attr)); + attrExt.attributeCount = htods(NA); + attrExt.idIndex = htods(0); + attrExt.classIndex = htods(0); + attrExt.styleIndex = htods(0); + for (i=0; i<NA; i++) { + ssize_t idx = mAttributeOrder.valueAt(i); + const attribute_entry& ae = mAttributes.itemAt(idx); + if (ae.ns.size() == 0) { + if (ae.name == id16) { + attrExt.idIndex = htods(i+1); + } else if (ae.name == class16) { + attrExt.classIndex = htods(i+1); + } else if (ae.name == style16) { + attrExt.styleIndex = htods(i+1); + } + } + } + } else if (type == TYPE_NAMESPACE) { + if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) { + writeCurrentNode = false; + } else { + node.header.type = htods(RES_XML_START_NAMESPACE_TYPE); + extData = &namespaceExt; + extSize = sizeof(namespaceExt); + memset(&namespaceExt, 0, sizeof(namespaceExt)); + if (mNamespacePrefix.size() > 0) { + namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); + } else { + namespaceExt.prefix.index = htodl((uint32_t)-1); + } + namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix)); + namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri)); + } + LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!"); + } else if (type == TYPE_CDATA) { + node.header.type = htods(RES_XML_CDATA_TYPE); + extData = &cdataExt; + extSize = sizeof(cdataExt); + memset(&cdataExt, 0, sizeof(cdataExt)); + cdataExt.data.index = htodl(strings.offsetForString(mChars)); + cdataExt.typedData.size = htods(sizeof(cdataExt.typedData)); + cdataExt.typedData.res0 = 0; + cdataExt.typedData.dataType = mCharsValue.dataType; + cdataExt.typedData.data = htodl(mCharsValue.data); + LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!"); + } + + node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA)); + + if (writeCurrentNode) { + dest->writeData(&node, sizeof(node)); + if (extSize > 0) { + dest->writeData(extData, extSize); + } + } + + for (i=0; i<NA; i++) { + ssize_t idx = mAttributeOrder.valueAt(i); + const attribute_entry& ae = mAttributes.itemAt(idx); + if (ae.ns.size() > 0) { + attr.ns.index = htodl(strings.offsetForString(ae.ns)); + } else { + attr.ns.index = htodl((uint32_t)-1); + } + attr.name.index = htodl(ae.namePoolIdx); + + if (!stripRawValues || ae.needStringValue()) { + attr.rawValue.index = htodl(strings.offsetForString(ae.string)); + } else { + attr.rawValue.index = htodl((uint32_t)-1); + } + attr.typedValue.size = htods(sizeof(attr.typedValue)); + if (ae.value.dataType == Res_value::TYPE_NULL + || ae.value.dataType == Res_value::TYPE_STRING) { + attr.typedValue.res0 = 0; + attr.typedValue.dataType = Res_value::TYPE_STRING; + attr.typedValue.data = htodl(strings.offsetForString(ae.string)); + } else { + attr.typedValue.res0 = 0; + attr.typedValue.dataType = ae.value.dataType; + attr.typedValue.data = htodl(ae.value.data); + } + dest->writeData(&attr, sizeof(attr)); + } + + for (i=0; i<NC; i++) { + status_t err = mChildren.itemAt(i)->flatten_node(strings, dest, + stripComments, stripRawValues); + if (err != NO_ERROR) { + return err; + } + } + + if (type == TYPE_ELEMENT) { + ResXMLTree_endElementExt endElementExt; + memset(&endElementExt, 0, sizeof(endElementExt)); + node.header.type = htods(RES_XML_END_ELEMENT_TYPE); + node.header.size = htodl(sizeof(node)+sizeof(endElementExt)); + node.lineNumber = htodl(getEndLineNumber()); + node.comment.index = htodl((uint32_t)-1); + endElementExt.ns.index = attrExt.ns.index; + endElementExt.name.index = attrExt.name.index; + dest->writeData(&node, sizeof(node)); + dest->writeData(&endElementExt, sizeof(endElementExt)); + } else if (type == TYPE_NAMESPACE) { + if (writeCurrentNode) { + node.header.type = htods(RES_XML_END_NAMESPACE_TYPE); + node.lineNumber = htodl(getEndLineNumber()); + node.comment.index = htodl((uint32_t)-1); + node.header.size = htodl(sizeof(node)+extSize); + dest->writeData(&node, sizeof(node)); + dest->writeData(extData, extSize); + } + } + + return NO_ERROR; +} diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h new file mode 100644 index 0000000..05624b7 --- /dev/null +++ b/tools/aapt/XMLNode.h @@ -0,0 +1,202 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#ifndef XML_NODE_H +#define XML_NODE_H + +#include "StringPool.h" +#include "ResourceTable.h" + +class XMLNode; + +extern const char* const RESOURCES_ROOT_NAMESPACE; +extern const char* const RESOURCES_ANDROID_NAMESPACE; + +bool isWhitespace(const char16_t* str); + +String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic = NULL); + +status_t parseStyledString(Bundle* bundle, + const char* fileName, + ResXMLTree* inXml, + const String16& endTag, + String16* outString, + Vector<StringPool::entry_style_span>* outSpans, + bool isFormatted, + bool isPseudolocalizable); + +void printXMLBlock(ResXMLTree* block); + +status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree, + bool stripAll=true, bool keepComments=false, + const char** cDataTags=NULL); + +class XMLNode : public RefBase +{ +public: + static sp<XMLNode> parse(const sp<AaptFile>& file); + + static inline + sp<XMLNode> newNamespace(const String8& filename, const String16& prefix, const String16& uri) { + return new XMLNode(filename, prefix, uri, true); + } + + static inline + sp<XMLNode> newElement(const String8& filename, const String16& ns, const String16& name) { + return new XMLNode(filename, ns, name, false); + } + + static inline + sp<XMLNode> newCData(const String8& filename) { + return new XMLNode(filename); + } + + enum type { + TYPE_NAMESPACE, + TYPE_ELEMENT, + TYPE_CDATA + }; + + type getType() const; + + const String16& getNamespacePrefix() const; + const String16& getNamespaceUri() const; + + const String16& getElementNamespace() const; + const String16& getElementName() const; + const Vector<sp<XMLNode> >& getChildren() const; + + const String8& getFilename() const; + + struct attribute_entry { + attribute_entry() : index(~(uint32_t)0), nameResId(0) + { + value.dataType = Res_value::TYPE_NULL; + } + + bool needStringValue() const { + return nameResId == 0 + || value.dataType == Res_value::TYPE_NULL + || value.dataType == Res_value::TYPE_STRING; + } + + String16 ns; + String16 name; + String16 string; + Res_value value; + uint32_t index; + uint32_t nameResId; + mutable uint32_t namePoolIdx; + }; + + const Vector<attribute_entry>& getAttributes() const; + + const attribute_entry* getAttribute(const String16& ns, const String16& name) const; + + attribute_entry* editAttribute(const String16& ns, const String16& name); + + const String16& getCData() const; + + const String16& getComment() const; + + int32_t getStartLineNumber() const; + int32_t getEndLineNumber() const; + + sp<XMLNode> searchElement(const String16& tagNamespace, const String16& tagName); + + sp<XMLNode> getChildElement(const String16& tagNamespace, const String16& tagName); + + status_t addChild(const sp<XMLNode>& child); + + status_t insertChildAt(const sp<XMLNode>& child, size_t index); + + status_t addAttribute(const String16& ns, const String16& name, + const String16& value); + + void setAttributeResID(size_t attrIdx, uint32_t resId); + + status_t appendChars(const String16& chars); + + status_t appendComment(const String16& comment); + + void setStartLineNumber(int32_t line); + void setEndLineNumber(int32_t line); + + void removeWhitespace(bool stripAll=true, const char** cDataTags=NULL); + + void setUTF8(bool val) { mUTF8 = val; } + + status_t parseValues(const sp<AaptAssets>& assets, ResourceTable* table); + + status_t assignResourceIds(const sp<AaptAssets>& assets, + const ResourceTable* table = NULL); + + status_t flatten(const sp<AaptFile>& dest, bool stripComments, + bool stripRawValues) const; + + void print(int indent=0); + +private: + struct ParseState + { + String8 filename; + XML_Parser parser; + sp<XMLNode> root; + Vector<sp<XMLNode> > stack; + String16 pendingComment; + }; + + static void XMLCALL + startNamespace(void *userData, const char *prefix, const char *uri); + static void XMLCALL + startElement(void *userData, const char *name, const char **atts); + static void XMLCALL + characterData(void *userData, const XML_Char *s, int len); + static void XMLCALL + endElement(void *userData, const char *name); + static void XMLCALL + endNamespace(void *userData, const char *prefix); + + static void XMLCALL + commentData(void *userData, const char *comment); + + // Creating an element node. + XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace); + + // Creating a CDATA node. + XMLNode(const String8& filename); + + status_t collect_strings(StringPool* dest, Vector<uint32_t>* outResIds, + bool stripComments, bool stripRawValues) const; + + status_t collect_attr_strings(StringPool* outPool, + Vector<uint32_t>* outResIds, bool allAttrs) const; + + status_t collect_resid_strings(StringPool* outPool, + Vector<uint32_t>* outResIds) const; + + status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest, + bool stripComments, bool stripRawValues) const; + + String16 mNamespacePrefix; + String16 mNamespaceUri; + String16 mElementName; + Vector<sp<XMLNode> > mChildren; + Vector<attribute_entry> mAttributes; + KeyedVector<uint32_t, uint32_t> mAttributeOrder; + uint32_t mNextAttributeIndex; + String16 mChars; + Res_value mCharsValue; + String16 mComment; + String8 mFilename; + int32_t mStartLineNumber; + int32_t mEndLineNumber; + + // Encode compiled XML with UTF-8 StringPools? + bool mUTF8; +}; + +#endif diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp new file mode 100644 index 0000000..b575988 --- /dev/null +++ b/tools/aapt/ZipEntry.cpp @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2006 The Android Open Source 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. + */ + +// +// Access to entries in a Zip archive. +// + +#define LOG_TAG "zip" + +#include "ZipEntry.h" +#include <utils/Log.h> + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +using namespace android; + +/* + * Initialize a new ZipEntry structure from a FILE* positioned at a + * CentralDirectoryEntry. + * + * On exit, the file pointer will be at the start of the next CDE or + * at the EOCD. + */ +status_t ZipEntry::initFromCDE(FILE* fp) +{ + status_t result; + long posn; + bool hasDD; + + //ALOGV("initFromCDE ---\n"); + + /* read the CDE */ + result = mCDE.read(fp); + if (result != NO_ERROR) { + ALOGD("mCDE.read failed\n"); + return result; + } + + //mCDE.dump(); + + /* using the info in the CDE, go load up the LFH */ + posn = ftell(fp); + if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { + ALOGD("local header seek failed (%ld)\n", + mCDE.mLocalHeaderRelOffset); + return UNKNOWN_ERROR; + } + + result = mLFH.read(fp); + if (result != NO_ERROR) { + ALOGD("mLFH.read failed\n"); + return result; + } + + if (fseek(fp, posn, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + //mLFH.dump(); + + /* + * We *might* need to read the Data Descriptor at this point and + * integrate it into the LFH. If this bit is set, the CRC-32, + * compressed size, and uncompressed size will be zero. In practice + * these seem to be rare. + */ + hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; + if (hasDD) { + // do something clever + //ALOGD("+++ has data descriptor\n"); + } + + /* + * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" + * flag is set, because the LFH is incomplete. (Not a problem, since we + * prefer the CDE values.) + */ + if (!hasDD && !compareHeaders()) { + ALOGW("warning: header mismatch\n"); + // keep going? + } + + /* + * If the mVersionToExtract is greater than 20, we may have an + * issue unpacking the record -- could be encrypted, compressed + * with something we don't support, or use Zip64 extensions. We + * can defer worrying about that to when we're extracting data. + */ + + return NO_ERROR; +} + +/* + * Initialize a new entry. Pass in the file name and an optional comment. + * + * Initializes the CDE and the LFH. + */ +void ZipEntry::initNew(const char* fileName, const char* comment) +{ + assert(fileName != NULL && *fileName != '\0'); // name required + + /* most fields are properly initialized by constructor */ + mCDE.mVersionMadeBy = kDefaultMadeBy; + mCDE.mVersionToExtract = kDefaultVersion; + mCDE.mCompressionMethod = kCompressStored; + mCDE.mFileNameLength = strlen(fileName); + if (comment != NULL) + mCDE.mFileCommentLength = strlen(comment); + mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + strcpy((char*) mCDE.mFileName, fileName); + } + if (mCDE.mFileCommentLength > 0) { + /* TODO: stop assuming null-terminated ASCII here? */ + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + strcpy((char*) mCDE.mFileComment, comment); + } + + copyCDEtoLFH(); +} + +/* + * Initialize a new entry, starting with the ZipEntry from a different + * archive. + * + * Initializes the CDE and the LFH. + */ +status_t ZipEntry::initFromExternal(const ZipFile* pZipFile, + const ZipEntry* pEntry) +{ + /* + * Copy everything in the CDE over, then fix up the hairy bits. + */ + memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE)); + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + if (mCDE.mFileName == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName); + } + if (mCDE.mFileCommentLength > 0) { + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + if (mCDE.mFileComment == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment); + } + if (mCDE.mExtraFieldLength > 0) { + /* we null-terminate this, though it may not be a string */ + mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1]; + if (mCDE.mExtraField == NULL) + return NO_MEMORY; + memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField, + mCDE.mExtraFieldLength+1); + } + + /* construct the LFH from the CDE */ + copyCDEtoLFH(); + + /* + * The LFH "extra" field is independent of the CDE "extra", so we + * handle it here. + */ + assert(mLFH.mExtraField == NULL); + mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; + if (mLFH.mExtraFieldLength > 0) { + mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; + if (mLFH.mExtraField == NULL) + return NO_MEMORY; + memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, + mLFH.mExtraFieldLength+1); + } + + return NO_ERROR; +} + +/* + * Insert pad bytes in the LFH by tweaking the "extra" field. This will + * potentially confuse something that put "extra" data in here earlier, + * but I can't find an actual problem. + */ +status_t ZipEntry::addPadding(int padding) +{ + if (padding <= 0) + return INVALID_OPERATION; + + //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n", + // padding, mLFH.mExtraFieldLength, mCDE.mFileName); + + if (mLFH.mExtraFieldLength > 0) { + /* extend existing field */ + unsigned char* newExtra; + + newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; + if (newExtra == NULL) + return NO_MEMORY; + memset(newExtra + mLFH.mExtraFieldLength, 0, padding); + memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); + + delete[] mLFH.mExtraField; + mLFH.mExtraField = newExtra; + mLFH.mExtraFieldLength += padding; + } else { + /* create new field */ + mLFH.mExtraField = new unsigned char[padding]; + memset(mLFH.mExtraField, 0, padding); + mLFH.mExtraFieldLength = padding; + } + + return NO_ERROR; +} + +/* + * Set the fields in the LFH equal to the corresponding fields in the CDE. + * + * This does not touch the LFH "extra" field. + */ +void ZipEntry::copyCDEtoLFH(void) +{ + mLFH.mVersionToExtract = mCDE.mVersionToExtract; + mLFH.mGPBitFlag = mCDE.mGPBitFlag; + mLFH.mCompressionMethod = mCDE.mCompressionMethod; + mLFH.mLastModFileTime = mCDE.mLastModFileTime; + mLFH.mLastModFileDate = mCDE.mLastModFileDate; + mLFH.mCRC32 = mCDE.mCRC32; + mLFH.mCompressedSize = mCDE.mCompressedSize; + mLFH.mUncompressedSize = mCDE.mUncompressedSize; + mLFH.mFileNameLength = mCDE.mFileNameLength; + // the "extra field" is independent + + delete[] mLFH.mFileName; + if (mLFH.mFileNameLength > 0) { + mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; + strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); + } else { + mLFH.mFileName = NULL; + } +} + +/* + * Set some information about a file after we add it. + */ +void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod) +{ + mCDE.mCompressionMethod = compressionMethod; + mCDE.mCRC32 = crc32; + mCDE.mCompressedSize = compLen; + mCDE.mUncompressedSize = uncompLen; + mCDE.mCompressionMethod = compressionMethod; + if (compressionMethod == kCompressDeflated) { + mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used + } + copyCDEtoLFH(); +} + +/* + * See if the data in mCDE and mLFH match up. This is mostly useful for + * debugging these classes, but it can be used to identify damaged + * archives. + * + * Returns "false" if they differ. + */ +bool ZipEntry::compareHeaders(void) const +{ + if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { + ALOGV("cmp: VersionToExtract\n"); + return false; + } + if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { + ALOGV("cmp: GPBitFlag\n"); + return false; + } + if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { + ALOGV("cmp: CompressionMethod\n"); + return false; + } + if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { + ALOGV("cmp: LastModFileTime\n"); + return false; + } + if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { + ALOGV("cmp: LastModFileDate\n"); + return false; + } + if (mCDE.mCRC32 != mLFH.mCRC32) { + ALOGV("cmp: CRC32\n"); + return false; + } + if (mCDE.mCompressedSize != mLFH.mCompressedSize) { + ALOGV("cmp: CompressedSize\n"); + return false; + } + if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { + ALOGV("cmp: UncompressedSize\n"); + return false; + } + if (mCDE.mFileNameLength != mLFH.mFileNameLength) { + ALOGV("cmp: FileNameLength\n"); + return false; + } +#if 0 // this seems to be used for padding, not real data + if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { + ALOGV("cmp: ExtraFieldLength\n"); + return false; + } +#endif + if (mCDE.mFileName != NULL) { + if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { + ALOGV("cmp: FileName\n"); + return false; + } + } + + return true; +} + + +/* + * Convert the DOS date/time stamp into a UNIX time stamp. + */ +time_t ZipEntry::getModWhen(void) const +{ + struct tm parts; + + parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; + parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; + parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; + parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); + parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; + parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; + parts.tm_wday = parts.tm_yday = 0; + parts.tm_isdst = -1; // DST info "not available" + + return mktime(&parts); +} + +/* + * Set the CDE/LFH timestamp from UNIX time. + */ +void ZipEntry::setModWhen(time_t when) +{ +#ifdef HAVE_LOCALTIME_R + struct tm tmResult; +#endif + time_t even; + unsigned short zdate, ztime; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ +#ifdef HAVE_LOCALTIME_R + ptm = localtime_r(&even, &tmResult); +#else + ptm = localtime(&even); +#endif + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; + + mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; + mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; +} + + +/* + * =========================================================================== + * ZipEntry::LocalFileHeader + * =========================================================================== + */ + +/* + * Read a local file header. + * + * On entry, "fp" points to the signature at the start of the header. + * On exit, "fp" points to the start of data. + */ +status_t ZipEntry::LocalFileHeader::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kLFHLen]; + + assert(mFileName == NULL); + assert(mExtraField == NULL); + + if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + ALOGD("whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); + mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); + + // TODO: validate sizes + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* grab extra field */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a local file header. + */ +status_t ZipEntry::LocalFileHeader::write(FILE* fp) +{ + unsigned char buf[kLFHLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x0e], mCRC32); + ZipEntry::putLongLE(&buf[0x12], mCompressedSize); + ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); + + if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Dump the contents of a LocalFileHeader object. + */ +void ZipEntry::LocalFileHeader::dump(void) const +{ + ALOGD(" LocalFileHeader contents:\n"); + ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionToExtract, mGPBitFlag, mCompressionMethod); + ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + ALOGD(" filenameLen=%u extraLen=%u\n", + mFileNameLength, mExtraFieldLength); + if (mFileName != NULL) + ALOGD(" filename: '%s'\n", mFileName); +} + + +/* + * =========================================================================== + * ZipEntry::CentralDirEntry + * =========================================================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "fp" should be positioned on the signature bytes for the + * entry. On exit, "fp" will point at the signature word for the next + * entry or for the EOCD. + */ +status_t ZipEntry::CentralDirEntry::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kCDELen]; + + /* no re-use */ + assert(mFileName == NULL); + assert(mExtraField == NULL); + assert(mFileComment == NULL); + + if (fread(buf, 1, kCDELen, fp) != kCDELen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + ALOGD("Whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); + mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); + mCRC32 = ZipEntry::getLongLE(&buf[0x10]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); + mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); + mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); + mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); + mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); + mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); + + // TODO: validate sizes and offsets + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* read "extra field" */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + + + /* grab comment, if any */ + if (mFileCommentLength != 0) { + mFileComment = new unsigned char[mFileCommentLength+1]; + if (mFileComment == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + { + result = UNKNOWN_ERROR; + goto bail; + } + mFileComment[mFileCommentLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a central dir entry. + */ +status_t ZipEntry::CentralDirEntry::write(FILE* fp) +{ + unsigned char buf[kCDELen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); + ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x10], mCRC32); + ZipEntry::putLongLE(&buf[0x14], mCompressedSize); + ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); + ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); + ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); + ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); + ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); + ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); + + if (fwrite(buf, 1, kCDELen, fp) != kCDELen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + /* write comment */ + if (mFileCommentLength != 0) { + if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void ZipEntry::CentralDirEntry::dump(void) const +{ + ALOGD(" CentralDirEntry contents:\n"); + ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); + ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", + mFileNameLength, mExtraFieldLength, mFileCommentLength); + ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", + mDiskNumberStart, mInternalAttrs, mExternalAttrs, + mLocalHeaderRelOffset); + + if (mFileName != NULL) + ALOGD(" filename: '%s'\n", mFileName); + if (mFileComment != NULL) + ALOGD(" comment: '%s'\n", mFileComment); +} + diff --git a/tools/aapt/ZipEntry.h b/tools/aapt/ZipEntry.h new file mode 100644 index 0000000..c2f3227 --- /dev/null +++ b/tools/aapt/ZipEntry.h @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2006 The Android Open Source 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. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> + +namespace android { + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace android + +#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp new file mode 100644 index 0000000..8057068 --- /dev/null +++ b/tools/aapt/ZipFile.cpp @@ -0,0 +1,1297 @@ +/* + * Copyright (C) 2006 The Android Open Source 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. + */ + +// +// Access to Zip archives. +// + +#define LOG_TAG "zip" + +#include <androidfw/ZipUtils.h> +#include <utils/Log.h> + +#include "ZipFile.h" + +#include <zlib.h> +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +#include <memory.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/* + * Some environments require the "b", some choke on it. + */ +#define FILE_OPEN_RO "rb" +#define FILE_OPEN_RW "r+b" +#define FILE_OPEN_RW_CREATE "w+b" + +/* should live somewhere else? */ +static status_t errnoToStatus(int err) +{ + if (err == ENOENT) + return NAME_NOT_FOUND; + else if (err == EACCES) + return PERMISSION_DENIED; + else + return UNKNOWN_ERROR; +} + +/* + * Open a file and parse its guts. + */ +status_t ZipFile::open(const char* zipFileName, int flags) +{ + bool newArchive = false; + + 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 + + if (flags & kOpenTruncate) { + newArchive = true; + } else { + newArchive = (access(zipFileName, F_OK) != 0); + if (!(flags & kOpenCreate) && newArchive) { + /* not creating, must already exist */ + ALOGD("File %s does not exist", zipFileName); + return NAME_NOT_FOUND; + } + } + + /* 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 = fopen(zipFileName, openflags); + if (mZipFp == NULL) { + int err = errno; + ALOGD("fopen failed: %d\n", 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 +{ + if (idx < 0 || idx >= (int) mEntries.size()) + return NULL; + + return mEntries[idx]; +} + +/* + * Find an entry by name. + */ +ZipEntry* ZipFile::getEntryByName(const char* fileName) const +{ + /* + * Do a stupid linear string-compare search. + * + * There are various ways to speed this up, especially since it's rare + * to intermingle changes to the archive with "get by name" calls. We + * don't want to sort the mEntries vector itself, however, because + * it's used to recreate the Central Directory. + * + * (Hash table works, parallel list of pointers in sorted order is good.) + */ + int idx; + + for (idx = mEntries.size()-1; idx >= 0; idx--) { + ZipEntry* pEntry = mEntries[idx]; + if (!pEntry->getDeleted() && + strcmp(fileName, pEntry->getFileName()) == 0) + { + return pEntry; + } + } + + return NULL; +} + +/* + * Empty the mEntries vector. + */ +void ZipFile::discardEntries(void) +{ + int count = mEntries.size(); + + while (--count >= 0) + delete mEntries[count]; + + mEntries.clear(); +} + + +/* + * Find the central directory and read the contents. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ +status_t ZipFile::readCentralDir(void) +{ + status_t result = NO_ERROR; + unsigned char* buf = NULL; + off_t fileLength, seekStart; + long readAmount; + int i; + + fseek(mZipFp, 0, SEEK_END); + fileLength = ftell(mZipFp); + rewind(mZipFp); + + /* too small to be a ZIP archive? */ + if (fileLength < EndOfCentralDir::kEOCDLen) { + ALOGD("Length is %ld -- too small\n", (long)fileLength); + result = INVALID_OPERATION; + goto bail; + } + + buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; + if (buf == NULL) { + ALOGD("Failure allocating %d bytes for EOCD search", + EndOfCentralDir::kMaxEOCDSearch); + result = NO_MEMORY; + goto bail; + } + + if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { + seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; + readAmount = EndOfCentralDir::kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) fileLength; + } + if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { + ALOGD("Failure seeking to end of zip at %ld", (long) seekStart); + result = UNKNOWN_ERROR; + goto bail; + } + + /* read the last part of the file into the buffer */ + if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { + ALOGD("short file? wanted %ld\n", readAmount); + result = UNKNOWN_ERROR; + goto bail; + } + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + ALOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + ALOGD("EOCD not found, not Zip\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* extract eocd values */ + result = mEOCD.readBuf(buf + i, readAmount - i); + if (result != NO_ERROR) { + ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i); + goto bail; + } + //mEOCD.dump(); + + if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || + mEOCD.mNumEntries != mEOCD.mTotalNumEntries) + { + ALOGD("Archive spanning not supported\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* + * So far so good. "mCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward mCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + ALOGD("Failure seeking to central dir offset %ld\n", + mEOCD.mCentralDirOffset); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Loop through and read the central dir entries. + */ + ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); + int entry; + for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { + ZipEntry* pEntry = new ZipEntry; + + result = pEntry->initFromCDE(mZipFp); + if (result != NO_ERROR) { + ALOGD("initFromCDE failed\n"); + delete pEntry; + goto bail; + } + + mEntries.add(pEntry); + } + + + /* + * If all went well, we should now be back at the EOCD. + */ + { + unsigned char checkBuf[4]; + if (fread(checkBuf, 1, 4, mZipFp) != 4) { + ALOGD("EOCD check read failed\n"); + result = INVALID_OPERATION; + goto bail; + } + if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { + ALOGD("EOCD read check failed\n"); + result = UNKNOWN_ERROR; + goto bail; + } + ALOGV("+++ EOCD read check passed\n"); + } + +bail: + delete[] buf; + return result; +} + + +/* + * Add a new file to the archive. + * + * This requires creating and populating a ZipEntry structure, and copying + * the data into the file at the appropriate position. The "appropriate + * position" is the current location of the central directory, which we + * casually overwrite (we can put it back later). + * + * If we were concerned about safety, we would want to make all changes + * in a temp file and then overwrite the original after everything was + * safely written. Not really a concern for us. + */ +status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result = NO_ERROR; + long lfhPosn, startPosn, endPosn, uncompressedLen; + FILE* inputFp = NULL; + unsigned long crc; + time_t modWhen; + + if (mReadOnly) + return INVALID_OPERATION; + + assert(compressionMethod == ZipEntry::kCompressDeflated || + compressionMethod == ZipEntry::kCompressStored); + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + /* make sure it doesn't already exist */ + if (getEntryByName(storageName) != NULL) + return ALREADY_EXISTS; + + if (!data) { + inputFp = fopen(fileName, FILE_OPEN_RO); + if (inputFp == NULL) + return errnoToStatus(errno); + } + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + pEntry->initNew(storageName, NULL); + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH, even though it's still mostly blank. We need it + * as a place-holder. In theory the LFH isn't necessary, but in + * practice some utilities demand it. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + startPosn = ftell(mZipFp); + + /* + * Copy the data in, possibly compressing it as we go. + */ + if (sourceType == ZipEntry::kCompressStored) { + if (compressionMethod == ZipEntry::kCompressDeflated) { + bool failed = false; + result = compressFpToFp(mZipFp, inputFp, data, size, &crc); + if (result != NO_ERROR) { + ALOGD("compression failed, storing\n"); + failed = true; + } else { + /* + * Make sure it has compressed "enough". This probably ought + * to be set through an API call, but I don't expect our + * criteria to change over time. + */ + long src = inputFp ? ftell(inputFp) : size; + long dst = ftell(mZipFp) - startPosn; + if (dst + (dst / 10) > src) { + ALOGD("insufficient compression (src=%ld dst=%ld), storing\n", + src, dst); + failed = true; + } + } + + if (failed) { + compressionMethod = ZipEntry::kCompressStored; + if (inputFp) rewind(inputFp); + fseek(mZipFp, startPosn, SEEK_SET); + /* fall through to kCompressStored case */ + } + } + /* handle "no compression" request, or failed compression from above */ + if (compressionMethod == ZipEntry::kCompressStored) { + if (inputFp) { + result = copyFpToFp(mZipFp, inputFp, &crc); + } else { + result = copyDataToFp(mZipFp, data, size, &crc); + } + if (result != NO_ERROR) { + // don't need to truncate; happens in CDE rewrite + ALOGD("failed copying data in\n"); + goto bail; + } + } + + // currently seeked to end of file + uncompressedLen = inputFp ? ftell(inputFp) : size; + } else if (sourceType == ZipEntry::kCompressDeflated) { + /* we should support uncompressed-from-compressed, but it's not + * important right now */ + assert(compressionMethod == ZipEntry::kCompressDeflated); + + bool scanResult; + int method; + long compressedLen; + + scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, + &compressedLen, &crc); + if (!scanResult || method != ZipEntry::kCompressDeflated) { + ALOGD("this isn't a deflated gzip file?"); + result = UNKNOWN_ERROR; + goto bail; + } + + result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); + if (result != NO_ERROR) { + ALOGD("failed copying gzip data in\n"); + goto bail; + } + } else { + assert(false); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * We could write the "Data Descriptor", but there doesn't seem to + * be any point since we're going to go back and write the LFH. + * + * Update file offsets. + */ + endPosn = ftell(mZipFp); // seeked to end of compressed data + + /* + * Success! Fill out new values. + */ + pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, + compressionMethod); + modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); + pEntry->setModWhen(modWhen); + pEntry->setLFHOffset(lfhPosn); + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Go back and write the LFH. + */ + if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + pEntry->mLFH.write(mZipFp); + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + +bail: + if (inputFp != NULL) + fclose(inputFp); + delete pEntry; + return result; +} + +/* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ +status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result; + long lfhPosn, endPosn; + + if (mReadOnly) + return INVALID_OPERATION; + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + if (pEntry == NULL) { + result = NO_MEMORY; + goto bail; + } + + result = pEntry->initFromExternal(pSourceZip, pSourceEntry); + if (result != NO_ERROR) + goto bail; + if (padding != 0) { + result = pEntry->addPadding(padding); + if (result != NO_ERROR) + goto bail; + } + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH. Since we're not recompressing the data, we already + * have all of the fields filled out. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + + /* + * Copy the data over. + * + * If the "has data descriptor" flag is set, we want to copy the DD + * fields as well. This is a fixed-size area immediately following + * the data. + */ + if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) + { + result = UNKNOWN_ERROR; + goto bail; + } + + off_t copyLen; + copyLen = pSourceEntry->getCompressedLen(); + if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) + copyLen += ZipEntry::kDataDescriptorLen; + + if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) + != NO_ERROR) + { + ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Update file offsets. + */ + endPosn = ftell(mZipFp); + + /* + * Success! Fill out new values. + */ + pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + + result = NO_ERROR; + +bail: + delete pEntry; + return result; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data. + */ +status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (1) { + count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); + if (ferror(srcFp) || ferror(dstFp)) + return errnoToStatus(errno); + if (count == 0) + break; + + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + ALOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "dstFp" will be seeked immediately past the data. + */ +status_t ZipFile::copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + if (size > 0) { + *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); + if (fwrite(data, 1, size, dstFp) != size) { + ALOGD("fwrite %d bytes failed\n", (int) size); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy some of the bytes in "src" to "dst". + * + * If "pCRC32" is NULL, the CRC will not be computed. + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data just written. + */ +status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + if (pCRC32 != NULL) + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (length) { + long readSize; + + readSize = sizeof(tmpBuf); + if (readSize > length) + readSize = length; + + count = fread(tmpBuf, 1, readSize, srcFp); + if ((long) count != readSize) { // error or unexpected EOF + ALOGD("fread %d bytes failed\n", (int) readSize); + return UNKNOWN_ERROR; + } + + if (pCRC32 != NULL) + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + ALOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + + length -= readSize; + } + + return NO_ERROR; +} + +/* + * Compress all of the data in "srcFp" and write it to "dstFp". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the compressed data. + */ +status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + status_t result = NO_ERROR; + const size_t kBufSize = 32768; + unsigned char* inBuf = NULL; + unsigned char* outBuf = NULL; + z_stream zstream; + bool atEof = false; // no feof() aviailable yet + unsigned long crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new unsigned char[kBufSize]; + outBuf = new unsigned char[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + result = NO_MEMORY; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + result = UNKNOWN_ERROR; + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + size_t getSize; + int flush; + + /* only read if the input buffer is empty */ + if (zstream.avail_in == 0 && !atEof) { + ALOGV("+++ reading %d bytes\n", (int)kBufSize); + if (data) { + getSize = size > kBufSize ? kBufSize : size; + memcpy(inBuf, data, getSize); + data = ((const char*)data) + getSize; + size -= getSize; + } else { + getSize = fread(inBuf, 1, kBufSize, srcFp); + if (ferror(srcFp)) { + ALOGD("deflate read failed (errno=%d)\n", errno); + goto z_bail; + } + } + if (getSize < kBufSize) { + ALOGV("+++ got %d bytes, EOF reached\n", + (int)getSize); + atEof = true; + } + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (atEof) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGD("zlib deflate call failed (zerr=%d)\n", zerr); + result = UNKNOWN_ERROR; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) + { + ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); + if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != + (size_t)(zstream.next_out - outBuf)) + { + ALOGD("write %d failed in deflate\n", + (int) (zstream.next_out - outBuf)); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCRC32 = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return result; +} + +/* + * Mark an entry as deleted. + * + * We will eventually need to crunch the file down, but if several files + * are being removed (perhaps as part of an "update" process) we can make + * things considerably faster by deferring the removal to "flush" time. + */ +status_t ZipFile::remove(ZipEntry* pEntry) +{ + /* + * Should verify that pEntry is actually part of this archive, and + * not some stray ZipEntry from a different file. + */ + + /* mark entry as deleted, and mark archive as dirty */ + pEntry->setDeleted(); + mNeedCDRewrite = true; + return NO_ERROR; +} + +/* + * Flush any pending writes. + * + * In particular, this will crunch out deleted entries, and write the + * Central Directory and EOCD if we have stomped on them. + */ +status_t ZipFile::flush(void) +{ + status_t result = NO_ERROR; + long eocdPosn; + int i, count; + + if (mReadOnly) + return INVALID_OPERATION; + if (!mNeedCDRewrite) + return NO_ERROR; + + assert(mZipFp != NULL); + + result = crunchArchive(); + if (result != NO_ERROR) + return result; + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + count = mEntries.size(); + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + pEntry->mCDE.write(mZipFp); + } + + eocdPosn = ftell(mZipFp); + mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; + + mEOCD.write(mZipFp); + + /* + * If we had some stuff bloat up during compression and get replaced + * with plain files, or if we deleted some entries, there's a lot + * of wasted space at the end of the file. Remove it now. + */ + if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { + ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); + // not fatal + } + + /* should we clear the "newly added" flag in all entries now? */ + + mNeedCDRewrite = false; + return NO_ERROR; +} + +/* + * Crunch deleted files out of an archive by shifting the later files down. + * + * Because we're not using a temp file, we do the operation inside the + * current file. + */ +status_t ZipFile::crunchArchive(void) +{ + status_t result = NO_ERROR; + int i, count; + long delCount, adjust; + +#if 0 + printf("CONTENTS:\n"); + for (i = 0; i < (int) mEntries.size(); i++) { + printf(" %d: lfhOff=%ld del=%d\n", + i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); + } + printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); +#endif + + /* + * Roll through the set of files, shifting them as appropriate. We + * could probably get a slight performance improvement by sliding + * multiple files down at once (because we could use larger reads + * when operating on batches of small files), but it's not that useful. + */ + count = mEntries.size(); + delCount = adjust = 0; + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + long span; + + if (pEntry->getLFHOffset() != 0) { + long nextOffset; + + /* Get the length of this entry by finding the offset + * of the next entry. Directory entries don't have + * file offsets, so we need to find the next non-directory + * entry. + */ + nextOffset = 0; + for (int ii = i+1; nextOffset == 0 && ii < count; ii++) + nextOffset = mEntries[ii]->getLFHOffset(); + if (nextOffset == 0) + nextOffset = mEOCD.mCentralDirOffset; + span = nextOffset - pEntry->getLFHOffset(); + + assert(span >= ZipEntry::LocalFileHeader::kLFHLen); + } else { + /* This is a directory entry. It doesn't have + * any actual file contents, so there's no need to + * move anything. + */ + span = 0; + } + + //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", + // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); + + if (pEntry->getDeleted()) { + adjust += span; + delCount++; + + delete pEntry; + mEntries.removeAt(i); + + /* adjust loop control */ + count--; + i--; + } else if (span != 0 && adjust > 0) { + /* shuffle this entry back */ + //printf("+++ Shuffling '%s' back %ld\n", + // pEntry->getFileName(), adjust); + result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, + pEntry->getLFHOffset(), span); + if (result != NO_ERROR) { + /* this is why you use a temp file */ + ALOGE("error during crunch - archive is toast\n"); + return result; + } + + pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); + } + } + + /* + * Fix EOCD info. We have to wait until the end to do some of this + * because we use mCentralDirOffset to determine "span" for the + * last entry. + */ + mEOCD.mCentralDirOffset -= adjust; + mEOCD.mNumEntries -= delCount; + mEOCD.mTotalNumEntries -= delCount; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + + assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); + assert(mEOCD.mNumEntries == count); + + return result; +} + +/* + * Works like memmove(), but on pieces of a file. + */ +status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) +{ + if (dst == src || n <= 0) + return NO_ERROR; + + unsigned char readBuf[32768]; + + if (dst < src) { + /* shift stuff toward start of file; must read from start */ + while (n != 0) { + size_t getSize = sizeof(readBuf); + if (getSize > n) + getSize = n; + + if (fseek(fp, (long) src, SEEK_SET) != 0) { + ALOGD("filemove src seek %ld failed\n", (long) src); + return UNKNOWN_ERROR; + } + + if (fread(readBuf, 1, getSize, fp) != getSize) { + ALOGD("filemove read %ld off=%ld failed\n", + (long) getSize, (long) src); + return UNKNOWN_ERROR; + } + + if (fseek(fp, (long) dst, SEEK_SET) != 0) { + ALOGD("filemove dst seek %ld failed\n", (long) dst); + return UNKNOWN_ERROR; + } + + if (fwrite(readBuf, 1, getSize, fp) != getSize) { + ALOGD("filemove write %ld off=%ld failed\n", + (long) getSize, (long) dst); + return UNKNOWN_ERROR; + } + + src += getSize; + dst += getSize; + n -= getSize; + } + } else { + /* shift stuff toward end of file; must read from end */ + assert(false); // write this someday, maybe + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Get the modification time from a file descriptor. + */ +time_t ZipFile::getModTime(int fd) +{ + struct stat sb; + + if (fstat(fd, &sb) < 0) { + ALOGD("HEY: fstat on fd %d failed\n", fd); + return (time_t) -1; + } + + return sb.st_mtime; +} + + +#if 0 /* this is a bad idea */ +/* + * Get a copy of the Zip file descriptor. + * + * We don't allow this if the file was opened read-write because we tend + * to leave the file contents in an uncertain state between calls to + * flush(). The duplicated file descriptor should only be valid for reads. + */ +int ZipFile::getZipFd(void) const +{ + if (!mReadOnly) + return INVALID_OPERATION; + assert(mZipFp != NULL); + + int fd; + fd = dup(fileno(mZipFp)); + if (fd < 0) { + ALOGD("didn't work, errno=%d\n", errno); + } + + return fd; +} +#endif + + +#if 0 +/* + * Expand data. + */ +bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const +{ + return false; +} +#endif + +// free the memory when you're done +void* ZipFile::uncompress(const ZipEntry* entry) +{ + size_t unlen = entry->getUncompressedLen(); + size_t clen = entry->getCompressedLen(); + + void* buf = malloc(unlen); + if (buf == NULL) { + return NULL; + } + + fseek(mZipFp, 0, SEEK_SET); + + off_t offset = entry->getFileOffset(); + if (fseek(mZipFp, offset, SEEK_SET) != 0) { + goto bail; + } + + switch (entry->getCompressionMethod()) + { + case ZipEntry::kCompressStored: { + ssize_t amt = fread(buf, 1, unlen, mZipFp); + if (amt != (ssize_t)unlen) { + goto bail; + } +#if 0 + printf("data...\n"); + const unsigned char* p = (unsigned char*)buf; + const unsigned char* end = p+unlen; + for (int i=0; i<32 && p < end; i++) { + printf("0x%08x ", (int)(offset+(i*0x10))); + for (int j=0; j<0x10 && p < end; j++) { + printf(" %02x", *p); + p++; + } + printf("\n"); + } +#endif + + } + break; + case ZipEntry::kCompressDeflated: { + if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { + goto bail; + } + } + break; + default: + goto bail; + } + return buf; + +bail: + free(buf); + return NULL; +} + + +/* + * =========================================================================== + * ZipFile::EndOfCentralDir + * =========================================================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature, and should contain + * the entire EOCD area including the comment. + */ +status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) +{ + /* don't allow re-use */ + assert(mComment == NULL); + + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n", + kEOCDLen, len); + return INVALID_OPERATION; + } + + /* this should probably be an assert() */ + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) + return UNKNOWN_ERROR; + + mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); + mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); + mNumEntries = ZipEntry::getShortLE(&buf[0x08]); + mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); + mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); + mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); + mCommentLen = ZipEntry::getShortLE(&buf[0x14]); + + // TODO: validate mCentralDirOffset + + if (mCommentLen > 0) { + if (kEOCDLen + mCommentLen > len) { + ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", + kEOCDLen, mCommentLen, len); + return UNKNOWN_ERROR; + } + mComment = new unsigned char[mCommentLen]; + memcpy(mComment, buf + kEOCDLen, mCommentLen); + } + + return NO_ERROR; +} + +/* + * Write an end-of-central-directory section. + */ +status_t ZipFile::EndOfCentralDir::write(FILE* fp) +{ + unsigned char buf[kEOCDLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mDiskNumber); + ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); + ZipEntry::putShortLE(&buf[0x08], mNumEntries); + ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); + ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); + ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); + ZipEntry::putShortLE(&buf[0x14], mCommentLen); + + if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) + return UNKNOWN_ERROR; + if (mCommentLen > 0) { + assert(mComment != NULL); + if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void ZipFile::EndOfCentralDir::dump(void) const +{ + ALOGD(" EndOfCentralDir contents:\n"); + ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", + mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); + ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", + mCentralDirSize, mCentralDirOffset, mCommentLen); +} + diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h new file mode 100644 index 0000000..7877550 --- /dev/null +++ b/tools/aapt/ZipFile.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2006 The Android Open Source 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. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include <utils/Vector.h> +#include <utils/Errors.h> +#include <stdio.h> + +#include "ZipEntry.h" + +namespace android { + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least <uncompressed len> bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + Vector<ZipEntry*> mEntries; +}; + +}; // namespace android + +#endif // __LIBS_ZIPFILE_H diff --git a/tools/aapt/printapk.cpp b/tools/aapt/printapk.cpp new file mode 100644 index 0000000..4cf73d8 --- /dev/null +++ b/tools/aapt/printapk.cpp @@ -0,0 +1,127 @@ +#include <utils/ResourceTypes.h> +#include <utils/String8.h> +#include <utils/String16.h> +#include <zipfile/zipfile.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +using namespace android; + +static int +usage() +{ + fprintf(stderr, + "usage: apk APKFILE\n" + "\n" + "APKFILE an android packge file produced by aapt.\n" + ); + return 1; +} + + +int +main(int argc, char** argv) +{ + const char* filename; + int fd; + ssize_t amt; + off_t size; + void* buf; + zipfile_t zip; + zipentry_t entry; + void* cookie; + void* resfile; + int bufsize; + int err; + + if (argc != 2) { + return usage(); + } + + filename = argv[1]; + fd = open(filename, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "apk: couldn't open file for read: %s\n", filename); + return 1; + } + + size = lseek(fd, 0, SEEK_END); + amt = lseek(fd, 0, SEEK_SET); + + if (size < 0 || amt < 0) { + fprintf(stderr, "apk: error determining file size: %s\n", filename); + return 1; + } + + buf = malloc(size); + if (buf == NULL) { + fprintf(stderr, "apk: file too big: %s\n", filename); + return 1; + } + + amt = read(fd, buf, size); + if (amt != size) { + fprintf(stderr, "apk: error reading file: %s\n", filename); + return 1; + } + + close(fd); + + zip = init_zipfile(buf, size); + if (zip == NULL) { + fprintf(stderr, "apk: file doesn't seem to be a zip file: %s\n", + filename); + return 1; + } + + printf("files:\n"); + cookie = NULL; + while ((entry = iterate_zipfile(zip, &cookie))) { + char* name = get_zipentry_name(entry); + printf(" %s\n", name); + free(name); + } + + entry = lookup_zipentry(zip, "resources.arsc"); + if (entry != NULL) { + size = get_zipentry_size(entry); + bufsize = size + (size / 1000) + 1; + resfile = malloc(bufsize); + + err = decompress_zipentry(entry, resfile, bufsize); + if (err != 0) { + fprintf(stderr, "apk: error decompressing resources.arsc"); + return 1; + } + + ResTable res(resfile, size, resfile); + res.print(); +#if 0 + size_t tableCount = res.getTableCount(); + printf("Tables: %d\n", (int)tableCount); + for (size_t tableIndex=0; tableIndex<tableCount; tableIndex++) { + const ResStringPool* strings = res.getTableStringBlock(tableIndex); + size_t stringCount = strings->size(); + for (size_t stringIndex=0; stringIndex<stringCount; stringIndex++) { + size_t len; + const char16_t* ch = strings->stringAt(stringIndex, &len); + String8 s(String16(ch, len)); + printf(" [%3d] %s\n", (int)stringIndex, s.string()); + } + } + + size_t basePackageCount = res.getBasePackageCount(); + printf("Base Packages: %d\n", (int)basePackageCount); + for (size_t bpIndex=0; bpIndex<basePackageCount; bpIndex++) { + const char16_t* ch = res.getBasePackageName(bpIndex); + String8 s = String8(String16(ch)); + printf(" [%3d] %s\n", (int)bpIndex, s.string()); + } +#endif + } + + + return 0; +} diff --git a/tools/aapt/pseudolocalize.cpp b/tools/aapt/pseudolocalize.cpp new file mode 100644 index 0000000..9e50c5a --- /dev/null +++ b/tools/aapt/pseudolocalize.cpp @@ -0,0 +1,119 @@ +#include "pseudolocalize.h" + +using namespace std; + +static const char* +pseudolocalize_char(char c) +{ + switch (c) { + case 'a': return "\xc4\x83"; + case 'b': return "\xcf\x84"; + case 'c': return "\xc4\x8b"; + case 'd': return "\xc4\x8f"; + case 'e': return "\xc4\x99"; + case 'f': return "\xc6\x92"; + case 'g': return "\xc4\x9d"; + case 'h': return "\xd1\x9b"; + case 'i': return "\xcf\x8a"; + case 'j': return "\xc4\xb5"; + case 'k': return "\xc4\xb8"; + case 'l': return "\xc4\xba"; + case 'm': return "\xe1\xb8\xbf"; + case 'n': return "\xd0\xb8"; + case 'o': return "\xcf\x8c"; + case 'p': return "\xcf\x81"; + case 'q': return "\x51"; + case 'r': return "\xd2\x91"; + case 's': return "\xc5\xa1"; + case 't': return "\xd1\x82"; + case 'u': return "\xce\xb0"; + case 'v': return "\x56"; + case 'w': return "\xe1\xba\x85"; + case 'x': return "\xd1\x85"; + case 'y': return "\xe1\xbb\xb3"; + case 'z': return "\xc5\xba"; + case 'A': return "\xc3\x85"; + case 'B': return "\xce\xb2"; + case 'C': return "\xc4\x88"; + case 'D': return "\xc4\x90"; + case 'E': return "\xd0\x84"; + case 'F': return "\xce\x93"; + case 'G': return "\xc4\x9e"; + case 'H': return "\xc4\xa6"; + case 'I': return "\xd0\x87"; + case 'J': return "\xc4\xb5"; + case 'K': return "\xc4\xb6"; + case 'L': return "\xc5\x81"; + case 'M': return "\xe1\xb8\xbe"; + case 'N': return "\xc5\x83"; + case 'O': return "\xce\x98"; + case 'P': return "\xcf\x81"; + case 'Q': return "\x71"; + case 'R': return "\xd0\xaf"; + case 'S': return "\xc8\x98"; + case 'T': return "\xc5\xa6"; + case 'U': return "\xc5\xa8"; + case 'V': return "\xce\xbd"; + case 'W': return "\xe1\xba\x84"; + case 'X': return "\xc3\x97"; + case 'Y': return "\xc2\xa5"; + case 'Z': return "\xc5\xbd"; + default: return NULL; + } +} + +/** + * Converts characters so they look like they've been localized. + * + * Note: This leaves escape sequences untouched so they can later be + * processed by ResTable::collectString in the normal way. + */ +string +pseudolocalize_string(const string& source) +{ + const char* s = source.c_str(); + string result; + const size_t I = source.length(); + for (size_t i=0; i<I; i++) { + char c = s[i]; + if (c == '\\') { + if (i<I-1) { + result += '\\'; + i++; + c = s[i]; + switch (c) { + case 'u': + // this one takes up 5 chars + result += string(s+i, 5); + i += 4; + break; + case 't': + case 'n': + case '#': + case '@': + case '?': + case '"': + case '\'': + case '\\': + default: + result += c; + break; + } + } else { + result += c; + } + } else { + const char* p = pseudolocalize_char(c); + if (p != NULL) { + result += p; + } else { + result += c; + } + } + } + + //printf("result=\'%s\'\n", result.c_str()); + return result; +} + + diff --git a/tools/aapt/pseudolocalize.h b/tools/aapt/pseudolocalize.h new file mode 100644 index 0000000..94cb034 --- /dev/null +++ b/tools/aapt/pseudolocalize.h @@ -0,0 +1,9 @@ +#ifndef HOST_PSEUDOLOCALIZE_H +#define HOST_PSEUDOLOCALIZE_H + +#include <string> + +std::string pseudolocalize_string(const std::string& source); + +#endif // HOST_PSEUDOLOCALIZE_H + diff --git a/tools/aapt/qsort_r_compat.c b/tools/aapt/qsort_r_compat.c new file mode 100644 index 0000000..2a8dbe8 --- /dev/null +++ b/tools/aapt/qsort_r_compat.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 The Android Open Source 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 <stdlib.h> +#include "qsort_r_compat.h" + +/* + * Note: This code is only used on the host, and is primarily here for + * Mac OS compatibility. Apparently, glibc and Apple's libc disagree on + * the parameter order for qsort_r. + */ + +#if HAVE_BSD_QSORT_R + +/* + * BSD qsort_r parameter order is as we have defined here. + */ + +void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk, + int (*compar)(void*, const void* , const void*)) { + qsort_r(base, nel, width, thunk, compar); +} + +#elif HAVE_GNU_QSORT_R + +/* + * GNU qsort_r parameter order places the thunk parameter last. + */ + +struct compar_data { + void* thunk; + int (*compar)(void*, const void* , const void*); +}; + +static int compar_wrapper(const void* a, const void* b, void* data) { + struct compar_data* compar_data = (struct compar_data*)data; + return compar_data->compar(compar_data->thunk, a, b); +} + +void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk, + int (*compar)(void*, const void* , const void*)) { + struct compar_data compar_data; + compar_data.thunk = thunk; + compar_data.compar = compar; + qsort_r(base, nel, width, compar_wrapper, &compar_data); +} + +#else + +/* + * Emulate qsort_r using thread local storage to access the thunk data. + */ + +#include <cutils/threads.h> + +static thread_store_t compar_data_key = THREAD_STORE_INITIALIZER; + +struct compar_data { + void* thunk; + int (*compar)(void*, const void* , const void*); +}; + +static int compar_wrapper(const void* a, const void* b) { + struct compar_data* compar_data = (struct compar_data*)thread_store_get(&compar_data_key); + return compar_data->compar(compar_data->thunk, a, b); +} + +void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk, + int (*compar)(void*, const void* , const void*)) { + struct compar_data compar_data; + compar_data.thunk = thunk; + compar_data.compar = compar; + thread_store_set(&compar_data_key, &compar_data, NULL); + qsort(base, nel, width, compar_wrapper); +} + +#endif diff --git a/tools/aapt/qsort_r_compat.h b/tools/aapt/qsort_r_compat.h new file mode 100644 index 0000000..e14f999 --- /dev/null +++ b/tools/aapt/qsort_r_compat.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source 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. + */ + +/* + * Provides a portable version of qsort_r, called qsort_r_compat, which is a + * reentrant variant of qsort that passes a user data pointer to its comparator. + * This implementation follows the BSD parameter convention. + */ + +#ifndef ___QSORT_R_COMPAT_H +#define ___QSORT_R_COMPAT_H + +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk, + int (*compar)(void*, const void* , const void* )); + +#ifdef __cplusplus +} +#endif + +#endif // ___QSORT_R_COMPAT_H diff --git a/tools/aapt/tests/CrunchCache_test.cpp b/tools/aapt/tests/CrunchCache_test.cpp new file mode 100644 index 0000000..20b5022 --- /dev/null +++ b/tools/aapt/tests/CrunchCache_test.cpp @@ -0,0 +1,97 @@ +// +// 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 diff --git a/tools/aapt/tests/FileFinder_test.cpp b/tools/aapt/tests/FileFinder_test.cpp new file mode 100644 index 0000000..07bd665 --- /dev/null +++ b/tools/aapt/tests/FileFinder_test.cpp @@ -0,0 +1,101 @@ +// +// 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 diff --git a/tools/aapt/tests/MockCacheUpdater.h b/tools/aapt/tests/MockCacheUpdater.h new file mode 100644 index 0000000..c7f4bd7 --- /dev/null +++ b/tools/aapt/tests/MockCacheUpdater.h @@ -0,0 +1,40 @@ +// +// 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 diff --git a/tools/aapt/tests/MockDirectoryWalker.h b/tools/aapt/tests/MockDirectoryWalker.h new file mode 100644 index 0000000..5900cf3 --- /dev/null +++ b/tools/aapt/tests/MockDirectoryWalker.h @@ -0,0 +1,85 @@ +// +// 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 diff --git a/tools/aapt/tests/MockFileFinder.h b/tools/aapt/tests/MockFileFinder.h new file mode 100644 index 0000000..da5ea4f --- /dev/null +++ b/tools/aapt/tests/MockFileFinder.h @@ -0,0 +1,55 @@ +// +// 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 diff --git a/tools/aapt/tests/plurals/AndroidManifest.xml b/tools/aapt/tests/plurals/AndroidManifest.xml new file mode 100644 index 0000000..c721dee --- /dev/null +++ b/tools/aapt/tests/plurals/AndroidManifest.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.test.plurals"> + +</manifest> diff --git a/tools/aapt/tests/plurals/res/values/strings.xml b/tools/aapt/tests/plurals/res/values/strings.xml new file mode 100644 index 0000000..1c1fc19 --- /dev/null +++ b/tools/aapt/tests/plurals/res/values/strings.xml @@ -0,0 +1,7 @@ +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="ok">OK</string> + <plurals name="a_plural"> + <item quantity="one">A dog</item> + <item quantity="other">Some dogs</item> + </plurals> +</resources> diff --git a/tools/aapt/tests/plurals/run.sh b/tools/aapt/tests/plurals/run.sh new file mode 100755 index 0000000..4d39e10 --- /dev/null +++ b/tools/aapt/tests/plurals/run.sh @@ -0,0 +1,16 @@ +TEST_DIR=tools/aapt/tests/plurals +TEST_OUT_DIR=out/plurals_test + +rm -rf $TEST_OUT_DIR +mkdir -p $TEST_OUT_DIR +mkdir -p $TEST_OUT_DIR/java + +#gdb --args \ +aapt package -v -x -m -z -J $TEST_OUT_DIR/java -M $TEST_DIR/AndroidManifest.xml \ + -I out/target/common/obj/APPS/framework-res_intermediates/package-export.apk \ + -P $TEST_OUT_DIR/public_resources.xml \ + -S $TEST_DIR/res + +echo +echo "==================== FILES CREATED ==================== " +find $TEST_OUT_DIR -type f |