From dadd9c1fc18bd05c84a357b56e945b5829b3bd95 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: [PATCH 1/1] Initial Contribution --- AaptAssets.cpp | 1708 +++++++++++++ AaptAssets.h | 507 ++++ Android.mk | 52 + Bundle.h | 158 ++ Command.cpp | 829 +++++++ Images.cpp | 804 +++++++ Images.h | 18 + Main.cpp | 335 +++ Main.h | 41 + Package.cpp | 441 ++++ Resource.cpp | 1433 +++++++++++ ResourceTable.cpp | 3333 ++++++++++++++++++++++++++ ResourceTable.h | 520 ++++ SourcePos.cpp | 171 ++ SourcePos.h | 28 + StringPool.cpp | 369 +++ StringPool.h | 148 ++ XMLNode.cpp | 1279 ++++++++++ XMLNode.h | 184 ++ printapk.cpp | 127 + tests/plurals/AndroidManifest.xml | 5 + tests/plurals/res/values/strings.xml | 7 + tests/plurals/run.sh | 16 + 23 files changed, 12513 insertions(+) create mode 100644 AaptAssets.cpp create mode 100644 AaptAssets.h create mode 100644 Android.mk create mode 100644 Bundle.h create mode 100644 Command.cpp create mode 100644 Images.cpp create mode 100644 Images.h create mode 100644 Main.cpp create mode 100644 Main.h create mode 100644 Package.cpp create mode 100644 Resource.cpp create mode 100644 ResourceTable.cpp create mode 100644 ResourceTable.h create mode 100644 SourcePos.cpp create mode 100644 SourcePos.h create mode 100644 StringPool.cpp create mode 100644 StringPool.h create mode 100644 XMLNode.cpp create mode 100644 XMLNode.h create mode 100644 printapk.cpp create mode 100644 tests/plurals/AndroidManifest.xml create mode 100644 tests/plurals/res/values/strings.xml create mode 100755 tests/plurals/run.sh diff --git a/AaptAssets.cpp b/AaptAssets.cpp new file mode 100644 index 0000000..d07e4de --- /dev/null +++ b/AaptAssets.cpp @@ -0,0 +1,1708 @@ +// +// Copyright 2006 The Android Open Source Project +// + +#include "AaptAssets.h" +#include "Main.h" + +#include +#include + +#include +#include +#include + +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 char* kExcludeExtension = ".EXCLUDE"; +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, "picassa.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 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; + } + + 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& 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 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& 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& dir) +{ + if (mDirs.indexOfKey(name) >= 0) { + return ALREADY_EXISTS; + } + mDirs.add(name, dir); + return NO_ERROR; +} + +sp AaptDir::makeDir(const String8& path) +{ + String8 name; + String8 remain = path; + + sp 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 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& file, const String8& newName) +{ + sp 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 g = mFiles.valueAt(i); + const size_t NF = g->getFiles().size(); + for (size_t j=0; jgetFiles().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& file) +{ + sp 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 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 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 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; iprint(); + } + + const size_t NF=getFiles().size(); + for (i=0; iprint(); + } +} + +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 AaptAssets::addFile( + const String8& filePath, const AaptGroupEntry& entry, + const String8& srcDir, sp* outGroup, + const String8& resType) +{ + sp dir = this; + sp group; + sp 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 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& file, const String8& resType) +{ + sp res = AaptDir::makeDir(kResString); + String8 dirname = file->getGroupEntry().toDirName(resType); + sp subdir = res->makeDir(dirname); + sp grr = new AaptGroup(leafName, path); + grr->addFile(file); + + subdir->addFile(leafName, grr); +} + + +ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) +{ + int count; + int totalCount = 0; + int i; + int arg = 0; + FileType type; + const char* res; + + 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 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. + */ + res = bundle->getResourceSourceDir(); + 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) { + count = 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; arggetFileSpecEntry(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 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 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; igetEntryByIndex(i); + if (entry->getDeleted()) { + continue; + } + + String8 entryName(entry->getFileName()); + + String8 dirName = entryName.getPathDir(); + sp 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 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 AaptAssets::getSymbolsFor(const String8& name) +{ + sp 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& incl = bundle->getPackageIncludes(); + const size_t N=incl.size(); + for (size_t i=0; igetVerbose()) + 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& file) +{ + const ResTable& res = getIncludedResources(); + // XXX dirty! + return const_cast(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 >& getFiles() const + { return mFiles; } + + status_t addFile(const sp& file); + void removeFile(size_t index); + + void print() const; + + String8 getPrintableSource() const; + +private: + String8 mLeaf; + String8 mPath; + + DefaultKeyedVector > 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 >& getFiles() const { return mFiles; } + const DefaultKeyedVector >& getDirs() const { return mDirs; } + + status_t addFile(const String8& name, const sp& file); + status_t addDir(const String8& name, const sp& dir); + + sp makeDir(const String8& name); + + void removeFile(const String8& name); + void removeDir(const String8& name); + + status_t renameFile(const sp& file, const String8& newName); + + status_t addLeafFile(const String8& leafName, + const sp& 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 > mFiles; + DefaultKeyedVector > 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 addNestedSymbol(const String8& name, const SourcePos& pos) { + if (!check_valid_symbol_name(name, pos, "nested symbol")) { + return NULL; + } + + sp sym = mNestedSymbols.valueFor(name); + if (sym == NULL) { + sym = new AaptSymbols(); + mNestedSymbols.add(name, sym); + } + + return sym; + } + + const KeyedVector& getSymbols() const + { return mSymbols; } + const DefaultKeyedVector >& 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 mSymbols; + DefaultKeyedVector > mNestedSymbols; + AaptSymbolEntry mDefSymbol; +}; + +/** + * 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& getGroupEntries() const { return mGroupEntries; } + + sp addFile(const String8& filePath, + const AaptGroupEntry& entry, + const String8& srcDir, + sp* outGroup, + const String8& resType); + + void addResource(const String8& leafName, + const String8& path, + const sp& 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 getSymbolsFor(const String8& name); + + const DefaultKeyedVector >& 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& file); + const ResTable& getIncludedResources() const; + + void print() const; + + inline const Vector >& resDirs() { return mDirs; } + +private: + String8 mPackage; + SortedVector mGroupEntries; + DefaultKeyedVector > mSymbols; + String8 mSymbolsPrivatePackage; + + Vector > mDirs; + + bool mHaveIncludedAssets; + AssetManager mIncludedAssets; +}; + +#endif // __AAPT_ASSETS_H + diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..fdc859c --- /dev/null +++ b/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/Bundle.h b/Bundle.h new file mode 100644 index 0000000..1d7b3ad --- /dev/null +++ b/Bundle.h @@ -0,0 +1,158 @@ +// +// 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 +#include // android +#include +#include + +/* + * 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), mMakePackageDirs(false), + mUpdate(false), mExtending(false), + mRequireLocalization(false), mPseudolocalize(false), + mCompressionMethod(0), mOutputAPKFile(NULL), + mAssetSourceDir(NULL), mResourceSourceDir(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; } + 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 char* getResourceSourceDir() const { return mResourceSourceDir; } + void setResourceSourceDir(const char* dir) { mResourceSourceDir = dir; } + 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& getPackageIncludes() const { return mPackageIncludes; } + void addPackageInclude(const char* file) { mPackageIncludes.add(file); } + const android::Vector& getJarFiles() const { return mJarFiles; } + void addJarFile(const char* file) { mJarFiles.add(file); } + + /* + * 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; + bool mMakePackageDirs; + bool mUpdate; + bool mExtending; + bool mRequireLocalization; + bool mPseudolocalize; + int mCompressionMethod; + const char* mOutputAPKFile; + const char* mAssetSourceDir; + const char* mResourceSourceDir; + const char* mAndroidManifestFile; + const char* mPublicOutputFile; + const char* mRClassDir; + const char* mResourceIntermediatesDir; + android::String8 mConfigurations; + android::Vector mPackageIncludes; + android::Vector mJarFiles; + + /* file specification */ + int mArgc; + char* const* mArgv; + +#if 0 + /* misc stuff */ + int mPackageCount; +#endif +}; + +#endif // __BUNDLE_H diff --git a/Command.cpp b/Command.cpp new file mode 100644 index 0000000..c02e2c0 --- /dev/null +++ b/Command.cpp @@ -0,0 +1,829 @@ +// +// 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 +#include + +#include +#include + +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; iresolveReference(&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)->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; igetFileSpecCount(); 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; igetFileSpecCount(); 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 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 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()); + } + } + } else if (strcmp("configurations", option) == 0) { + Vector configs; + res.getConfigurations(&configs); + const size_t N = configs.size(); + for (size_t i=0; igetUpdate()) { + /* 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 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->getResourceSourceDir() == NULL && 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->getResourceSourceDir() || 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/Images.cpp b/Images.cpp new file mode 100644 index 0000000..4a766d1 --- /dev/null +++ b/Images.cpp @@ -0,0 +1,804 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#define PNG_INTERNAL + +#include "Images.h" + +#include +#include + +#include + +#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), hasTransparency(true), 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; + + bool hasTransparency; + + // 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 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 void examine_image(image_info* image) +{ + bool hasTrans = false; + for (int i=0; i<(int)image->height && !hasTrans; i++) { + png_bytep p = image->rows[i]; + for (int j=0; j<(int)image->width; j++) { + if (p[(j*4)+3] != 0xFF) { + hasTrans = true; + break; + } + } + } + + image->hasTransparency = hasTrans; +} + +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; iinfo9Patch.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) +{ + size_t patchSize = inPatch->serializedSize(); + void * newData = malloc(patchSize); + memcpy(newData, data, patchSize); + Res_png_9patch* outPatch = inPatch->deserialize(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]); + } +} + +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 write_png(const char* imageName, + png_structp write_ptr, png_infop write_info, + image_info& imageInfo) +{ + png_uint_32 width, height; + int color_type; + int bit_depth, interlace_type, compression_type; + int i; + + png_unknown_chunk unknowns[1]; + + png_set_compression_level(write_ptr, Z_BEST_COMPRESSION); + + color_type = PNG_COLOR_MASK_COLOR; + if (imageInfo.hasTransparency) { + color_type |= PNG_COLOR_MASK_ALPHA; + } + + 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 (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); + + if (!imageInfo.hasTransparency) { + png_set_filler(write_ptr, 0, PNG_FILLER_AFTER); + } + + png_write_image(write_ptr, imageInfo.rows); + + png_write_end(write_ptr, write_info); + + 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& assets, + const sp& 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); + + examine_image(&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); + + 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& assets, + ResourceTable* table, const sp& 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/Images.h b/Images.h new file mode 100644 index 0000000..168e22f --- /dev/null +++ b/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& assets, + const sp& file, String8* outNewLeafName); + +status_t postProcessImage(const sp& assets, + ResourceTable* table, const sp& file); + +#endif diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..d86ad47 --- /dev/null +++ b/Main.cpp @@ -0,0 +1,335 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Android Asset Packaging Tool main entry point. +// +#include "Main.h" +#include "Bundle.h" + +#include +#include + +#include +#include +#include + +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" + " 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] [-f][-u][-m][-v][-x][-M AndroidManifest.xml] \\\n" + " [-I base-package [-I base-package ...]] \\\n" + " [-A asset-source-dir] [-P public-definitions-file] \\\n" + " [-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" + " -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\n" + " -0 don't compress files we're adding\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[]) +{ + Bundle bundle; + bool wantUsage = false; + int result = 1; // pessimistically assume an error. + + /* 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 '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.setResourceSourceDir(argv[0]); + break; + case '0': + 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/Main.h b/Main.h new file mode 100644 index 0000000..65c0a8a --- /dev/null +++ b/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 +#include "Bundle.h" +#include "AaptAssets.h" +#include + +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& assets, + const android::String8& outputFile); + +extern android::status_t buildResources(Bundle* bundle, + const sp& assets); + +extern android::status_t writeResourceSymbols(Bundle* bundle, + const sp& assets, const String8& pkgName, bool includePrivate); + +extern bool isValidResourceType(const String8& type); + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp& assets); + +extern status_t filterResources(Bundle* bundle, const sp& assets); + +int dumpResources(Bundle* bundle); + +#endif // __MAIN_H diff --git a/Package.cpp b/Package.cpp new file mode 100644 index 0000000..23f641a --- /dev/null +++ b/Package.cpp @@ -0,0 +1,441 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Package assets into Zip files. +// +#include "Main.h" +#include "AaptAssets.h" + +#include +#include + +#include +#include +#include +#include + +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", + ".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& assets); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, + const sp& dir, const AaptGroupEntry& ge); +bool processFile(Bundle* bundle, ZipFile* zip, + const sp& group, const sp& file); +bool okayToCompress(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& 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& assets) +{ + ssize_t count = 0; + + const size_t N = assets->getGroupEntries().size(); + for (size_t i=0; igetGroupEntries()[i]; + 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& dir, const AaptGroupEntry& ge) +{ + ssize_t count = 0; + + const size_t ND = dir->getDirs().size(); + size_t i; + for (i=0; igetDirs().valueAt(i), ge); + if (res < 0) { + return res; + } + count += res; + } + + const size_t NF = dir->getFiles().size(); + for (i=0; i gp = dir->getFiles().valueAt(i); + ssize_t fi = gp->getFiles().indexOfKey(ge); + if (fi >= 0) { + sp 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& group, const sp& 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(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(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; + } + + 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; igetEntryByIndex(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& jars = bundle->getJarFiles(); + + size_t N = jars.size(); + for (size_t i=0; i& 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 > +{ +public: + ResourceTypeSet(); +}; + +ResourceTypeSet::ResourceTypeSet() + :RefBase(), + KeyedVector >() +{ +} + +class ResourceDirIterator +{ +public: + ResourceDirIterator(const sp& set, const String8& resType) + : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0) + { + } + + inline const sp& getGroup() const { return mGroup; } + inline const sp& 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 group; + sp 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 mSet; + size_t mSetPos; + + sp mGroup; + size_t mGroupPos; + + sp 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 getResourceFile(const sp& assets, bool makeIfNecessary=true) +{ + sp group = assets->getFiles().valueFor(String8("resources.arsc")); + sp 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& assets, const sp& grp) +{ + if (grp->getFiles().size() != 1) { + fprintf(stderr, "WARNING: Multiple AndroidManifest.xml files found, using %s\n", + grp->getFiles().valueAt(0)->getPrintableSource().string()); + } + + sp 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 \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: 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& assets, + ResourceTable* table, + const sp& 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& assets, + const sp& set) +{ + ResourceDirIterator it(set, String8("drawable")); + Vector > newNameFiles; + Vector newNamePaths; + ssize_t res; + while ((res=it.next()) == NO_ERROR) { + res = preProcessImage(bundle, assets, it.getFile(), NULL); + if (res != NO_ERROR) { + return res; + } + } + + return NO_ERROR; +} + +status_t postProcessImages(const sp& assets, + ResourceTable* table, + const sp& 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& dir, + KeyedVector >* resources) +{ + const DefaultKeyedVector >& groups = dir->getFiles(); + int N = groups.size(); + for (int i=0; i& group = groups.valueAt(i); + + const DefaultKeyedVector >& files + = group->getFiles(); + + if (files.size() == 0) { + continue; + } + + String8 resType = files.valueAt(0)->getResourceType(); + + ssize_t index = resources->indexOfKey(resType); + + if (index < 0) { + sp set = new ResourceTypeSet(); + set->add(leafName, group); + resources->add(resType, set); + } else { + sp set = resources->valueAt(index); + index = set->indexOfKey(leafName); + if (index < 0) { + set->add(leafName, group); + } else { + sp existingGroup = set->valueAt(index); + int M = files.size(); + for (int j=0; jaddFile(files.valueAt(j)); + } + } + } + } +} + +static void collect_files(const sp& ass, + KeyedVector >* resources) +{ + const Vector >& dirs = ass->resDirs(); + int N = dirs.size(); + + for (int i=0; i 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 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()); + } + } + } +} + +#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& assets) +{ + // First, look for a package file to parse. This is required to + // be able to generate the resource information. + sp 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())); + + sp res = assets->getDirs().valueFor(String8("res")); + + // -------------------------------------------------------------- + // First, gather all resource information. + // -------------------------------------------------------------- + + // resType -> leafName -> group + KeyedVector > resources; + collect_files(assets, &resources); + + sp drawables; + sp valuess; + sp layouts; + sp anims; + sp xmls; + sp raws; + sp colors; + sp menus; + + ASSIGN_IT(drawable); + ASSIGN_IT(layout); + ASSIGN_IT(anim); + ASSIGN_IT(xml); + ASSIGN_IT(raw); + ASSIGN_IT(values); + ASSIGN_IT(color); + ASSIGN_IT(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; + } + } + + if (valuess != NULL) { + ResourceDirIterator it(valuess, String8("values")); + ssize_t res; + while ((res=it.next()) == NO_ERROR) { + sp file = it.getFile(); + + res = compileResourceFile(bundle, assets, file, it.getParams(), &table); + if (res != NO_ERROR) { + hasErrors = true; + } + } + } + + 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 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 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 permissionSymbols; + sp 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 syms; + if (!isGroup) { + syms = permissionSymbols; + if (syms == NULL) { + sp symbols = + assets->getSymbolsFor(String8("Manifest")); + syms = permissionSymbols = symbols->addNestedSymbol( + String8("permission"), srcPos); + } + } else { + syms = permissionGroupSymbols; + if (syms == NULL) { + sp 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 (hasErrors) { + return UNKNOWN_ERROR; + } + + // Generate final compiled manifest file. + manifestFile->clearData(); + err = compileXmlFile(assets, manifestFile, &table); + if (err < NO_ERROR) { + return err; + } + + //block.restart(); + //printXMLBlock(&block); + + // -------------------------------------------------------------- + // Generate the final resource table. + // Re-flatten because we may have added new resource IDs + // -------------------------------------------------------------- + + if (table.hasResources()) { + sp symbols = assets->getSymbolsFor(String8("R")); + err = table.addSymbols(symbols); + if (err < NO_ERROR) { + return err; + } + + sp resFile(getResourceFile(assets)); + if (resFile == NULL) { + fprintf(stderr, "Error: unable to generate entry for resource data\n"); + return UNKNOWN_ERROR; + } + + err = table.flatten(bundle, resFile); + if (err < NO_ERROR) { + return err; + } + + if (bundle->getPublicOutputFile()) { + FILE* fp = fopen(bundle->getPublicOutputFile(), "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n", + (const char*)bundle->getPublicOutputFile(), strerror(errno)); + return UNKNOWN_ERROR; + } + if (bundle->getVerbose()) { + printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile()); + } + table.writePublicDefinitions(String16(assets->getPackage()), fp); + } + + 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& assets, + const String8& name, + String16* outTypeComment = NULL) +{ + sp 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& assets, + const sp& 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 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 idents; + Vector origOrder; + Vector publicFlags; + + size_t a; + size_t NA = nsymbols->getSymbols().size(); + for (a=0; agetSymbols().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 %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= 0) { + if (!hasTable) { + hasTable = true; + fprintf(fp, + "%s

