diff options
Diffstat (limited to 'tools/aapt')
-rw-r--r-- | tools/aapt/AaptAssets.cpp | 1717 | ||||
-rw-r--r-- | tools/aapt/AaptAssets.h | 519 | ||||
-rw-r--r-- | tools/aapt/Android.mk | 52 | ||||
-rw-r--r-- | tools/aapt/Bundle.h | 164 | ||||
-rw-r--r-- | tools/aapt/Command.cpp | 841 | ||||
-rw-r--r-- | tools/aapt/Images.cpp | 1082 | ||||
-rw-r--r-- | tools/aapt/Images.h | 18 | ||||
-rw-r--r-- | tools/aapt/Main.cpp | 369 | ||||
-rw-r--r-- | tools/aapt/Main.h | 41 | ||||
-rw-r--r-- | tools/aapt/Package.cpp | 464 | ||||
-rw-r--r-- | tools/aapt/Resource.cpp | 1524 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.cpp | 3491 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.h | 534 | ||||
-rw-r--r-- | tools/aapt/SourcePos.cpp | 171 | ||||
-rw-r--r-- | tools/aapt/SourcePos.h | 28 | ||||
-rw-r--r-- | tools/aapt/StringPool.cpp | 369 | ||||
-rw-r--r-- | tools/aapt/StringPool.h | 148 | ||||
-rw-r--r-- | tools/aapt/XMLNode.cpp | 1295 | ||||
-rw-r--r-- | tools/aapt/XMLNode.h | 184 | ||||
-rw-r--r-- | tools/aapt/printapk.cpp | 127 | ||||
-rw-r--r-- | tools/aapt/tests/plurals/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | tools/aapt/tests/plurals/res/values/strings.xml | 7 | ||||
-rwxr-xr-x | tools/aapt/tests/plurals/run.sh | 16 |
23 files changed, 13166 insertions, 0 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp new file mode 100644 index 0000000..6bc1ee6 --- /dev/null +++ b/tools/aapt/AaptAssets.cpp @@ -0,0 +1,1717 @@ +// +// Copyright 2006 The Android Open Source Project +// + +#include "AaptAssets.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* 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; +} + +static bool isHidden(const char *root, const char *path) +{ + const char *type = NULL; + + // Skip all hidden files. + if (path[0] == '.') { + // Skip ., .. and .svn but don't chatter about it. + if (strcmp(path, ".") == 0 + || strcmp(path, "..") == 0 + || strcmp(path, ".svn") == 0) { + return true; + } + type = "hidden"; + } else if (path[0] == '_') { + // skip directories starting with _ (don't chatter about it) + String8 subdirName(root); + subdirName.appendPath(path); + if (getFileType(subdirName.string()) == kFileTypeDirectory) { + return true; + } + } else if (strcmp(path, "CVS") == 0) { + // Skip CVS but don't chatter about it. + return true; + } else if (strcasecmp(path, "thumbs.db") == 0 + || strcasecmp(path, "picasa.ini") == 0) { + // Skip suspected image indexes files. + type = "index"; + } else if (path[strlen(path)-1] == '~') { + // Skip suspected emacs backup files. + type = "backup"; + } else { + // Let everything else through. + return false; + } + + /* If we get this far, "type" should be set and the file + * should be skipped. + */ + String8 subdirName(root); + subdirName.appendPath(path); + fprintf(stderr, " (skipping %s %s '%s')\n", type, + getFileType(subdirName.string())==kFileTypeDirectory ? "dir":"file", + subdirName.string()); + + return true; +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +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; + } + + // orientation + if (getOrientationName(part.string(), &config)) { + *axis = AXIS_ORIENTATION; + *value = config.orientation; + 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 + 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; +} + +bool +AaptGroupEntry::initFromDirName(const char* dir, String8* resType) +{ + Vector<String8> parts; + + String8 mcc, mnc, loc, orient, den, touch, key, keysHidden, nav, size, vers; + + 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()); + } + + // orientation + if (getOrientationName(part.string())) { + orient = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not orientation: %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()); + } + + 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->orientation = orient; + this->density = den; + this->touchscreen = touch; + this->keysHidden = keysHidden; + this->keyboard = key; + this->navigation = nav; + this->screenSize = size; + 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 += this->orientation; + s += ","; + s += density; + s += ","; + s += touchscreen; + s += ","; + s += keysHidden; + s += ","; + s += keyboard; + s += ","; + s += navigation; + s += ","; + s += screenSize; + s += ","; + s += version; + return s; +} + +String8 +AaptGroupEntry::toDirName(const String8& resType) const +{ + String8 s = resType; + if (this->mcc != "") { + s += "-"; + s += mcc; + } + if (this->mnc != "") { + s += "-"; + s += mnc; + } + if (this->locale != "") { + s += "-"; + s += locale; + } + if (this->orientation != "") { + s += "-"; + s += orientation; + } + if (this->density != "") { + s += "-"; + s += density; + } + if (this->touchscreen != "") { + s += "-"; + s += touchscreen; + } + if (this->keysHidden != "") { + s += "-"; + s += keysHidden; + } + if (this->keyboard != "") { + s += "-"; + s += keyboard; + } + if (this->navigation != "") { + s += "-"; + s += navigation; + } + if (this->screenSize != "") { + s += "-"; + s += screenSize; + } + if (this->version != "") { + 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; + + int d = atoi(val); + if (d != 0) { + if (out) out->mnc = d; + return true; + } + + return false; +} + +/* + * 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::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::getDensityName(const char* name, + ResTable_config* out) +{ + if (strcmp(name, kWildcardName) == 0) { + if (out) out->density = 0; + 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 = out->MASK_KEYSHIDDEN; + value = out->KEYSHIDDEN_ANY; + } else if (strcmp(name, "keysexposed") == 0) { + mask = out->MASK_KEYSHIDDEN; + value = out->KEYSHIDDEN_NO; + } else if (strcmp(name, "keyshidden") == 0) { + mask = out->MASK_KEYSHIDDEN; + value = out->KEYSHIDDEN_YES; + } else if (strcmp(name, "keyssoft") == 0) { + mask = out->MASK_KEYSHIDDEN; + value = out->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::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::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 = vendor.compare(o.vendor); + if (v == 0) v = orientation.compare(o.orientation); + 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 = navigation.compare(o.navigation); + if (v == 0) v = screenSize.compare(o.screenSize); + if (v == 0) v = version.compare(o.version); + return v; +} + +ResTable_config AaptGroupEntry::toParams() const +{ + ResTable_config params; + memset(¶ms, 0, sizeof(params)); + getMccName(mcc.string(), ¶ms); + getMncName(mnc.string(), ¶ms); + getLocaleName(locale.string(), ¶ms); + getOrientationName(orientation.string(), ¶ms); + getDensityName(density.string(), ¶ms); + getTouchscreenName(touchscreen.string(), ¶ms); + getKeysHiddenName(keysHidden.string(), ¶ms); + getKeyboardName(keyboard.string(), ¶ms); + getNavigationName(navigation.string(), ¶ms); + getScreenSizeName(screenSize.string(), ¶ms); + getVersionName(version.string(), ¶ms); + 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.locale.string()); + name.appendPath(mGroupEntry.vendor.string()); + 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; + } + + 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 +{ + printf(" %s\n", 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(" Gen: (%s) %d bytes\n", e.toString().string(), + (int)file->getSize()); + } else { + printf(" Src: %s\n", file->getPrintableSource().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::renameFile(const sp<AaptFile>& file, const String8& newName) +{ + sp<AaptGroup> origGroup; + + // Find and remove the given file with shear, brute force! + const size_t NG = mFiles.size(); + size_t i; + for (i=0; origGroup == NULL && i<NG; i++) { + sp<AaptGroup> g = mFiles.valueAt(i); + const size_t NF = g->getFiles().size(); + for (size_t j=0; j<NF; j++) { + if (g->getFiles().valueAt(j) == file) { + origGroup = g; + g->removeFile(j); + if (NF == 1) { + mFiles.removeItemsAt(i); + } + break; + } + } + } + + //printf("Renaming %s to %s\n", file->getPath().getPathName(), newName.string()); + + // Place the file under its new name. + if (origGroup != NULL) { + return addLeafFile(newName, file); + } + + return NO_ERROR; +} + +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) +{ + 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; + + fileNames.add(String8(entry->d_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); + 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 +{ + const size_t ND=getDirs().size(); + size_t i; + for (i=0; i<ND; i++) { + getDirs().valueAt(i)->print(); + } + + const size_t NF=getFiles().size(); + for (i=0; i<NF; i++) { + getFiles().valueAt(i)->print(); + } +} + +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; + +} + +// ========================================================================= +// ========================================================================= +// ========================================================================= + +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()); + 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; + } + 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()); + 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; + } + + +bail: + return totalCount; +} + +ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir, + const AaptGroupEntry& kind, + const String8& resType) +{ + ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType); + 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; + } + + FileType type = getFileType(subdirName.string()); + + if (type == kFileTypeDirectory) { + sp<AaptDir> dir = makeDir(String8(entry->d_name)); + ssize_t res = dir->slurpFullTree(bundle, subdirName, group, + resType); + if (res < 0) { + count = res; + goto bail; + } + if (res > 0) { + mGroupEntries.add(group); + count += res; + } + + mDirs.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; +} + +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; +} + +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 +{ + printf("Locale/Vendor pairs:\n"); + const size_t N=mGroupEntries.size(); + for (size_t i=0; i<N; i++) { + printf(" %s/%s\n", + mGroupEntries.itemAt(i).locale.string(), + mGroupEntries.itemAt(i).vendor.string()); + } + + printf("\nFiles:\n"); + AaptDir::print(); +} + +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..01c8140 --- /dev/null +++ b/tools/aapt/AaptAssets.h @@ -0,0 +1,519 @@ +// +// 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 <utils/AssetManager.h> +#include <utils/KeyedVector.h> +#include <utils/String8.h> +#include <utils/ResourceTypes.h> +#include <utils/SortedVector.h> +#include <utils/String8.h> +#include <utils/Vector.h> +#include <utils/RefBase.h> +#include <utils/ZipFile.h> + +#include "Bundle.h" +#include "SourcePos.h" + +using namespace android; + +bool valid_symbol_name(const String8& str); + +enum { + AXIS_NONE = 0, + AXIS_MCC = 1, + AXIS_MNC, + AXIS_LANGUAGE, + AXIS_REGION, + AXIS_ORIENTATION, + AXIS_DENSITY, + AXIS_TOUCHSCREEN, + AXIS_KEYSHIDDEN, + AXIS_KEYBOARD, + AXIS_NAVIGATION, + AXIS_SCREENSIZE, + 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() { } + AaptGroupEntry(const String8& _locale, const String8& _vendor) + : locale(_locale), vendor(_vendor) { } + + String8 mcc; + String8 mnc; + String8 locale; + String8 vendor; + String8 orientation; + String8 density; + String8 touchscreen; + String8 keysHidden; + String8 keyboard; + String8 navigation; + String8 screenSize; + String8 version; + + bool initFromDirName(const char* dir, String8* resType); + + static status_t parseNamePart(const String8& part, int* axis, uint32_t* value); + + 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 getOrientationName(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 getScreenSizeName(const char* name, ResTable_config* out = NULL); + static bool getVersionName(const char* name, ResTable_config* out = NULL); + + int compare(const AaptGroupEntry& o) 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; +}; + +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; + +/** + * 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() { } + + 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 getPrintableSource() const; + +private: + String8 mLeaf; + String8 mPath; + + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles; +}; + +/** + * A single directory of assets, which can contain for 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; } + + status_t addFile(const String8& name, const sp<AaptGroup>& file); + status_t addDir(const String8& name, const sp<AaptDir>& dir); + + sp<AaptDir> makeDir(const String8& name); + + void removeFile(const String8& name); + void removeDir(const String8& name); + + status_t renameFile(const sp<AaptFile>& file, const String8& newName); + + 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); + + /* + * 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 getPrintableSource() const; + +private: + 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), typeCode(TYPE_UNKNOWN) + { + } + AaptSymbolEntry(const String8& _name) + : name(_name), isPublic(false), typeCode(TYPE_UNKNOWN) + { + } + AaptSymbolEntry(const AaptSymbolEntry& o) + : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic) + , 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; + comment = o.comment; + typeComment = o.typeComment; + typeCode = o.typeCode; + int32Val = o.int32Val; + stringVal = o.stringVal; + return *this; + } + + const String8 name; + + SourcePos sourcePos; + bool isPublic; + + 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; + } + + 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; + } + + 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; + +/** + * Asset hierarchy being operated on. + */ +class AaptAssets : public AaptDir +{ +public: + AaptAssets() : AaptDir(String8(), String8()), mHaveIncludedAssets(false) { } + virtual ~AaptAssets() { } + + const String8& getPackage() const { return mPackage; } + void setPackage(const String8& package) { mPackage = package; mSymbolsPrivatePackage = package; } + + const SortedVector<AaptGroupEntry>& getGroupEntries() const { return mGroupEntries; } + + 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); + + ssize_t slurpFromArgs(Bundle* bundle); + + virtual ssize_t slurpFullTree(Bundle* bundle, + const String8& srcDir, + const AaptGroupEntry& kind, + const String8& resType); + + ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir); + ssize_t slurpResourceZip(Bundle* bundle, const char* filename); + + sp<AaptSymbols> getSymbolsFor(const String8& name); + + const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; } + + String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; } + void setSymbolsPrivatePackage(const String8& pkg) { mSymbolsPrivatePackage = pkg; } + + status_t buildIncludedResources(Bundle* bundle); + status_t addIncludedResources(const sp<AaptFile>& file); + const ResTable& getIncludedResources() const; + + void print() const; + + inline const Vector<sp<AaptDir> >& resDirs() { return mDirs; } + + 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) { mRes = res; } + +private: + String8 mPackage; + SortedVector<AaptGroupEntry> mGroupEntries; + DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols; + String8 mSymbolsPrivatePackage; + + Vector<sp<AaptDir> > mDirs; + + bool mHaveIncludedAssets; + AssetManager mIncludedAssets; + + sp<AaptAssets> mOverlay; + KeyedVector<String8, sp<ResourceTypeSet> >* mRes; +}; + +#endif // __AAPT_ASSETS_H + diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk new file mode 100644 index 0000000..fdc859c --- /dev/null +++ b/tools/aapt/Android.mk @@ -0,0 +1,52 @@ +# +# Copyright 2006 The Android Open Source Project +# +# Android Asset Packaging Tool +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + AaptAssets.cpp \ + Command.cpp \ + Main.cpp \ + Package.cpp \ + StringPool.cpp \ + XMLNode.cpp \ + ResourceTable.cpp \ + Images.cpp \ + Resource.cpp \ + SourcePos.cpp + +LOCAL_CFLAGS += -Wno-format-y2k + +LOCAL_C_INCLUDES += external/expat/lib +LOCAL_C_INCLUDES += external/libpng +LOCAL_C_INCLUDES += external/zlib +LOCAL_C_INCLUDES += build/libs/host/include + +#LOCAL_WHOLE_STATIC_LIBRARIES := +LOCAL_STATIC_LIBRARIES := \ + libhost \ + libutils \ + libcutils \ + libexpat \ + libpng + +LOCAL_LDLIBS := -lz + +ifeq ($(HOST_OS),linux) +LOCAL_LDLIBS += -lrt +endif + +ifeq ($(HOST_OS),windows) +ifeq ($(strip $(USE_CYGWIN),),) +LOCAL_LDLIBS += -lws2_32 +endif +endif + +LOCAL_MODULE := aapt + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h new file mode 100644 index 0000000..2d8471b --- /dev/null +++ b/tools/aapt/Bundle.h @@ -0,0 +1,164 @@ +// +// 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.h> // android +#include <utils/String8.h> +#include <utils/Vector.h> + +/* + * Things we can do. + */ +typedef enum Command { + kCommandUnknown = 0, + kCommandVersion, + kCommandList, + kCommandDump, + kCommandAdd, + kCommandRemove, + kCommandPackage, +} 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), + mCompressionMethod(0), mOutputAPKFile(NULL), + mAssetSourceDir(NULL), + mAndroidManifestFile(NULL), mPublicOutputFile(NULL), + mRClassDir(NULL), mResourceIntermediatesDir(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() { 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; } + int getCompressionMethod(void) const { return mCompressionMethod; } + void setCompressionMethod(int val) { mCompressionMethod = val; } + const char* getOutputAPKFile() const { return mOutputAPKFile; } + void setOutputAPKFile(const char* val) { mOutputAPKFile = val; } + + /* + * Input options. + */ + const char* getAssetSourceDir() const { return mAssetSourceDir; } + void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; } + 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* 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); } + + /* + * 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 + +private: + /* commands & modifiers */ + Command mCmd; + bool mVerbose; + bool mAndroidList; + bool mForce; + int mGrayscaleTolerance; + bool mMakePackageDirs; + bool mUpdate; + bool mExtending; + bool mRequireLocalization; + bool mPseudolocalize; + int mCompressionMethod; + const char* mOutputAPKFile; + const char* mAssetSourceDir; + const char* mAndroidManifestFile; + const char* mPublicOutputFile; + const char* mRClassDir; + const char* mResourceIntermediatesDir; + android::String8 mConfigurations; + android::Vector<const char*> mPackageIncludes; + android::Vector<const char*> mJarFiles; + android::Vector<const char*> mNoCompressExtensions; + android::Vector<const char*> mResourceSourceDirs; + + /* file specification */ + int mArgc; + char* const* mArgv; + +#if 0 + /* misc stuff */ + int mPackageCount; +#endif +}; + +#endif // __BUNDLE_H diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp new file mode 100644 index 0000000..bff0423 --- /dev/null +++ b/tools/aapt/Command.cpp @@ -0,0 +1,841 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Android Asset Packaging Tool main entry point. +// +#include "Main.h" +#include "Bundle.h" +#include "ResourceTable.h" +#include "XMLNode.h" + +#include <utils.h> +#include <utils/ZipFile.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 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%% %s %08lx %s\n", + (long) entry->getUncompressedLen(), + compressionName(entry->getCompressionMethod()), + (long) entry->getCompressedLen(), + calcPercent(entry->getUncompressedLen(), + entry->getCompressedLen()), + 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 { + printf("\nResource table:\n"); + res.print(); + } + + 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; +} + +static 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) +{ + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return -1; + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType != Res_value::TYPE_INT_DEC) { + if (outError != NULL) *outError = "attribute is not an integer value"; + return -1; + } + } + 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(); +} + +// These are attribute resource constants for the platform, as found +// in android.R.attr +enum { + NAME_ATTR = 0x01010003, + VERSION_CODE_ATTR = 0x0101021b, + VERSION_NAME_ATTR = 0x0101021c, + LABEL_ATTR = 0x01010001, + ICON_ATTR = 0x01010002, +}; + +/* + * Handle the "dump" command, to extract select data from an archive. + */ +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; + if (!assets.addAssetPath(String8(filename), NULL)) { + fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n"); + return 1; + } + + 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) { + res.print(); + + } 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 %p 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); + 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 %p 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()); + } + } + } else if (strcmp("badging", option) == 0) { + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + String8 error; + bool withinActivity = false; + bool isMainActivity = false; + bool isLauncherActivity = false; + String8 activityName; + String8 activityLabel; + String8 activityIcon; + 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: 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 = getAttribute(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 && tag == "application") { + String8 label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string()); + goto bail; + } + printf("application: label='%s' ", label.string()); + + String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); + goto bail; + } + printf("icon='%s'\n", icon.string()); + } else if (depth == 3 && tag == "activity") { + withinActivity = true; + //printf("LOG: withinActivity==true\n"); + + 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; + } + } else if (depth == 5 && withinActivity) { + if (tag == "action") { + //printf("LOG: action tag\n"); + String8 action = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); + goto bail; + } + if (action == "android.intent.action.MAIN") { + isMainActivity = true; + //printf("LOG: isMainActivity==true\n"); + } + } else 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 (category == "android.intent.category.LAUNCHER") { + isLauncherActivity = true; + //printf("LOG: isLauncherActivity==true\n"); + } + } + } + + if (depth < 3) { + //if (withinActivity) printf("LOG: withinActivity==false\n"); + withinActivity = false; + } + + if (depth < 5) { + //if (isMainActivity) printf("LOG: isMainActivity==false\n"); + //if (isLauncherActivity) printf("LOG: isLauncherActivity==false\n"); + isMainActivity = false; + isLauncherActivity = false; + } + + if (withinActivity && isMainActivity && isLauncherActivity) { + printf("launchable activity: name='%s' label='%s' icon='%s'\n", + activityName.string(), activityLabel.string(), + activityIcon.string()); + } + } + printf("locales:"); + Vector<String8> locales; + res.getLocales(&locales); + const size_t N = locales.size(); + for (size_t i=0; i<N; i++) { + const char* localeStr = locales[i].string(); + if (localeStr == NULL || strlen(localeStr) == 0) { + localeStr = "--_--"; + } + printf(" '%s'", localeStr); + } + printf("\n"); + } 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 { + 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; + + // -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(); + err = assets->slurpFromArgs(bundle); + if (err < 0) { + goto bail; + } + + if (bundle->getVerbose()) { + assets->print(); + } + + // If they asked for any files 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; + } + + // Write out R.java constants + if (assets->getPackage() == assets->getSymbolsPrivatePackage()) { + err = writeResourceSymbols(bundle, assets, assets->getPackage(), true); + if (err < 0) { + goto bail; + } + } 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 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; + } + } + + retVal = 0; +bail: + if (SourcePos::hasErrors()) { + SourcePos::printErrors(stderr); + } + return retVal; +} diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp new file mode 100644 index 0000000..edb12ea --- /dev/null +++ b/tools/aapt/Images.cpp @@ -0,0 +1,1082 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#define PNG_INTERNAL + +#include "Images.h" + +#include <utils/ResourceTypes.h> +#include <utils/ByteOrder.h> + +#include <png.h> + +#define NOISY(x) //x + +static void +png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length) +{ + status_t err = ((AaptFile*)png_ptr->io_ptr)->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); + } + } + + png_uint_32 width; + png_uint_32 height; + png_bytepp rows; + + // 9-patch info. + bool is9Patch; + Res_png_9patch info9Patch; + + 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_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 * png_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); +} + +static bool is_tick(png_bytep p, bool transparent, const char** outError) +{ + if (transparent) { + if (p[3] == 0) { + return false; + } + if (p[3] != 0xff) { + *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)"; + return false; + } + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + *outError = "Ticks in transparent frame must be black"; + } + return true; + } + + if (p[3] != 0xFF) { + *outError = "White frame must be a solid color (no alpha)"; + } + if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) { + return false; + } + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + *outError = "Ticks in white frame must be black"; + return false; + } + return true; +} + +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 (is_tick(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 (is_tick(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 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 / 2 + 1) * sizeof(int32_t); + int maxSizeYDivs = (H / 2 + 1) * 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; + + png_bytep p = image->rows[0]; + bool transparent = p[3] == 0; + bool hasColor = false; + + const char* errorMsg = NULL; + int errorPixel = -1; + const char* errorEdge = ""; + + 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; + } + + // 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) * png_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--; + } + 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 (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[1]; + + png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_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) { + NOISY(printf("Adding 9-patch info...\n")); + strcpy((char*)unknowns[0].name, "npTc"); + unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize(); + unknowns[0].size = imageInfo.info9Patch.serializedSize(); + // TODO: remove the check below when everything works + checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data); + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, + (png_byte*)"npTc", 1); + png_set_unknown_chunks(write_ptr, write_info, unknowns, 1); + // XXX I can't get this to work without forcibly changing + // the location to what I want... which apparently is supposed + // to be a private API, but everything else I have tried results + // in the location being set to what I -last- wrote so I never + // get written. :p + png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE); + } + + png_write_info(write_ptr, write_info); + + png_bytepp rows; + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + 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); + + 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(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()); + + 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 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..168e22f --- /dev/null +++ b/tools/aapt/Images.h @@ -0,0 +1,18 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#ifndef IMAGES_H +#define IMAGES_H + +#include "ResourceTable.h" + +status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets, + const sp<AaptFile>& file, String8* outNewLeafName); + +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..71b1a3c --- /dev/null +++ b/tools/aapt/Main.cpp @@ -0,0 +1,369 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Android Asset Packaging Tool main entry point. +// +#include "Main.h" +#include "Bundle.h" + +#include <utils.h> +#include <utils/ZipFile.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] WHAT file.{apk} [asset [asset ...]]\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 ...]] \\\n" + " [-g tolerance] \\\n" + " [-j jarfile] \\\n" + " [-I base-package [-I base-package ...]] \\\n" + " [-A asset-source-dir] [-P public-definitions-file] \\\n" + " [-S resource-sources [-S resource-sources ...]] " + " [-F apk-file] [-J R-file-dir] \\\n" + " [raw-files-dir [raw-files-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 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" + " -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" + " -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" + " and the first match found (left to right) will take precedence." + " -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"); +} + +/* + * 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); + 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 { + 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 '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 '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 '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; + 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..65c0a8a --- /dev/null +++ b/tools/aapt/Main.h @@ -0,0 +1,41 @@ +// +// 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.h> +#include "Bundle.h" +#include "AaptAssets.h" +#include <utils/ZipFile.h> + +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 calcPercent(long uncompressedLen, long compressedLen); + +extern android::status_t writeAPK(Bundle* bundle, + const sp<AaptAssets>& assets, + const android::String8& outputFile); + +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 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); + +#endif // __MAIN_H diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp new file mode 100644 index 0000000..eb7d6f5 --- /dev/null +++ b/tools/aapt/Package.cpp @@ -0,0 +1,464 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Package assets into Zip files. +// +#include "Main.h" +#include "AaptAssets.h" +#include "ResourceTable.h" + +#include <utils.h> +#include <utils/ZipFile.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); +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) +{ + 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()); + } + } + + 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"); + 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]; + if (!filter.match(ge.toParams())) { + continue; + } + ssize_t res = processAssets(bundle, zip, assets, ge); + if (res < 0) { + return res; + } + count += res; + } + + return count; +} + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, + const sp<AaptDir>& dir, const AaptGroupEntry& ge) +{ + ssize_t count = 0; + + const size_t ND = dir->getDirs().size(); + size_t i; + for (i=0; i<ND; i++) { + ssize_t res = processAssets(bundle, zip, dir->getDirs().valueAt(i), ge); + if (res < 0) { + return res; + } + count += res; + } + + 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) +{ + ssize_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: %zd\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..b2bd9ff --- /dev/null +++ b/tools/aapt/Resource.cpp @@ -0,0 +1,1524 @@ +// +// 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" + +#define NOISY(x) // x + +// ========================================================================== +// ========================================================================== +// ========================================================================== + +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); + } +} + +class ResourceTypeSet : public RefBase, + public KeyedVector<String8,sp<AaptGroup> > +{ +public: + ResourceTypeSet(); +}; + +ResourceTypeSet::ResourceTypeSet() + :RefBase(), + KeyedVector<String8,sp<AaptGroup> >() +{ +} + +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 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.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 == "drawable" || type == "layout" + || type == "values" || type == "xml" || type == "raw" + || type == "color" || type == "menu"; +} + +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(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))); + + 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; +} + +static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets, + const sp<ResourceTypeSet>& set) +{ + ResourceDirIterator it(set, String8("drawable")); + Vector<sp<AaptFile> > newNameFiles; + Vector<String8> newNamePaths; + ssize_t res; + while ((res=it.next()) == NO_ERROR) { + res = preProcessImage(bundle, assets, it.getFile(), NULL); + if (res != NO_ERROR) { + return res; + } + } + + return NO_ERROR; +} + +status_t postProcessImages(const sp<AaptAssets>& assets, + ResourceTable* table, + const sp<ResourceTypeSet>& set) +{ + ResourceDirIterator it(set, String8("drawable")); + ssize_t res; + while ((res=it.next()) == NO_ERROR) { + res = postProcessImage(assets, table, it.getFile()); + if (res != NO_ERROR) { + return res; + } + } + + return res < NO_ERROR ? res : (status_t)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(); + set->add(leafName, group); + resources->add(resType, set); + } else { + sp<ResourceTypeSet> set = resources->valueAt(index); + index = set->indexOfKey(leafName); + if (index < 0) { + set->add(leafName, group); + } else { + sp<AaptGroup> existingGroup = set->valueAt(index); + int M = files.size(); + for (int j=0; j<M; j++) { + 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); + collect_files(d, resources); + + // don't try to include the res dir + 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 ResXMLParser& parser, + const char* ns, const char* attr, const char* validChars, bool required) +{ + size_t len; + + ssize_t index = parser.indexOfAttribute(ns, attr); + const uint16_t* str; + if (index >= 0 && (str=parser.getAttributeStringValue(index, &len)) != NULL) { + 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 void applyFileOverlay(const sp<AaptAssets>& assets, + const sp<ResourceTypeSet>& baseSet, + const char *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++) { + size_t 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> > baseFiles = + baseGroup->getFiles(); + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles = + overlayGroup->getFiles(); + size_t overlayGroupSize = overlayFiles.size(); + for (size_t overlayGroupIndex = 0; + overlayGroupIndex<overlayGroupSize; + overlayGroupIndex++) { + size_t baseFileIndex = + baseFiles.indexOfKey(overlayFiles.keyAt(overlayGroupIndex)); + if(baseFileIndex < UNKNOWN_ERROR) { + baseGroup->removeFile(baseFileIndex); + } else { + // didn't find a match fall through and add it.. + } + baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex)); + } + } else { + // this group doesn't exist (a file that's only in the overlay) + // add it + baseSet->add(overlaySet->keyAt(overlayIndex), + overlaySet->valueAt(overlayIndex)); + } + } + // this overlay didn't have resources for this type + } + // try next overlay + overlay = overlay->getOverlay(); + } + return; +} + +#define ASSIGN_IT(n) \ + do { \ + ssize_t index = resources->indexOfKey(String8(#n)); \ + if (index >= 0) { \ + n ## s = resources->valueAt(index); \ + } \ + } while (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(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())); + + // -------------------------------------------------------------- + // 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> xmls; + sp<ResourceTypeSet> raws; + sp<ResourceTypeSet> colors; + sp<ResourceTypeSet> menus; + + ASSIGN_IT(drawable); + ASSIGN_IT(layout); + ASSIGN_IT(anim); + ASSIGN_IT(xml); + ASSIGN_IT(raw); + ASSIGN_IT(color); + ASSIGN_IT(menu); + + 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 + applyFileOverlay(assets, drawables, "drawable"); + applyFileOverlay(assets, layouts, "layout"); + applyFileOverlay(assets, anims, "anim"); + applyFileOverlay(assets, xmls, "xml"); + applyFileOverlay(assets, raws, "raw"); + applyFileOverlay(assets, colors, "color"); + applyFileOverlay(assets, menus, "menu"); + + bool hasErrors = false; + + if (drawables != NULL) { + err = preProcessImages(bundle, assets, drawables); + if (err == NO_ERROR) { + err = makeFileResources(bundle, assets, &table, drawables, "drawable"); + 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 (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); + 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); + 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); + 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); + 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); + 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; + } + + const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0)); + String8 manifestPath(manifestFile->getPrintableSource()); + + // Perform a basic validation of the manifest file. This time we + // parse it with the comments intact, so that we can use them to + // generate java docs... so we are not going to write this one + // back out to the final manifest data. + err = compileXmlFile(assets, manifestFile, &table, + XML_COMPILE_ASSIGN_ATTRIBUTE_IDS + | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES); + if (err < NO_ERROR) { + return err; + } + ResXMLTree block; + block.setTo(manifestFile->getData(), manifestFile->getSize(), true); + 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, block, NULL, "package", + packageIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0 + || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) { + const bool isGroup = strcmp16(block.getElementName(&len), + permission_group16.string()) == 0; + if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", + isGroup ? packageIdentCharsWithTheStupid + : 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, block, RESOURCES_ANDROID_NAMESPACE, "name", + packageIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) { + if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", + classIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "targetPackage", + packageIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) { + if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", + classIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "permission", + packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "process", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "taskAffinity", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) { + if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name", + classIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "authorities", + authoritiesIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "permission", + packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, 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, block, RESOURCES_ANDROID_NAMESPACE, "name", + classIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "permission", + packageIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "process", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "taskAffinity", + processIdentChars, false) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), action16.string()) == 0 + || strcmp16(block.getElementName(&len), category16.string()) == 0) { + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "name", + packageIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) { + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "mimeType", + typeIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + if (validateAttr(manifestPath, block, + RESOURCES_ANDROID_NAMESPACE, "scheme", + schemeIdentChars, true) != ATTR_OKAY) { + hasErrors = true; + } + } + } + } + + if (table.validateLocalizations()) { + hasErrors = true; + } + + if (hasErrors) { + return UNKNOWN_ERROR; + } + + // Generate final compiled manifest file. + manifestFile->clearData(); + err = compileXmlFile(assets, manifestFile, &table); + if (err < NO_ERROR) { + return err; + } + + //block.restart(); + //printXMLBlock(&block); + + // -------------------------------------------------------------- + // Generate the final resource table. + // Re-flatten because we may have added new resource IDs + // -------------------------------------------------------------- + + if (table.hasResources()) { + sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); + err = table.addSymbols(symbols); + if (err < NO_ERROR) { + return err; + } + + sp<AaptFile> 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); + } + + NOISY( + ResTable rt; + rt.add(resFile->getData(), resFile->getSize(), NULL); + printf("Generated resources:\n"); + rt.print(); + ) + + // 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 status_t fixupSymbol(String16* inoutSymbol) +{ + inoutSymbol->replaceAll('.', '_'); + inoutSymbol->replaceAll(':', '_'); + return NO_ERROR; +} + +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); + String16 nclassName16(symbols->getNestedSymbols().keyAt(i)); + String8 realClassName(nclassName16); + if (fixupSymbol(&nclassName16) != NO_ERROR) { + hasErrors = true; + } + String8 nclassName(nclassName16); + + 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(); + + String16 comment = symbols->getComment(realClassName); + fprintf(fp, "%s/** ", indentStr); + if (comment.size() > 0) { + fprintf(fp, "%s\n", String8(comment).string()); + } 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 border=\"2\" width=\"85%%\" align=\"center\" frame=\"hsides\" rules=\"all\" cellpadding=\"5\">\n" + "%s <colgroup align=\"left\" />\n" + "%s <colgroup align=\"left\" />\n" + "%s <tr><th>Attribute<th>Summary</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()); + } + String16 name(name8); + fixupSymbol(&name); + fprintf(fp, "%s <tr><th><code>{@link #%s_%s %s:%s}</code><td>%s</tr>\n", + indentStr, nclassName.string(), + String8(name).string(), + assets->getPackage().string(), + String8(name).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; + } + String16 name(sym.name); + fixupSymbol(&name); + fprintf(fp, "%s @see #%s_%s\n", + indentStr, nclassName.string(), + String8(name).string()); + } + } + fprintf(fp, "%s */\n", getIndentSpace(indent)); + + 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); + } + String16 name(name8); + if (fixupSymbol(&name) != NO_ERROR) { + hasErrors = true; + } + + 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, "%s/**\n", indentStr); + if (comment.size() > 0) { + fprintf(fp, "%s <p>\n%s @attr description\n", indentStr, indentStr); + fprintf(fp, "%s %s\n", indentStr, String8(comment).string()); + } 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, + pub ? assets->getPackage().string() + : assets->getSymbolsPrivatePackage().string(), + String8(name).string(), + indentStr, nclassName.string()); + } + if (typeComment.size() > 0) { + fprintf(fp, "\n\n%s %s\n", indentStr, String8(typeComment).string()); + } + if (comment.size() > 0) { + if (pub) { + fprintf(fp, + "%s <p>This corresponds to the global attribute" + "%s resource symbol {@link %s.R.attr#%s}.\n", + indentStr, indentStr, + assets->getPackage().string(), + String8(name).string()); + } else { + fprintf(fp, + "%s <p>This is a private symbol.\n", indentStr); + } + } + fprintf(fp, "%s @attr name %s:%s\n", indentStr, + "android", String8(name).string()); + fprintf(fp, "%s*/\n", indentStr); + fprintf(fp, + "%spublic static final int %s_%s = %d;\n", + indentStr, nclassName.string(), + String8(name).string(), (int)pos); + } + } + } + + indent--; + fprintf(fp, "%s};\n", getIndentSpace(indent)); + 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) +{ + fprintf(fp, "%spublic %sfinal class %s {\n", + getIndentSpace(indent), + indent != 0 ? "static " : "", className.string()); + indent++; + + 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 (!includePrivate && !sym.isPublic) { + continue; + } + String16 name(sym.name); + String8 realName(name); + if (fixupSymbol(&name) != NO_ERROR) { + return UNKNOWN_ERROR; + } + String16 comment(sym.comment); + bool haveComment = false; + if (comment.size() > 0) { + haveComment = true; + fprintf(fp, + "%s/** %s\n", + getIndentSpace(indent), String8(comment).string()); + } 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) { + if (!haveComment) { + haveComment = true; + fprintf(fp, + "%s/** %s\n", + getIndentSpace(indent), String8(typeComment).string()); + } else { + fprintf(fp, + "%s %s\n", + getIndentSpace(indent), String8(typeComment).string()); + } + } + if (haveComment) { + fprintf(fp,"%s */\n", getIndentSpace(indent)); + } + fprintf(fp, "%spublic static final int %s=0x%08x;\n", + getIndentSpace(indent), + String8(name).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 (!includePrivate && !sym.isPublic) { + continue; + } + String16 name(sym.name); + if (fixupSymbol(&name) != NO_ERROR) { + return UNKNOWN_ERROR; + } + String16 comment(sym.comment); + if (comment.size() > 0) { + fprintf(fp, + "%s/** %s\n" + "%s */\n", + getIndentSpace(indent), String8(comment).string(), + getIndentSpace(indent)); + } 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()); + } + fprintf(fp, "%spublic static final String %s=\"%s\";\n", + getIndentSpace(indent), + String8(name).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); + } + 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; +} + +status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, + const String8& package, bool includePrivate) +{ + if (!bundle->getRClassDir()) { + return NO_ERROR; + } + + 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); + if (err != NO_ERROR) { + return err; + } + fclose(fp); + } + + return NO_ERROR; +} diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp new file mode 100644 index 0000000..6f71a1e --- /dev/null +++ b/tools/aapt/ResourceTable.cpp @@ -0,0 +1,3491 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "ResourceTable.h" + +#include "XMLNode.h" + +#include <utils/ByteOrder.h> +#include <utils/ResourceTypes.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; + } + if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) { + root->removeWhitespace(true, NULL); + } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) { + root->removeWhitespace(false, NULL); + } + + 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[10]; + 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 border=\"2\" width=\"85%\" align=\"center\" frame=\"hsides\" rules=\"all\" cellpadding=\"5\">\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<colgroup align=\"left\" />\n" + "<tr><th>Constant<th>Value<th>Description</tr>")); + } + + enumOrFlagsComment.append(String16("\n<tr><th><code>")); + enumOrFlagsComment.append(itemIdent); + enumOrFlagsComment.append(String16("</code><td>")); + enumOrFlagsComment.append(value); + enumOrFlagsComment.append(String16("<td>")); + if (block.getComment(&len)) { + enumOrFlagsComment.append(String16(block.getComment(&len))); + } + enumOrFlagsComment.append(String16("</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 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, + 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; +} + + +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 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, + pseudolocalize); + + if (err < NO_ERROR) { + return err; + } + + 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 private_symbols16("private-symbols"); + 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 false16("false"); + + const String16 myPackage(assets->getPackage()); + + bool hasErrors = false; + + 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 curIsStyled = false; + bool curIsPseudolocalizable = false; + 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 = ident+1; + } + } else if (nextPublicId == 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; + nextPublicId++; + } + + 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), 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), 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; + + 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)); + } + } + + if (name.size() > 0) { + if (translatable == false16) { + // 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); + } + } + + 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; + 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) { + curTag = &string_array16; + curType = array16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; + curIsBag = 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; + } 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 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); + 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, + 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, 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, 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, true, false, 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 resource 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, 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 replace, bool isId) +{ + // 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, params); + if (e == NULL) { + return UNKNOWN_ERROR; + } + + // If a parent is explicitly specified, set it. + if (bagParent.size() > 0) { + String16 curPar = e->getParent(); + if (curPar.size() > 0 && curPar != bagParent) { + sourcePos.error("Conflicting parents specified, was '%s', now '%s'\n", + String8(e->getParent()).string(), + String8(bagParent).string()); + return UNKNOWN_ERROR; + } + e->setParent(bagParent); + } + + return e->makeItABag(sourcePos); +} + +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, params); + if (e == NULL) { + return UNKNOWN_ERROR; + } + + // If a parent is explicitly specified, set it. + if (bagParent.size() > 0) { + String16 curPar = e->getParent(); + if (curPar.size() > 0 && curPar != bagParent) { + sourcePos.error("Conflicting parents specified, was '%s', now '%s'\n", + String8(e->getParent()).string(), + String8(bagParent).string()); + return UNKNOWN_ERROR; + } + 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& 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; +} + +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 +{ + 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 rid; + } + return 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 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; + if (!ResTable::expandResourceRef( + ref.string(), ref.size(), &package, &type, &name, + defType, defPackage ? defPackage:&mAssetsPackage, + outErrorMsg)) { + 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); + 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) +{ + 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) { + if (style != NULL && style->size() > 0) { + outValue->data = pool->add(finalStr, *style); + } else { + outValue->data = pool->add(finalStr, true); + } + } 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; + bool failed = false; + while (pos < end && !failed) { + const char16_t* start = pos; + end++; + 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; + } + if (pos < end) { + 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>::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: error: " + "*** string '%s' has no default or required localization " + "for '%s' in %s\n", + String8(nameIter->first).string(), + config.string(), + mBundle->getResourceSourceDirs()[0]); + err = UNKNOWN_ERROR; + } + } + } + } + } while (comma != NULL); + } + } + + return err; +} + + +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::match(int axis, uint32_t value) +{ + 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(const ResTable_config& config) +{ + if (config.locale) { + uint32_t locale = (config.country[1] << 24) | (config.country[0] << 16) + | (config.language[1] << 8) | (config.language[0]); + if (!match(AXIS_LANGUAGE, locale)) { + return false; + } + } + if (!match(AXIS_ORIENTATION, config.orientation)) { + return false; + } + if (!match(AXIS_DENSITY, config.density)) { + return false; + } + if (!match(AXIS_TOUCHSCREEN, config.touchscreen)) { + return false; + } + if (!match(AXIS_KEYSHIDDEN, config.inputFlags)) { + return false; + } + if (!match(AXIS_KEYBOARD, config.keyboard)) { + return false; + } + if (!match(AXIS_NAVIGATION, config.navigation)) { + return false; + } + if (!match(AXIS_SCREENSIZE, config.screenSize)) { + return false; + } + if (!match(AXIS_VERSION, config.version)) { + return false; + } + return true; +} + +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 size_t N = mOrderedPackages.size(); + size_t pi; + + // Iterate through all data, collecting all values (strings, + // references, etc). + StringPool valueStrings; + for (pi=0; pi<N; pi++) { + sp<Package> p = mOrderedPackages.itemAt(pi); + if (p->getTypes().size() == 0) { + // Empty, skip! + continue; + } + + StringPool typeStrings; + StringPool keyStrings; + + 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; + } + typeStrings.add(t->getName(), false); + + 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 (!filter.match(config)) { + continue; + } + sp<Entry> e = c->getEntries().valueAt(ei); + if (e == NULL) { + continue; + } + e->setNameIndex(keyStrings.add(e->getName(), true)); + status_t err = e->prepareFlatten(&valueStrings, this); + if (err != NO_ERROR) { + return err; + } + } + } + } + + p->setTypeStrings(typeStrings.createStringBlock()); + p->setKeyStrings(keyStrings.createStringBlock()); + } + + 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 size_t N = t != NULL ? t->getOrderedConfigs().size() : 0; + + // 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 (!filter.match(cl->getEntries().keyAt(ci))) { + continue; + } + for (size_t cj=ci+1; cj<CN; cj++) { + if (!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 touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%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.touchscreen, + config.density, + config.keyboard, + config.inputFlags, + config.navigation, + config.screenWidth, + config.screenHeight)); + + if (!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 touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%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.touchscreen, + tHeader->config.density, + tHeader->config.keyboard, + tHeader->config.inputFlags, + tHeader->config.navigation, + tHeader->config.screenWidth, + tHeader->config.screenHeight)); + 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; + } + } 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); + } + } + + // 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::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) +{ + 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)) { + 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)) { + 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; +} + +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; +} + +sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry, + const SourcePos& sourcePos, + const ResTable_config* config, + bool doSetIndex) +{ + int pos = -1; + sp<ConfigList> c = mConfigs.valueFor(entry); + if (c == 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 w:%d h:%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)); + } 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, + 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); +} + +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..e8fbd9b --- /dev/null +++ b/tools/aapt/ResourceTable.h @@ -0,0 +1,534 @@ +// +// 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 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_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 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; + + 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 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& 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); + + 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 = false) const; + + uint32_t getResId(const String16& ref, + const String16* defType = NULL, + const String16* defPackage = NULL, + const char** outErrorMsg = NULL, + bool onlyPublic = false) 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); + + 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 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); + + 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; + }; + + 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; } + }; + + 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); + + String16 getName() const { return mName; } + sp<Entry> getEntry(const String16& entry, + const SourcePos& pos, + const ResTable_config* config = NULL, + bool doSetIndex = 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 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; + 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, + 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; +}; + +class ResourceFilter +{ +public: + ResourceFilter() : mData(), mContainsPseudo(false) {} + status_t parse(const char* arg); + bool match(int axis, uint32_t value); + bool match(const ResTable_config& config); + inline bool containsPseudo() { return mContainsPseudo; } + +private: + KeyedVector<int,SortedVector<uint32_t> > mData; + bool mContainsPseudo; +}; + + +#endif diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp new file mode 100644 index 0000000..2761d18 --- /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..878d3b1 --- /dev/null +++ b/tools/aapt/StringPool.cpp @@ -0,0 +1,369 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "StringPool.h" + +#include <utils/ByteOrder.h> + +#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) +{ + const size_t NS = pool->size(); + for (size_t s=0; s<NS; s++) { + size_t len; + printf("String #%d: %s\n", s, + String8(pool->stringAt(s, &len)).string()); + } +} + +StringPool::StringPool(bool sorted) + : mSorted(sorted), mValues(-1), mIdents(-1) +{ +} + +ssize_t StringPool::add(const String16& value, bool mergeDuplicates) +{ + return add(String16(), value, mergeDuplicates); +} + +ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans) +{ + ssize_t res = add(String16(), value, false); + if (res >= 0) { + addStyleSpans(res, spans); + } + return res; +} + +ssize_t StringPool::add(const String16& ident, const String16& value, + bool mergeDuplicates) +{ + if (ident.size() > 0) { + ssize_t idx = mIdents.valueFor(ident); + if (idx >= 0) { + fprintf(stderr, "ERROR: Duplicate string identifier %s\n", + String8(mEntries[idx].value).string()); + return UNKNOWN_ERROR; + } + } + + 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; + } + } + + const bool first = vidx < 0; + if (first || !mergeDuplicates) { + pos = mEntryArray.add(eidx); + if (first) { + vidx = mValues.add(value, pos); + const size_t N = mEntryArrayToValues.size(); + for (size_t i=0; i<N; i++) { + size_t& e = mEntryArrayToValues.editItemAt(i); + if ((ssize_t)e >= vidx) { + e++; + } + } + } + mEntryArrayToValues.add(vidx); + if (!mSorted) { + entry& ent = mEntries.editItemAt(eidx); + ent.indices.add(pos); + } + } + + if (ident.size() > 0) { + mIdents.add(ident, vidx); + } + + 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) +{ + LOG_ALWAYS_FATAL_IF(mSorted, "Can't use styles with sorted string pools."); + + // 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); + return NO_ERROR; +} + +size_t StringPool::size() const +{ + return mSorted ? mValues.size() : mEntryArray.size(); +} + +const StringPool::entry& StringPool::entryAt(size_t idx) const +{ + if (!mSorted) { + return mEntries[mEntryArray[idx]]; + } else { + return mEntries[mEntryArray[mValues.valueAt(idx)]]; + } +} + +size_t StringPool::countIdentifiers() const +{ + return mIdents.size(); +} + +sp<AaptFile> StringPool::createStringBlock() +{ + sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(), + String8()); + status_t err = writeStringBlock(pool); + return err == NO_ERROR ? pool : NULL; +} + +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 = 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; + } + + 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 > 0x7fff ? sizeof(uint32_t) : sizeof(uint16_t); + const size_t totalSize = lenSize + ((strSize+1)*sizeof(uint16_t)); + + ent.offset = strPos; + uint16_t* dat = (uint16_t*)pool->editData(preSize + strPos + totalSize); + if (dat == NULL) { + fprintf(stderr, "ERROR: Out of memory for string pool\n"); + return NO_MEMORY; + } + dat += (preSize+strPos)/sizeof(uint16_t); + if (lenSize > sizeof(uint16_t)) { + *dat = htods(0x8000 | ((strSize>>16)&0x7ffff)); + dat++; + } + *dat++ = htods(strSize); + strcpy16_htod(dat, ent.value); + + strPos += lenSize + (strSize+1)*sizeof(uint16_t); + } + + // 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 (mSorted) { + header->flags |= htodl(ResStringPool_header::SORTED_FLAG); + } + header->stringsStart = htodl(preSize); + header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0); + + // Write string index array. + + uint32_t* index = (uint32_t*)(header+1); + if (mSorted) { + for (i=0; i<ENTRIES; i++) { + entry& ent = const_cast<entry&>(entryAt(i)); + ent.indices.clear(); + ent.indices.add(i); + *index++ = htodl(ent.offset); + } + } else { + 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. + + if (mSorted) { + for (i=0; i<STYLES; i++) { + LOG_ALWAYS_FATAL("Shouldn't be here!"); + } + } else { + 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..9082b37 --- /dev/null +++ b/tools/aapt/StringPool.h @@ -0,0 +1,148 @@ +// +// 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 <utils/ResourceTypes.h> +#include <utils/String16.h> +#include <utils/TextOutput.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> + +#include <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) { } + entry(const entry& o) : value(o.value), offset(o.offset), indices(o.indices) { } + + String16 value; + size_t offset; + Vector<size_t> indices; + }; + + 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 'sorted' is true, then the final strings in the resource data + * structure will be generated in sorted order. This allow for fast + * lookup with ResStringPool::indexOfString() (O(log n)), at the expense + * of support for styled string entries (which requires the same string + * be included multiple times in the pool). + */ + explicit StringPool(bool sorted = 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. Note that + * if this string pool is sorted, the returned index will not be valid + * when the pool is finally written. + */ + ssize_t add(const String16& value, bool mergeDuplicates = false); + + ssize_t add(const String16& value, const Vector<entry_style_span>& spans); + + ssize_t add(const String16& ident, const String16& value, + bool mergeDuplicates = false); + + 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); + + size_t size() const; + + const entry& entryAt(size_t idx) const; + + size_t countIdentifiers() const; + + 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: + const bool mSorted; + // Raw array of unique strings, in some arbitrary order. + 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). + Vector<size_t> mEntryArray; + // Optional style span information associated with each index of + // mEntryArray. + Vector<entry_style> mEntryStyleArray; + // Mapping from indices in mEntryArray to indices in mValues. + Vector<size_t> mEntryArrayToValues; + // 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; + // Unique set of all (optional) identifiers of strings in the + // pool, mapping to indices in mEntries. + DefaultKeyedVector<String16, ssize_t> mIdents; + +}; + +#endif + diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp new file mode 100644 index 0000000..d476567 --- /dev/null +++ b/tools/aapt/XMLNode.cpp @@ -0,0 +1,1295 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "XMLNode.h" +#include "ResourceTable.h" + +#include <host/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_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_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE); + +String16 getNamespaceResourcePackage(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)) { + 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 parseStyledString(Bundle* bundle, + const char* fileName, + ResXMLTree* inXml, + const String16& endTag, + String16* outString, + Vector<StringPool::entry_style_span>* outSpans, + 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 { + 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(); + + if (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\"", + String8(block->getAttributeStringValue(i, &len)).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\")", String8(val).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(), String8(block->getText(&len)).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) +{ + if (isNamespace) { + mNamespacePrefix = s1; + mNamespaceUri = s2; + } else { + mNamespaceUri = s1; + mElementName = s2; + } +} + +XMLNode::XMLNode(const String8& filename) + : mFilename(filename) +{ +} + +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 Vector<XMLNode::attribute_entry>& + XMLNode::getAttributes() const +{ + return mAttributes; +} + +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; +} + +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::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; + } + 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(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; + 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 (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; + + 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) { + 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)); + + 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) { + 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..86548a2 --- /dev/null +++ b/tools/aapt/XMLNode.h @@ -0,0 +1,184 @@ +// +// 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 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; + + 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 String16& getCData() const; + + const String16& getComment() const; + + int32_t getStartLineNumber() const; + int32_t getEndLineNumber() const; + + status_t addChild(const sp<XMLNode>& child); + + 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); + + 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; +}; + +#endif 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/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 |