Includes the following attributes:

\n" + "%s \n" + "%s \n" + "%s \n" + "%s \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 \n", + indentStr, nclassName.string(), + String8(name).string(), + assets->getPackage().string(), + String8(name).string(), + String8(comment).string()); + } + } + if (hasTable) { + fprintf(fp, "%s
AttributeSummary
{@link #%s_%s %s:%s}%s
\n", indentStr); + } + for (a=0; a= 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= 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

\n%s @attr description\n", indentStr, indentStr); + fprintf(fp, "%s %s\n", indentStr, String8(comment).string()); + } else { + fprintf(fp, + "%s

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

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

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& assets, bool includePrivate, + const sp& 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; igetSymbols().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; igetSymbols().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 styleableSymbols; + + N = symbols->getNestedSymbols().size(); + for (i=0; i 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& 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 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/ResourceTable.cpp b/ResourceTable.cpp new file mode 100644 index 0000000..6fe196a --- /dev/null +++ b/ResourceTable.cpp @@ -0,0 +1,3333 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "ResourceTable.h" + +#include "XMLNode.h" + +#include +#include +#include + +#define NOISY(x) //x + +status_t compileXmlFile(const sp& assets, + const sp& target, + ResourceTable* table, + int options) +{ + sp 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 \"@[+][package:]type:name\"\n" + "or to a theme attribute in the form \"?[package:][type:]name\"."}, + { 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 \"100\"." }, + { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN, + "a boolean value, either \"true\" or \"false\"." }, + { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR, + "a color value, in the form of \"#rgb\", \"#argb\",\n" + "\"#rrggbb\", or \"#aarrggbb\"." }, + { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT, + "a floating point value, such as \"1.2\"."}, + { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION, + "a dimension value, which is a floating point number appended with a unit such as \"14.5sp\".\n" + "Available units are: px (pixels), db (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 \"14.5%\".\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("

Must"); + } + + return String16("

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("

This may also be a reference to a resource (in the form\n" + "\"@[package:]type:name\") or\n" + "theme attribute (in the form\n" + "\"?[package:][type:]name\")\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& 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& 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 \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 '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 '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 '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 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 '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 , only or \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(" attribute can not be used inside a flags format\n"); + attr.hasErrors = true; + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error(" 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 or \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 or \n"); + attr.hasErrors = true; + } + if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) { + SourcePos(in->getPrintableSource(), block.getLineNumber()) + .error("Tag or '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("

\n\n" + "\n" + "\n" + "\n" + "")); + } + + enumOrFlagsComment.append(String16("\n")); + + 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 where 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 where 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
ConstantValueDescription
")); + enumOrFlagsComment.append(itemIdent); + enumOrFlagsComment.append(String16("")); + enumOrFlagsComment.append(value); + enumOrFlagsComment.append(String16("")); + if (block.getComment(&len)) { + enumOrFlagsComment.append(String16(block.getComment(&len))); + } + enumOrFlagsComment.append(String16("
")); + 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& 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, + ResourceTable* outTable) +{ + status_t err; + const String16 item16("item"); + + String16 str; + Vector 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, false, false, curFormat); + return err; +} + + +status_t parseAndAddEntry(Bundle* bundle, + const sp& 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, + ResourceTable* outTable) +{ + status_t err; + + String16 str; + Vector 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); + + return err; +} + +status_t compileResourceFile(Bundle* bundle, + const sp& assets, + const sp& in, + const ResTable_config& defParams, + 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 integer16("integer"); + const String16 dimen16("dimen"); + 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"); + + + 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 \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 \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 ," + " 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 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 \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 \n"); + hasErrors = localHasErrors = true; + } + ident = String16(block.getAttributeStringValue(identIdx, &len)); + + sp symbols = assets->getSymbolsFor(String8("R")); + if (!localHasErrors) { + if (symbols != NULL) { + symbols = symbols->addNestedSymbol(String8("styleable"), srcPos); + } + sp 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 , only \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 where 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 '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 \n"); + hasErrors = localHasErrors = true; + } + curIsStyled = true; + } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) { + 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), 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), 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 \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 '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 \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 inside \n"); + hasErrors = localHasErrors = true; + } + } else { + SourcePos(in->getPrintableSource(), block.getLineNumber()).error( + "A 'quantity' attribute is required for inside \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 \n"); + hasErrors = localHasErrors = true; + } + } + + ResXMLParser::ResXMLPosition parserPosition; + block.getPosition(&parserPosition); + + err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType, + ident, parentIdent, itemIdent, curFormat, false, 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, + 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 where 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, 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, 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& 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 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 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 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 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* style, + const ResTable_config* params, + const bool doSetIndex, + 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 entry left: file=%s, line=%d, type=%s, value=%s\n", + sourcePos.file.string(), sourcePos.line, String8(type).string(), + String8(value).string()); + } +#endif + + sp e = getEntry(package, type, name, sourcePos, params, doSetIndex); + if (e == NULL) { + return UNKNOWN_ERROR; + } + status_t err = e->setItem(sourcePos, value, style, format); + 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 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* 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 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 p = mPackages.valueFor(package); + if (p != NULL) { + sp t = p->getTypes().valueFor(type); + if (t != NULL) { + sp 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 p = mPackages.valueFor(package); + if (p != NULL) { + sp t = p->getTypes().valueFor(type); + if (t != NULL) { + sp 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 p = mPackages.valueFor(package); + if (p != NULL) { + sp t = p->getTypes().valueFor(type); + if (t != NULL) { + sp 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 ResourceTable::flatten(Bundle* bundle) +{ + sp 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& p, + const sp& t, + uint32_t nameId) +{ + return makeResId(p->getAssignedId(), t->getIndex(), nameId); +} + +uint32_t ResourceTable::getResId(const String16& package, + const String16& type, + const String16& name) const +{ + sp p = mPackages.valueFor(package); + if (p == NULL) return 0; + + // 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) { + if (Res_INTERNALID(rid)) { + return rid; + } + return Res_MAKEID(p->getAssignedId()-1, + Res_GETTYPE(rid), + Res_GETENTRY(rid)); + } + + sp t = p->getTypes().valueFor(type); + if (t == NULL) return 0; + sp 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) 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); + 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* 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 p = mPackages.valueFor(package); + if (p == NULL) return 0; + sp t = p->getTypes().valueFor(type); + if (t == NULL) return 0; + sp 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* outKeys) +{ + sp e = getEntry(attrID); + if (e != NULL) { + const size_t N = e->getBag().size(); + for (size_t i=0; igetBag().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 e = getEntry(attrID); + if (e != NULL) { + const size_t N = e->getBag().size(); + for (size_t i=0; igetBag().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 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; igetBag().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 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 t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + const size_t N = t->getOrderedConfigs().size(); + for (size_t ci=0; ci c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } + const size_t N = c->getEntries().size(); + for (size_t ei=0; ei 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 attr = p->getType(String16("attr"), unknown); + + // Assign indices... + for (ti=0; ti 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 c = t->getOrderedConfigs().itemAt(ei); + if (c == NULL) { + continue; + } + c->setEntryIndex(ei); + } + } + + // Assign resource IDs to keys in bags... + for (ti=0; ti t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + const size_t N = t->getOrderedConfigs().size(); + for (size_t ci=0; ci 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 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& outSymbols) { + const size_t N = mOrderedPackages.size(); + size_t pi; + + for (pi=0; pi 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 t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + const size_t N = t->getOrderedConfigs().size(); + sp typeSymbols; + typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); + for (size_t ci=0; ci 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; +} + + +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()); + } + SortedVector& 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& 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& 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 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 t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + typeStrings.add(String16(""), false); + continue; + } + typeStrings.add(t->getName(), false); + + const size_t N = t->getOrderedConfigs().size(); + for (size_t ci=0; ci c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } + const size_t N = c->getEntries().size(); + for (size_t ei=0; ei 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 > flatPackages; + for (pi=0; pi 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 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 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; tigetTypeStrings().stringAt(ti, &len)); + sp t = p->getTypes().valueFor(typeName); + LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16(""), + "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 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; cigetEntries().keyAt(ci))) { + continue; + } + for (size_t cj=ci+1; cjgetEntries().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; cigetUniqueConfigs().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 cl = t->getOrderedConfigs().itemAt(ei); + sp 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; piwriteData(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, + "\n" + "\n" + "\n"); + + writePublicDefinitions(package, fp, true); + writePublicDefinitions(package, fp, false); + + fprintf(fp, + "\n" + "\n"); +} + +void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub) +{ + bool didHeader = false; + + sp pkg = mPackages.valueFor(package); + if (pkg != NULL) { + const size_t NT = pkg->getOrderedTypes().size(); + for (size_t i=0; i 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 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," \n\n"); + } else { + fprintf(fp," \n\n"); + } + didHeader = true; + } + if (!pub) { + const size_t NE = c->getEntries().size(); + for (size_t k=0; kgetEntries().valueAt(k)->getPos(); + if (pos.file != "") { + fprintf(fp," \n", + pos.file.string(), pos.line); + } + } + } + fprintf(fp, " \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* _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* style, + int32_t format) +{ + 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) { + 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* 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; ihasBagOrEntry(key, &id16, &package)) { + String16 value("false"); + status_t err = table->addEntry(SourcePos(String8(""), 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("", 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; igetResId(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; istringToValue(&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& 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 items; + for (i=0; iwriteData(&mapHeader, sizeof(mapHeader)); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n"); + return err; + } + + for (i=0; iwriteData(&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::Type::getEntry(const String16& entry, + const SourcePos& sourcePos, + const ResTable_config* config, + bool doSetIndex) +{ + int pos = -1; + sp 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 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 > origOrder(mOrderedConfigs); + bool hasError = false; + + size_t i; + for (i=0; i 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 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 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::Package::getType(const String16& type, + const SourcePos& sourcePos, + bool doSetIndex) +{ + sp 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& 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& 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& data, + ResStringPool* strings, + DefaultKeyedVector* 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; iadd(String16(strings->stringAt(i, &len)), i); + } + } + return err; +} + +status_t ResourceTable::Package::applyPublicTypeOrder() +{ + size_t N = mOrderedTypes.size(); + Vector > origOrder(mOrderedTypes); + + size_t i; + for (i=0; i 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 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 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::getPackage(const String16& package) +{ + sp 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::getType(const String16& package, + const String16& type, + const SourcePos& sourcePos, + bool doSetIndex) +{ + sp p = getPackage(package); + if (p == NULL) { + return NULL; + } + return p->getType(type, sourcePos, doSetIndex); +} + +sp ResourceTable::getEntry(const String16& package, + const String16& type, + const String16& name, + const SourcePos& sourcePos, + const ResTable_config* config, + bool doSetIndex) +{ + sp t = getType(package, type, sourcePos, doSetIndex); + if (t == NULL) { + return NULL; + } + return t->getEntry(name, sourcePos, config, doSetIndex); +} + +sp 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 p; + for (i=0; i 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 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 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 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 e = getEntry(resID); + if (e == NULL) { + return NULL; + } + + const size_t N = e->getBag().size(); + for (size_t i=0; igetBag().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 e = getEntry(resID); + const size_t N = e->getBag().size(); + size_t i; + for (i=0; igetBag().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/ResourceTable.h b/ResourceTable.h new file mode 100644 index 0000000..b36234d --- /dev/null +++ b/ResourceTable.h @@ -0,0 +1,520 @@ +// +// 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" + +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& assets, + const sp& target, + ResourceTable* table, + int options = XML_COMPILE_STANDARD_RESOURCE); + +status_t compileResourceFile(Bundle* bundle, + const sp& assets, + const sp& in, + const ResTable_config& defParams, + 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& 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* style = NULL, + const ResTable_config* params = NULL, + const bool doSetIndex = false, + const int32_t format = ResTable_map::TYPE_ANY); + + 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* 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 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& p, + const sp& t, + uint32_t nameId); + + uint32_t getResId(const String16& package, + const String16& type, + const String16& name) const; + + uint32_t getResId(const String16& ref, + const String16* defType = NULL, + const String16* defPackage = NULL, + const char** outErrorMsg = NULL) 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* style = NULL, + String16* outStr = NULL, void* accessorCookie = NULL, + uint32_t attrType = ResTable_map::TYPE_ANY); + + status_t assignResourceIds(); + status_t addSymbols(const sp& outSymbols = NULL); + + status_t flatten(Bundle*, const sp& 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* 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* _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 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* style = NULL, + int32_t format = ResTable_map::TYPE_ANY); + + status_t addToBag(const SourcePos& pos, + const String16& key, const String16& value, + const Vector* 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& 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& data, bool isPublic); + + const SourcePos& getPos() const { return mPos; } + + private: + String16 mName; + String16 mParent; + type mType; + Item mItem; + int32_t mItemFormat; + KeyedVector 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(this) = o; + size = sizeof(ResTable_config); + } + ConfigDescription(const ConfigDescription&o) { + *static_cast(this) = o; + } + + ConfigDescription& operator=(const ResTable_config& o) { + *static_cast(this) = o; + size = sizeof(ResTable_config); + return *this; + } + ConfigDescription& operator=(const ConfigDescription& o) { + *static_cast(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) { + mEntries.add(config, entry); + } + + const DefaultKeyedVector >& getEntries() const { return mEntries; } + private: + const String16 mName; + const SourcePos mPos; + String16 mComment; + String16 mTypeComment; + bool mPublic; + SourcePos mPublicSourcePos; + int32_t mEntryIndex; + DefaultKeyedVector > 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 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& getUniqueConfigs() const { return mUniqueConfigs; } + + const DefaultKeyedVector >& getConfigs() const { return mConfigs; } + const Vector >& getOrderedConfigs() const { return mOrderedConfigs; } + + const SourcePos& getPos() const { return mPos; } + private: + String16 mName; + SourcePos* mFirstPublicSourcePos; + DefaultKeyedVector mPublic; + SortedVector mUniqueConfigs; + DefaultKeyedVector > mConfigs; + Vector > 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 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 getTypeStringsData() const { return mTypeStringsData; } + status_t setTypeStrings(const sp& data); + + const ResStringPool& getKeyStrings() const { return mKeyStrings; } + uint32_t indexOfKeyString(const String16& s) const { return mKeyStringsMapping.valueFor(s); } + const sp getKeyStringsData() const { return mKeyStringsData; } + status_t setKeyStrings(const sp& data); + + status_t applyPublicTypeOrder(); + + const DefaultKeyedVector >& getTypes() const { return mTypes; } + const Vector >& getOrderedTypes() const { return mOrderedTypes; } + + private: + status_t setStrings(const sp& data, + ResStringPool* strings, + DefaultKeyedVector* mappings); + + const String16 mName; + const ssize_t mIncludedId; + DefaultKeyedVector > mTypes; + Vector > mOrderedTypes; + sp mTypeStringsData; + sp mKeyStringsData; + ResStringPool mTypeStrings; + ResStringPool mKeyStrings; + DefaultKeyedVector mTypeStringsMapping; + DefaultKeyedVector mKeyStringsMapping; + }; + +private: + void writePublicDefinitions(const String16& package, FILE* fp, bool pub); + sp getPackage(const String16& package); + sp getType(const String16& package, + const String16& type, + const SourcePos& pos, + bool doSetIndex = false); + sp getEntry(const String16& package, + const String16& type, + const String16& name, + const SourcePos& pos, + const ResTable_config* config = NULL, + bool doSetIndex = false); + sp 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 mAssets; + DefaultKeyedVector > mPublicNames; + DefaultKeyedVector > mPackages; + Vector > mOrderedPackages; + uint32_t mNextPackageId; + bool mHaveAppPackage; + bool mIsAppPackage; + size_t mNumLocal; + SourcePos mCurrentXmlPos; + Bundle* mBundle; +}; + +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 > mData; + bool mContainsPseudo; +}; + + +#endif diff --git a/SourcePos.cpp b/SourcePos.cpp new file mode 100644 index 0000000..2761d18 --- /dev/null +++ b/SourcePos.cpp @@ -0,0 +1,171 @@ +#include "SourcePos.h" + +#include +#include + +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 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::const_iterator it; + for (it=g_errors.begin(); it!=g_errors.end(); it++) { + it->print(to); + } +} + + + diff --git a/SourcePos.h b/SourcePos.h new file mode 100644 index 0000000..33f72a9 --- /dev/null +++ b/SourcePos.h @@ -0,0 +1,28 @@ +#ifndef SOURCEPOS_H +#define SOURCEPOS_H + +#include +#include + +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/StringPool.cpp b/StringPool.cpp new file mode 100644 index 0000000..878d3b1 --- /dev/null +++ b/StringPool.cpp @@ -0,0 +1,369 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "StringPool.h" + +#include + +#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; sstringAt(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& 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= 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& spans) +{ + const size_t N=spans.size(); + for (size_t i=0; i StringPool::createStringBlock() +{ + sp pool = new AaptFile(String8(), AaptGroupEntry(), + String8()); + status_t err = writeStringBlock(pool); + return err == NO_ERROR ? pool : NULL; +} + +status_t StringPool::writeStringBlock(const sp& pool) +{ + // Allow appending. Sorry this is a little wacky. + if (pool->getSize() > 0) { + sp 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; ieditData(preSize) == NULL) { + fprintf(stderr, "ERROR: Out of memory for string pool\n"); + return NO_MEMORY; + } + + size_t strPos = 0; + for (i=0; i 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; ieditData(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; iname.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(entryAt(i)); + ent.indices.clear(); + ent.indices.add(i); + *index++ = htodl(ent.offset); + } + } else { + for (i=0; i* 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* 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/StringPool.h b/StringPool.h new file mode 100644 index 0000000..9082b37 --- /dev/null +++ b/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 +#include +#include + +#include +#include +#include +#include +#include + +#include + +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 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 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& 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& 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 createStringBlock(); + + status_t writeStringBlock(const sp& 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* offsetsForString(const String16& val) const; + +private: + const bool mSorted; + // Raw array of unique strings, in some arbitrary order. + Vector 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 mEntryArray; + // Optional style span information associated with each index of + // mEntryArray. + Vector mEntryStyleArray; + // Mapping from indices in mEntryArray to indices in mValues. + Vector mEntryArrayToValues; + // Unique set of all the strings added to the pool, mapped to + // the first index of mEntryArray where the value was added. + DefaultKeyedVector mValues; + // Unique set of all (optional) identifiers of strings in the + // pool, mapping to indices in mEntries. + DefaultKeyedVector mIdents; + +}; + +#endif + diff --git a/XMLNode.cpp b/XMLNode.cpp new file mode 100644 index 0000000..8f45959 --- /dev/null +++ b/XMLNode.cpp @@ -0,0 +1,1279 @@ +// +// Copyright 2006 The Android Open Source Project +// +// Build resource files from raw assets. +// + +#include "XMLNode.h" +#include "ResourceTable.h" + +#include +#include +#include +#include + +#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 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); + +String16 getNamespaceResourcePackage(String16 namespaceUri) +{ + //printf("%s starts with %s?\n", String8(namespaceUri).string(), + // String8(RESOURCES_PREFIX).string()); + if (!namespaceUri.startsWith(RESOURCES_PREFIX)) return String16(); + //printf("YES!\n"); + const size_t prefixSize = RESOURCES_PREFIX.size(); + //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string()); + return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize); +} + +status_t parseStyledString(Bundle* bundle, + const char* fileName, + ResXMLTree* inXml, + const String16& endTag, + String16* outString, + Vector* outSpans, + bool pseudolocalize) +{ + Vector 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; igetLineNumber()).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; aigetAttributeCount(); 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 for at text '%s'\n", + fileName, inXml->getLineNumber(), + String8(*outString).string(), String8(spanTag).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& namespaces, + const uint16_t* ns) +{ + String8 str; + if (ns != NULL) { + str = String8(ns); + const size_t N = namespaces.size(); + for (size_t i=0; irestart(); + + Vector 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 \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; igetAttributeNameResID(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 = ""; + } + 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 = ""; + } + 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& file, ResXMLTree* outTree, + bool stripAll, bool keepComments, + const char** cDataTags) +{ + sp 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 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::parse(const sp& 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 >& XMLNode::getChildren() const +{ + return mChildren; +} + +const Vector& + 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& 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 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& 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; isetCurrentXmlPos(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; iparseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } + } + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +status_t XMLNode::assignResourceIds(const sp& 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 %s\n", + String8(getElementName()).string(), + String8(e.name).string(), + String8(e.string).string(), + String8(e.ns).string(), String8(pkg).string())); + if (pkg.size() <= 0) continue; + uint32_t res = table != NULL + ? table->getResId(e.name, &attr, &pkg, &errorMsg) + : 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; iassignResourceIds(assets, table); + if (err < NO_ERROR) { + hasErrors = true; + } + } + + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +status_t XMLNode::flatten(const sp& dest, + bool stripComments, bool stripRawValues) const +{ + StringPool strings; + Vector 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 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; ieditData(); + 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 0) { + elemNs.append(":"); + } + printf("%s E: %s%s", prefix.string(), + elemNs.string(), String8(getElementName()).string()); + int N = mAttributes.size(); + for (i=0; i 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() : "", + String8(getNamespaceUri()).string()); + } else { + printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string()); + } + int N = mChildren.size(); + for (i=0; iprint(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 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 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 node = NULL; + if (st->stack.size() == 0) { + return; + } + sp 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 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 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* 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 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; icollect_strings(dest, outResIds, + stripComments, stripRawValues); + } + + return NO_ERROR; +} + +status_t XMLNode::collect_attr_strings(StringPool* outPool, + Vector* outResIds, bool allAttrs) const { + const int NA = mAttributes.size(); + + for (int i=0; i* 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; jitemAt(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* outResIds) const +{ + collect_attr_strings(outPool, outResIds, false); + + const int NC = mChildren.size(); + + for (int i=0; icollect_resid_strings(outPool, outResIds); + } + + return NO_ERROR; +} + +status_t XMLNode::flatten_node(const StringPool& strings, const sp& 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 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 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; iflatten_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/XMLNode.h b/XMLNode.h new file mode 100644 index 0000000..8c4243c --- /dev/null +++ b/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); + +status_t parseStyledString(Bundle* bundle, + const char* fileName, + ResXMLTree* inXml, + const String16& endTag, + String16* outString, + Vector* outSpans, + bool isPseudolocalizable); + +void printXMLBlock(ResXMLTree* block); + +status_t parseXMLResource(const sp& file, ResXMLTree* outTree, + bool stripAll=true, bool keepComments=false, + const char** cDataTags=NULL); + +class XMLNode : public RefBase +{ +public: + static sp parse(const sp& file); + + static inline + sp newNamespace(const String8& filename, const String16& prefix, const String16& uri) { + return new XMLNode(filename, prefix, uri, true); + } + + static inline + sp newElement(const String8& filename, const String16& ns, const String16& name) { + return new XMLNode(filename, ns, name, false); + } + + static inline + sp 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 >& 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& getAttributes() const; + + const String16& getCData() const; + + const String16& getComment() const; + + int32_t getStartLineNumber() const; + int32_t getEndLineNumber() const; + + status_t addChild(const sp& 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& assets, ResourceTable* table); + + status_t assignResourceIds(const sp& assets, + const ResourceTable* table = NULL); + + status_t flatten(const sp& dest, bool stripComments, + bool stripRawValues) const; + + void print(int indent=0); + +private: + struct ParseState + { + String8 filename; + XML_Parser parser; + sp root; + Vector > 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* outResIds, + bool stripComments, bool stripRawValues) const; + + status_t collect_attr_strings(StringPool* outPool, + Vector* outResIds, bool allAttrs) const; + + status_t collect_resid_strings(StringPool* outPool, + Vector* outResIds) const; + + status_t flatten_node(const StringPool& strings, const sp& dest, + bool stripComments, bool stripRawValues) const; + + String16 mNamespacePrefix; + String16 mNamespaceUri; + String16 mElementName; + Vector > mChildren; + Vector mAttributes; + KeyedVector mAttributeOrder; + uint32_t mNextAttributeIndex; + String16 mChars; + Res_value mCharsValue; + String16 mComment; + String8 mFilename; + int32_t mStartLineNumber; + int32_t mEndLineNumber; +}; + +#endif diff --git a/printapk.cpp b/printapk.cpp new file mode 100644 index 0000000..4cf73d8 --- /dev/null +++ b/printapk.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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; tableIndexsize(); + for (size_t stringIndex=0; stringIndexstringAt(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 + + + diff --git a/tests/plurals/res/values/strings.xml b/tests/plurals/res/values/strings.xml new file mode 100644 index 0000000..1c1fc19 --- /dev/null +++ b/tests/plurals/res/values/strings.xml @@ -0,0 +1,7 @@ + + OK + + A dog + Some dogs + + diff --git a/tests/plurals/run.sh b/tests/plurals/run.sh new file mode 100755 index 0000000..4d39e10 --- /dev/null +++ b/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 -- 2.45.2