summaryrefslogtreecommitdiffstats
path: root/tools/aapt
diff options
context:
space:
mode:
Diffstat (limited to 'tools/aapt')
-rw-r--r--tools/aapt/AaptAssets.cpp1717
-rw-r--r--tools/aapt/AaptAssets.h519
-rw-r--r--tools/aapt/Android.mk52
-rw-r--r--tools/aapt/Bundle.h164
-rw-r--r--tools/aapt/Command.cpp841
-rw-r--r--tools/aapt/Images.cpp1082
-rw-r--r--tools/aapt/Images.h18
-rw-r--r--tools/aapt/Main.cpp369
-rw-r--r--tools/aapt/Main.h41
-rw-r--r--tools/aapt/Package.cpp464
-rw-r--r--tools/aapt/Resource.cpp1524
-rw-r--r--tools/aapt/ResourceTable.cpp3491
-rw-r--r--tools/aapt/ResourceTable.h534
-rw-r--r--tools/aapt/SourcePos.cpp171
-rw-r--r--tools/aapt/SourcePos.h28
-rw-r--r--tools/aapt/StringPool.cpp369
-rw-r--r--tools/aapt/StringPool.h148
-rw-r--r--tools/aapt/XMLNode.cpp1295
-rw-r--r--tools/aapt/XMLNode.h184
-rw-r--r--tools/aapt/printapk.cpp127
-rw-r--r--tools/aapt/tests/plurals/AndroidManifest.xml5
-rw-r--r--tools/aapt/tests/plurals/res/values/strings.xml7
-rwxr-xr-xtools/aapt/tests/plurals/run.sh16
23 files changed, 13166 insertions, 0 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
new file mode 100644
index 0000000..6bc1ee6
--- /dev/null
+++ b/tools/aapt/AaptAssets.cpp
@@ -0,0 +1,1717 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+
+#include "AaptAssets.h"
+#include "Main.h"
+
+#include <utils/misc.h>
+#include <utils/SortedVector.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+
+static const char* kDefaultLocale = "default";
+static const char* kWildcardName = "any";
+static const char* kAssetDir = "assets";
+static const char* kResourceDir = "res";
+static const char* kInvalidChars = "/\\:";
+static const size_t kMaxAssetFileName = 100;
+
+static const String8 kResString(kResourceDir);
+
+/*
+ * Names of asset files must meet the following criteria:
+ *
+ * - the filename length must be less than kMaxAssetFileName bytes long
+ * (and can't be empty)
+ * - all characters must be 7-bit printable ASCII
+ * - none of { '/' '\\' ':' }
+ *
+ * Pass in just the filename, not the full path.
+ */
+static bool validateFileName(const char* fileName)
+{
+ const char* cp = fileName;
+ size_t len = 0;
+
+ while (*cp != '\0') {
+ if ((*cp & 0x80) != 0)
+ return false; // reject high ASCII
+ if (*cp < 0x20 || *cp >= 0x7f)
+ return false; // reject control chars and 0x7f
+ if (strchr(kInvalidChars, *cp) != NULL)
+ return false; // reject path sep chars
+ cp++;
+ len++;
+ }
+
+ if (len < 1 || len > kMaxAssetFileName)
+ return false; // reject empty or too long
+
+ return true;
+}
+
+static bool isHidden(const char *root, const char *path)
+{
+ const char *type = NULL;
+
+ // Skip all hidden files.
+ if (path[0] == '.') {
+ // Skip ., .. and .svn but don't chatter about it.
+ if (strcmp(path, ".") == 0
+ || strcmp(path, "..") == 0
+ || strcmp(path, ".svn") == 0) {
+ return true;
+ }
+ type = "hidden";
+ } else if (path[0] == '_') {
+ // skip directories starting with _ (don't chatter about it)
+ String8 subdirName(root);
+ subdirName.appendPath(path);
+ if (getFileType(subdirName.string()) == kFileTypeDirectory) {
+ return true;
+ }
+ } else if (strcmp(path, "CVS") == 0) {
+ // Skip CVS but don't chatter about it.
+ return true;
+ } else if (strcasecmp(path, "thumbs.db") == 0
+ || strcasecmp(path, "picasa.ini") == 0) {
+ // Skip suspected image indexes files.
+ type = "index";
+ } else if (path[strlen(path)-1] == '~') {
+ // Skip suspected emacs backup files.
+ type = "backup";
+ } else {
+ // Let everything else through.
+ return false;
+ }
+
+ /* If we get this far, "type" should be set and the file
+ * should be skipped.
+ */
+ String8 subdirName(root);
+ subdirName.appendPath(path);
+ fprintf(stderr, " (skipping %s %s '%s')\n", type,
+ getFileType(subdirName.string())==kFileTypeDirectory ? "dir":"file",
+ subdirName.string());
+
+ return true;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t
+AaptGroupEntry::parseNamePart(const String8& part, int* axis, uint32_t* value)
+{
+ ResTable_config config;
+
+ // IMSI - MCC
+ if (getMccName(part.string(), &config)) {
+ *axis = AXIS_MCC;
+ *value = config.mcc;
+ return 0;
+ }
+
+ // IMSI - MNC
+ if (getMncName(part.string(), &config)) {
+ *axis = AXIS_MNC;
+ *value = config.mnc;
+ return 0;
+ }
+
+ // locale - language
+ if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) {
+ *axis = AXIS_LANGUAGE;
+ *value = part[1] << 8 | part[0];
+ return 0;
+ }
+
+ // locale - language_REGION
+ if (part.length() == 5 && isalpha(part[0]) && isalpha(part[1])
+ && part[2] == '_' && isalpha(part[3]) && isalpha(part[4])) {
+ *axis = AXIS_LANGUAGE;
+ *value = (part[4] << 24) | (part[3] << 16) | (part[1] << 8) | (part[0]);
+ return 0;
+ }
+
+ // orientation
+ if (getOrientationName(part.string(), &config)) {
+ *axis = AXIS_ORIENTATION;
+ *value = config.orientation;
+ return 0;
+ }
+
+ // density
+ if (getDensityName(part.string(), &config)) {
+ *axis = AXIS_DENSITY;
+ *value = config.density;
+ return 0;
+ }
+
+ // touchscreen
+ if (getTouchscreenName(part.string(), &config)) {
+ *axis = AXIS_TOUCHSCREEN;
+ *value = config.touchscreen;
+ return 0;
+ }
+
+ // keyboard hidden
+ if (getKeysHiddenName(part.string(), &config)) {
+ *axis = AXIS_KEYSHIDDEN;
+ *value = config.inputFlags;
+ return 0;
+ }
+
+ // keyboard
+ if (getKeyboardName(part.string(), &config)) {
+ *axis = AXIS_KEYBOARD;
+ *value = config.keyboard;
+ return 0;
+ }
+
+ // navigation
+ if (getNavigationName(part.string(), &config)) {
+ *axis = AXIS_NAVIGATION;
+ *value = config.navigation;
+ return 0;
+ }
+
+ // screen size
+ if (getScreenSizeName(part.string(), &config)) {
+ *axis = AXIS_SCREENSIZE;
+ *value = config.screenSize;
+ return 0;
+ }
+
+ // version
+ if (getVersionName(part.string(), &config)) {
+ *axis = AXIS_VERSION;
+ *value = config.version;
+ return 0;
+ }
+
+ return 1;
+}
+
+bool
+AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
+{
+ Vector<String8> parts;
+
+ String8 mcc, mnc, loc, orient, den, touch, key, keysHidden, nav, size, vers;
+
+ const char *p = dir;
+ const char *q;
+ while (NULL != (q = strchr(p, '-'))) {
+ String8 val(p, q-p);
+ val.toLower();
+ parts.add(val);
+ //printf("part: %s\n", parts[parts.size()-1].string());
+ p = q+1;
+ }
+ String8 val(p);
+ val.toLower();
+ parts.add(val);
+ //printf("part: %s\n", parts[parts.size()-1].string());
+
+ const int N = parts.size();
+ int index = 0;
+ String8 part = parts[index];
+
+ // resource type
+ if (!isValidResourceType(part)) {
+ return false;
+ }
+ *resType = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+
+ // imsi - mcc
+ if (getMccName(part.string())) {
+ mcc = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not mcc: %s\n", part.string());
+ }
+
+ // imsi - mnc
+ if (getMncName(part.string())) {
+ mnc = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not mcc: %s\n", part.string());
+ }
+
+ // locale - language
+ if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) {
+ loc = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not language: %s\n", part.string());
+ }
+
+ // locale - region
+ if (loc.length() > 0
+ && part.length() == 3 && part[0] == 'r' && part[0] && part[1]) {
+ loc += "-";
+ part.toUpper();
+ loc += part.string() + 1;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not region: %s\n", part.string());
+ }
+
+ // orientation
+ if (getOrientationName(part.string())) {
+ orient = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not orientation: %s\n", part.string());
+ }
+
+ // density
+ if (getDensityName(part.string())) {
+ den = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not density: %s\n", part.string());
+ }
+
+ // touchscreen
+ if (getTouchscreenName(part.string())) {
+ touch = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not touchscreen: %s\n", part.string());
+ }
+
+ // keyboard hidden
+ if (getKeysHiddenName(part.string())) {
+ keysHidden = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not keysHidden: %s\n", part.string());
+ }
+
+ // keyboard
+ if (getKeyboardName(part.string())) {
+ key = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not keyboard: %s\n", part.string());
+ }
+
+ if (getNavigationName(part.string())) {
+ nav = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not navigation: %s\n", part.string());
+ }
+
+ if (getScreenSizeName(part.string())) {
+ size = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not screen size: %s\n", part.string());
+ }
+
+ if (getVersionName(part.string())) {
+ vers = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not version: %s\n", part.string());
+ }
+
+ // if there are extra parts, it doesn't match
+ return false;
+
+success:
+ this->mcc = mcc;
+ this->mnc = mnc;
+ this->locale = loc;
+ this->orientation = orient;
+ this->density = den;
+ this->touchscreen = touch;
+ this->keysHidden = keysHidden;
+ this->keyboard = key;
+ this->navigation = nav;
+ this->screenSize = size;
+ this->version = vers;
+
+ // what is this anyway?
+ this->vendor = "";
+
+ return true;
+}
+
+String8
+AaptGroupEntry::toString() const
+{
+ String8 s = this->mcc;
+ s += ",";
+ s += this->mnc;
+ s += ",";
+ s += this->locale;
+ s += ",";
+ s += this->orientation;
+ s += ",";
+ s += density;
+ s += ",";
+ s += touchscreen;
+ s += ",";
+ s += keysHidden;
+ s += ",";
+ s += keyboard;
+ s += ",";
+ s += navigation;
+ s += ",";
+ s += screenSize;
+ s += ",";
+ s += version;
+ return s;
+}
+
+String8
+AaptGroupEntry::toDirName(const String8& resType) const
+{
+ String8 s = resType;
+ if (this->mcc != "") {
+ s += "-";
+ s += mcc;
+ }
+ if (this->mnc != "") {
+ s += "-";
+ s += mnc;
+ }
+ if (this->locale != "") {
+ s += "-";
+ s += locale;
+ }
+ if (this->orientation != "") {
+ s += "-";
+ s += orientation;
+ }
+ if (this->density != "") {
+ s += "-";
+ s += density;
+ }
+ if (this->touchscreen != "") {
+ s += "-";
+ s += touchscreen;
+ }
+ if (this->keysHidden != "") {
+ s += "-";
+ s += keysHidden;
+ }
+ if (this->keyboard != "") {
+ s += "-";
+ s += keyboard;
+ }
+ if (this->navigation != "") {
+ s += "-";
+ s += navigation;
+ }
+ if (this->screenSize != "") {
+ s += "-";
+ s += screenSize;
+ }
+ if (this->version != "") {
+ s += "-";
+ s += version;
+ }
+
+ return s;
+}
+
+bool AaptGroupEntry::getMccName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+ if (*c != 0) return false;
+ if (c-val != 3) return false;
+
+ int d = atoi(val);
+ if (d != 0) {
+ if (out) out->mcc = d;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getMncName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'n') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+ if (*c != 0) return false;
+ if (c-val == 0 || c-val > 3) return false;
+
+ int d = atoi(val);
+ if (d != 0) {
+ if (out) out->mnc = d;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Does this directory name fit the pattern of a locale dir ("en-rUS" or
+ * "default")?
+ *
+ * TODO: Should insist that the first two letters are lower case, and the
+ * second two are upper.
+ */
+bool AaptGroupEntry::getLocaleName(const char* fileName,
+ ResTable_config* out)
+{
+ if (strcmp(fileName, kWildcardName) == 0
+ || strcmp(fileName, kDefaultLocale) == 0) {
+ if (out) {
+ out->language[0] = 0;
+ out->language[1] = 0;
+ out->country[0] = 0;
+ out->country[1] = 0;
+ }
+ return true;
+ }
+
+ if (strlen(fileName) == 2 && isalpha(fileName[0]) && isalpha(fileName[1])) {
+ if (out) {
+ out->language[0] = fileName[0];
+ out->language[1] = fileName[1];
+ out->country[0] = 0;
+ out->country[1] = 0;
+ }
+ return true;
+ }
+
+ if (strlen(fileName) == 5 &&
+ isalpha(fileName[0]) &&
+ isalpha(fileName[1]) &&
+ fileName[2] == '-' &&
+ isalpha(fileName[3]) &&
+ isalpha(fileName[4])) {
+ if (out) {
+ out->language[0] = fileName[0];
+ out->language[1] = fileName[1];
+ out->country[0] = fileName[3];
+ out->country[1] = fileName[4];
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getOrientationName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->orientation = out->ORIENTATION_ANY;
+ return true;
+ } else if (strcmp(name, "port") == 0) {
+ if (out) out->orientation = out->ORIENTATION_PORT;
+ return true;
+ } else if (strcmp(name, "land") == 0) {
+ if (out) out->orientation = out->ORIENTATION_LAND;
+ return true;
+ } else if (strcmp(name, "square") == 0) {
+ if (out) out->orientation = out->ORIENTATION_SQUARE;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getDensityName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->density = 0;
+ return true;
+ }
+ char* c = (char*)name;
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+
+ // check that we have 'dpi' after the last digit.
+ if (toupper(c[0]) != 'D' ||
+ toupper(c[1]) != 'P' ||
+ toupper(c[2]) != 'I' ||
+ c[3] != 0) {
+ return false;
+ }
+
+ // temporarily replace the first letter with \0 to
+ // use atoi.
+ char tmp = c[0];
+ c[0] = '\0';
+
+ int d = atoi(name);
+ c[0] = tmp;
+
+ if (d != 0) {
+ if (out) out->density = d;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getTouchscreenName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+ return true;
+ } else if (strcmp(name, "notouch") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+ return true;
+ } else if (strcmp(name, "stylus") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+ return true;
+ } else if (strcmp(name, "finger") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getKeysHiddenName(const char* name,
+ ResTable_config* out)
+{
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = out->MASK_KEYSHIDDEN;
+ value = out->KEYSHIDDEN_ANY;
+ } else if (strcmp(name, "keysexposed") == 0) {
+ mask = out->MASK_KEYSHIDDEN;
+ value = out->KEYSHIDDEN_NO;
+ } else if (strcmp(name, "keyshidden") == 0) {
+ mask = out->MASK_KEYSHIDDEN;
+ value = out->KEYSHIDDEN_YES;
+ } else if (strcmp(name, "keyssoft") == 0) {
+ mask = out->MASK_KEYSHIDDEN;
+ value = out->KEYSHIDDEN_SOFT;
+ }
+
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getKeyboardName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->keyboard = out->KEYBOARD_ANY;
+ return true;
+ } else if (strcmp(name, "nokeys") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+ return true;
+ } else if (strcmp(name, "qwerty") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_QWERTY;
+ return true;
+ } else if (strcmp(name, "12key") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_12KEY;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getNavigationName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->navigation = out->NAVIGATION_ANY;
+ return true;
+ } else if (strcmp(name, "nonav") == 0) {
+ if (out) out->navigation = out->NAVIGATION_NONAV;
+ return true;
+ } else if (strcmp(name, "dpad") == 0) {
+ if (out) out->navigation = out->NAVIGATION_DPAD;
+ return true;
+ } else if (strcmp(name, "trackball") == 0) {
+ if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+ return true;
+ } else if (strcmp(name, "wheel") == 0) {
+ if (out) out->navigation = out->NAVIGATION_WHEEL;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getScreenSizeName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->screenWidth = out->SCREENWIDTH_ANY;
+ out->screenHeight = out->SCREENHEIGHT_ANY;
+ }
+ return true;
+ }
+
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || *x != 'x') return false;
+ String8 xName(name, x-name);
+ x++;
+
+ const char* y = x;
+ while (*y >= '0' && *y <= '9') y++;
+ if (y == name || *y != 0) return false;
+ String8 yName(x, y-x);
+
+ uint16_t w = (uint16_t)atoi(xName.string());
+ uint16_t h = (uint16_t)atoi(yName.string());
+ if (w < h) {
+ return false;
+ }
+
+ if (out) {
+ out->screenWidth = w;
+ out->screenHeight = h;
+ }
+
+ return true;
+}
+
+bool AaptGroupEntry::getVersionName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->sdkVersion = out->SDKVERSION_ANY;
+ out->minorVersion = out->MINORVERSION_ANY;
+ }
+ return true;
+ }
+
+ if (*name != 'v') {
+ return false;
+ }
+
+ name++;
+ const char* s = name;
+ while (*s >= '0' && *s <= '9') s++;
+ if (s == name || *s != 0) return false;
+ String8 sdkName(name, s-name);
+
+ if (out) {
+ out->sdkVersion = (uint16_t)atoi(sdkName.string());
+ out->minorVersion = 0;
+ }
+
+ return true;
+}
+
+int AaptGroupEntry::compare(const AaptGroupEntry& o) const
+{
+ int v = mcc.compare(o.mcc);
+ if (v == 0) v = mnc.compare(o.mnc);
+ if (v == 0) v = locale.compare(o.locale);
+ if (v == 0) v = vendor.compare(o.vendor);
+ if (v == 0) v = orientation.compare(o.orientation);
+ if (v == 0) v = density.compare(o.density);
+ if (v == 0) v = touchscreen.compare(o.touchscreen);
+ if (v == 0) v = keysHidden.compare(o.keysHidden);
+ if (v == 0) v = keyboard.compare(o.keyboard);
+ if (v == 0) v = navigation.compare(o.navigation);
+ if (v == 0) v = screenSize.compare(o.screenSize);
+ if (v == 0) v = version.compare(o.version);
+ return v;
+}
+
+ResTable_config AaptGroupEntry::toParams() const
+{
+ ResTable_config params;
+ memset(&params, 0, sizeof(params));
+ getMccName(mcc.string(), &params);
+ getMncName(mnc.string(), &params);
+ getLocaleName(locale.string(), &params);
+ getOrientationName(orientation.string(), &params);
+ getDensityName(density.string(), &params);
+ getTouchscreenName(touchscreen.string(), &params);
+ getKeysHiddenName(keysHidden.string(), &params);
+ getKeyboardName(keyboard.string(), &params);
+ getNavigationName(navigation.string(), &params);
+ getScreenSizeName(screenSize.string(), &params);
+ getVersionName(version.string(), &params);
+ return params;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+void* AaptFile::editData(size_t size)
+{
+ if (size <= mBufferSize) {
+ mDataSize = size;
+ return mData;
+ }
+ size_t allocSize = (size*3)/2;
+ void* buf = realloc(mData, allocSize);
+ if (buf == NULL) {
+ return NULL;
+ }
+ mData = buf;
+ mDataSize = size;
+ mBufferSize = allocSize;
+ return buf;
+}
+
+void* AaptFile::editData(size_t* outSize)
+{
+ if (outSize) {
+ *outSize = mDataSize;
+ }
+ return mData;
+}
+
+void* AaptFile::padData(size_t wordSize)
+{
+ const size_t extra = mDataSize%wordSize;
+ if (extra == 0) {
+ return mData;
+ }
+
+ size_t initial = mDataSize;
+ void* data = editData(initial+(wordSize-extra));
+ if (data != NULL) {
+ memset(((uint8_t*)data) + initial, 0, wordSize-extra);
+ }
+ return data;
+}
+
+status_t AaptFile::writeData(const void* data, size_t size)
+{
+ size_t end = mDataSize;
+ size_t total = size + end;
+ void* buf = editData(total);
+ if (buf == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ memcpy(((char*)buf)+end, data, size);
+ return NO_ERROR;
+}
+
+void AaptFile::clearData()
+{
+ if (mData != NULL) free(mData);
+ mData = NULL;
+ mDataSize = 0;
+ mBufferSize = 0;
+}
+
+String8 AaptFile::getPrintableSource() const
+{
+ if (hasData()) {
+ String8 name(mGroupEntry.locale.string());
+ name.appendPath(mGroupEntry.vendor.string());
+ name.appendPath(mPath);
+ name.append(" #generated");
+ return name;
+ }
+ return mSourceFile;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptGroup::addFile(const sp<AaptFile>& file)
+{
+ if (mFiles.indexOfKey(file->getGroupEntry()) < 0) {
+ file->mPath = mPath;
+ mFiles.add(file->getGroupEntry(), file);
+ return NO_ERROR;
+ }
+
+ SourcePos(file->getSourceFile(), -1).error("Duplicate file.\n%s: Original is here.",
+ getPrintableSource().string());
+ return UNKNOWN_ERROR;
+}
+
+void AaptGroup::removeFile(size_t index)
+{
+ mFiles.removeItemsAt(index);
+}
+
+void AaptGroup::print() const
+{
+ printf(" %s\n", getPath().string());
+ const size_t N=mFiles.size();
+ size_t i;
+ for (i=0; i<N; i++) {
+ sp<AaptFile> file = mFiles.valueAt(i);
+ const AaptGroupEntry& e = file->getGroupEntry();
+ if (file->hasData()) {
+ printf(" Gen: (%s) %d bytes\n", e.toString().string(),
+ (int)file->getSize());
+ } else {
+ printf(" Src: %s\n", file->getPrintableSource().string());
+ }
+ }
+}
+
+String8 AaptGroup::getPrintableSource() const
+{
+ if (mFiles.size() > 0) {
+ // Arbitrarily pull the first source file out of the list.
+ return mFiles.valueAt(0)->getPrintableSource();
+ }
+
+ // Should never hit this case, but to be safe...
+ return getPath();
+
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file)
+{
+ if (mFiles.indexOfKey(name) >= 0) {
+ return ALREADY_EXISTS;
+ }
+ mFiles.add(name, file);
+ return NO_ERROR;
+}
+
+status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir)
+{
+ if (mDirs.indexOfKey(name) >= 0) {
+ return ALREADY_EXISTS;
+ }
+ mDirs.add(name, dir);
+ return NO_ERROR;
+}
+
+sp<AaptDir> AaptDir::makeDir(const String8& path)
+{
+ String8 name;
+ String8 remain = path;
+
+ sp<AaptDir> subdir = this;
+ while (name = remain.walkPath(&remain), remain != "") {
+ subdir = subdir->makeDir(name);
+ }
+
+ ssize_t i = subdir->mDirs.indexOfKey(name);
+ if (i >= 0) {
+ return subdir->mDirs.valueAt(i);
+ }
+ sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
+ subdir->mDirs.add(name, dir);
+ return dir;
+}
+
+void AaptDir::removeFile(const String8& name)
+{
+ mFiles.removeItem(name);
+}
+
+void AaptDir::removeDir(const String8& name)
+{
+ mDirs.removeItem(name);
+}
+
+status_t AaptDir::renameFile(const sp<AaptFile>& file, const String8& newName)
+{
+ sp<AaptGroup> origGroup;
+
+ // Find and remove the given file with shear, brute force!
+ const size_t NG = mFiles.size();
+ size_t i;
+ for (i=0; origGroup == NULL && i<NG; i++) {
+ sp<AaptGroup> g = mFiles.valueAt(i);
+ const size_t NF = g->getFiles().size();
+ for (size_t j=0; j<NF; j++) {
+ if (g->getFiles().valueAt(j) == file) {
+ origGroup = g;
+ g->removeFile(j);
+ if (NF == 1) {
+ mFiles.removeItemsAt(i);
+ }
+ break;
+ }
+ }
+ }
+
+ //printf("Renaming %s to %s\n", file->getPath().getPathName(), newName.string());
+
+ // Place the file under its new name.
+ if (origGroup != NULL) {
+ return addLeafFile(newName, file);
+ }
+
+ return NO_ERROR;
+}
+
+status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file)
+{
+ sp<AaptGroup> group;
+ if (mFiles.indexOfKey(leafName) >= 0) {
+ group = mFiles.valueFor(leafName);
+ } else {
+ group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
+ mFiles.add(leafName, group);
+ }
+
+ return group->addFile(file);
+}
+
+ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
+ const AaptGroupEntry& kind, const String8& resType)
+{
+ Vector<String8> fileNames;
+
+ {
+ DIR* dir = NULL;
+
+ dir = opendir(srcDir.string());
+ if (dir == NULL) {
+ fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ /*
+ * Slurp the filenames out of the directory.
+ */
+ while (1) {
+ struct dirent* entry;
+
+ entry = readdir(dir);
+ if (entry == NULL)
+ break;
+
+ if (isHidden(srcDir.string(), entry->d_name))
+ continue;
+
+ fileNames.add(String8(entry->d_name));
+ }
+
+ closedir(dir);
+ }
+
+ ssize_t count = 0;
+
+ /*
+ * Stash away the files and recursively descend into subdirectories.
+ */
+ const size_t N = fileNames.size();
+ size_t i;
+ for (i = 0; i < N; i++) {
+ String8 pathName(srcDir);
+ FileType type;
+
+ pathName.appendPath(fileNames[i].string());
+ type = getFileType(pathName.string());
+ if (type == kFileTypeDirectory) {
+ sp<AaptDir> subdir;
+ bool notAdded = false;
+ if (mDirs.indexOfKey(fileNames[i]) >= 0) {
+ subdir = mDirs.valueFor(fileNames[i]);
+ } else {
+ subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
+ notAdded = true;
+ }
+ ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
+ resType);
+ if (res < NO_ERROR) {
+ return res;
+ }
+ if (res > 0 && notAdded) {
+ mDirs.add(fileNames[i], subdir);
+ }
+ count += res;
+ } else if (type == kFileTypeRegular) {
+ sp<AaptFile> file = new AaptFile(pathName, kind, resType);
+ status_t err = addLeafFile(fileNames[i], file);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ count++;
+
+ } else {
+ if (bundle->getVerbose())
+ printf(" (ignoring non-file/dir '%s')\n", pathName.string());
+ }
+ }
+
+ return count;
+}
+
+status_t AaptDir::validate() const
+{
+ const size_t NF = mFiles.size();
+ const size_t ND = mDirs.size();
+ size_t i;
+ for (i = 0; i < NF; i++) {
+ if (!validateFileName(mFiles.valueAt(i)->getLeaf().string())) {
+ SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+ "Invalid filename. Unable to add.");
+ return UNKNOWN_ERROR;
+ }
+
+ size_t j;
+ for (j = i+1; j < NF; j++) {
+ if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
+ mFiles.valueAt(j)->getLeaf().string()) == 0) {
+ SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+ "File is case-insensitive equivalent to: %s",
+ mFiles.valueAt(j)->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+
+ // TODO: if ".gz", check for non-.gz; if non-, check for ".gz"
+ // (this is mostly caught by the "marked" stuff, below)
+ }
+
+ for (j = 0; j < ND; j++) {
+ if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
+ mDirs.valueAt(j)->getLeaf().string()) == 0) {
+ SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+ "File conflicts with dir from: %s",
+ mDirs.valueAt(j)->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+
+ for (i = 0; i < ND; i++) {
+ if (!validateFileName(mDirs.valueAt(i)->getLeaf().string())) {
+ SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
+ "Invalid directory name, unable to add.");
+ return UNKNOWN_ERROR;
+ }
+
+ size_t j;
+ for (j = i+1; j < ND; j++) {
+ if (strcasecmp(mDirs.valueAt(i)->getLeaf().string(),
+ mDirs.valueAt(j)->getLeaf().string()) == 0) {
+ SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
+ "Directory is case-insensitive equivalent to: %s",
+ mDirs.valueAt(j)->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ status_t err = mDirs.valueAt(i)->validate();
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+void AaptDir::print() const
+{
+ const size_t ND=getDirs().size();
+ size_t i;
+ for (i=0; i<ND; i++) {
+ getDirs().valueAt(i)->print();
+ }
+
+ const size_t NF=getFiles().size();
+ for (i=0; i<NF; i++) {
+ getFiles().valueAt(i)->print();
+ }
+}
+
+String8 AaptDir::getPrintableSource() const
+{
+ if (mFiles.size() > 0) {
+ // Arbitrarily pull the first file out of the list as the source dir.
+ return mFiles.valueAt(0)->getPrintableSource().getPathDir();
+ }
+ if (mDirs.size() > 0) {
+ // Or arbitrarily pull the first dir out of the list as the source dir.
+ return mDirs.valueAt(0)->getPrintableSource().getPathDir();
+ }
+
+ // Should never hit this case, but to be safe...
+ return mPath;
+
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+sp<AaptFile> AaptAssets::addFile(
+ const String8& filePath, const AaptGroupEntry& entry,
+ const String8& srcDir, sp<AaptGroup>* outGroup,
+ const String8& resType)
+{
+ sp<AaptDir> dir = this;
+ sp<AaptGroup> group;
+ sp<AaptFile> file;
+ String8 root, remain(filePath), partialPath;
+ while (remain.length() > 0) {
+ root = remain.walkPath(&remain);
+ partialPath.appendPath(root);
+
+ const String8 rootStr(root);
+
+ if (remain.length() == 0) {
+ ssize_t i = dir->getFiles().indexOfKey(rootStr);
+ if (i >= 0) {
+ group = dir->getFiles().valueAt(i);
+ } else {
+ group = new AaptGroup(rootStr, filePath);
+ status_t res = dir->addFile(rootStr, group);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+ }
+ file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
+ status_t res = group->addFile(file);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+ break;
+
+ } else {
+ ssize_t i = dir->getDirs().indexOfKey(rootStr);
+ if (i >= 0) {
+ dir = dir->getDirs().valueAt(i);
+ } else {
+ sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
+ status_t res = dir->addDir(rootStr, subdir);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+ dir = subdir;
+ }
+ }
+ }
+
+ mGroupEntries.add(entry);
+ if (outGroup) *outGroup = group;
+ return file;
+}
+
+void AaptAssets::addResource(const String8& leafName, const String8& path,
+ const sp<AaptFile>& file, const String8& resType)
+{
+ sp<AaptDir> res = AaptDir::makeDir(kResString);
+ String8 dirname = file->getGroupEntry().toDirName(resType);
+ sp<AaptDir> subdir = res->makeDir(dirname);
+ sp<AaptGroup> grr = new AaptGroup(leafName, path);
+ grr->addFile(file);
+
+ subdir->addFile(leafName, grr);
+}
+
+
+ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
+{
+ int count;
+ int totalCount = 0;
+ FileType type;
+ const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
+ const size_t dirCount =resDirs.size();
+ sp<AaptAssets> current = this;
+
+ const int N = bundle->getFileSpecCount();
+
+ /*
+ * If a package manifest was specified, include that first.
+ */
+ if (bundle->getAndroidManifestFile() != NULL) {
+ // place at root of zip.
+ String8 srcFile(bundle->getAndroidManifestFile());
+ addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
+ NULL, String8());
+ totalCount++;
+ }
+
+ /*
+ * If a directory of custom assets was supplied, slurp 'em up.
+ */
+ if (bundle->getAssetSourceDir()) {
+ const char* assetDir = bundle->getAssetSourceDir();
+
+ FileType type = getFileType(assetDir);
+ if (type == kFileTypeNonexistent) {
+ fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+ if (type != kFileTypeDirectory) {
+ fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+
+ String8 assetRoot(assetDir);
+ sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
+ AaptGroupEntry group;
+ count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
+ String8());
+ if (count < 0) {
+ totalCount = count;
+ goto bail;
+ }
+ if (count > 0) {
+ mGroupEntries.add(group);
+ }
+ totalCount += count;
+
+ if (bundle->getVerbose())
+ printf("Found %d custom asset file%s in %s\n",
+ count, (count==1) ? "" : "s", assetDir);
+ }
+
+ /*
+ * If a directory of resource-specific assets was supplied, slurp 'em up.
+ */
+ for (size_t i=0; i<dirCount; i++) {
+ const char *res = resDirs[i];
+ if (res) {
+ type = getFileType(res);
+ if (type == kFileTypeNonexistent) {
+ fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
+ return UNKNOWN_ERROR;
+ }
+ if (type == kFileTypeDirectory) {
+ if (i>0) {
+ sp<AaptAssets> nextOverlay = new AaptAssets();
+ current->setOverlay(nextOverlay);
+ current = nextOverlay;
+ }
+ count = current->slurpResourceTree(bundle, String8(res));
+
+ if (count < 0) {
+ totalCount = count;
+ goto bail;
+ }
+ totalCount += count;
+ }
+ else {
+ fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ }
+ /*
+ * Now do any additional raw files.
+ */
+ for (int arg=0; arg<N; arg++) {
+ const char* assetDir = bundle->getFileSpecEntry(arg);
+
+ FileType type = getFileType(assetDir);
+ if (type == kFileTypeNonexistent) {
+ fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+ if (type != kFileTypeDirectory) {
+ fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+
+ String8 assetRoot(assetDir);
+
+ if (bundle->getVerbose())
+ printf("Processing raw dir '%s'\n", (const char*) assetDir);
+
+ /*
+ * Do a recursive traversal of subdir tree. We don't make any
+ * guarantees about ordering, so we're okay with an inorder search
+ * using whatever order the OS happens to hand back to us.
+ */
+ count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8());
+ if (count < 0) {
+ /* failure; report error and remove archive */
+ totalCount = count;
+ goto bail;
+ }
+ totalCount += count;
+
+ if (bundle->getVerbose())
+ printf("Found %d asset file%s in %s\n",
+ count, (count==1) ? "" : "s", assetDir);
+ }
+
+ count = validate();
+ if (count != NO_ERROR) {
+ totalCount = count;
+ goto bail;
+ }
+
+
+bail:
+ return totalCount;
+}
+
+ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
+ const AaptGroupEntry& kind,
+ const String8& resType)
+{
+ ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType);
+ if (res > 0) {
+ mGroupEntries.add(kind);
+ }
+
+ return res;
+}
+
+ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
+{
+ ssize_t err = 0;
+
+ DIR* dir = opendir(srcDir.string());
+ if (dir == NULL) {
+ fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ status_t count = 0;
+
+ /*
+ * Run through the directory, looking for dirs that match the
+ * expected pattern.
+ */
+ while (1) {
+ struct dirent* entry = readdir(dir);
+ if (entry == NULL) {
+ break;
+ }
+
+ if (isHidden(srcDir.string(), entry->d_name)) {
+ continue;
+ }
+
+ String8 subdirName(srcDir);
+ subdirName.appendPath(entry->d_name);
+
+ AaptGroupEntry group;
+ String8 resType;
+ bool b = group.initFromDirName(entry->d_name, &resType);
+ if (!b) {
+ fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(),
+ entry->d_name);
+ err = -1;
+ continue;
+ }
+
+ FileType type = getFileType(subdirName.string());
+
+ if (type == kFileTypeDirectory) {
+ sp<AaptDir> dir = makeDir(String8(entry->d_name));
+ ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
+ resType);
+ if (res < 0) {
+ count = res;
+ goto bail;
+ }
+ if (res > 0) {
+ mGroupEntries.add(group);
+ count += res;
+ }
+
+ mDirs.add(dir);
+ } else {
+ if (bundle->getVerbose()) {
+ fprintf(stderr, " (ignoring file '%s')\n", subdirName.string());
+ }
+ }
+ }
+
+bail:
+ closedir(dir);
+ dir = NULL;
+
+ if (err != 0) {
+ return err;
+ }
+ return count;
+}
+
+ssize_t
+AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename)
+{
+ int count = 0;
+ SortedVector<AaptGroupEntry> entries;
+
+ ZipFile* zip = new ZipFile;
+ status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "error opening zip file %s\n", filename);
+ count = err;
+ delete zip;
+ return -1;
+ }
+
+ const int N = zip->getNumEntries();
+ for (int i=0; i<N; i++) {
+ ZipEntry* entry = zip->getEntryByIndex(i);
+ if (entry->getDeleted()) {
+ continue;
+ }
+
+ String8 entryName(entry->getFileName());
+
+ String8 dirName = entryName.getPathDir();
+ sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
+
+ String8 resType;
+ AaptGroupEntry kind;
+
+ String8 remain;
+ if (entryName.walkPath(&remain) == kResourceDir) {
+ // these are the resources, pull their type out of the directory name
+ kind.initFromDirName(remain.walkPath().string(), &resType);
+ } else {
+ // these are untyped and don't have an AaptGroupEntry
+ }
+ if (entries.indexOf(kind) < 0) {
+ entries.add(kind);
+ mGroupEntries.add(kind);
+ }
+
+ // use the one from the zip file if they both exist.
+ dir->removeFile(entryName.getPathLeaf());
+
+ sp<AaptFile> file = new AaptFile(entryName, kind, resType);
+ status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
+ count = err;
+ goto bail;
+ }
+ file->setCompressionMethod(entry->getCompressionMethod());
+
+#if 0
+ if (entryName == "AndroidManifest.xml") {
+ printf("AndroidManifest.xml\n");
+ }
+ printf("\n\nfile: %s\n", entryName.string());
+#endif
+
+ size_t len = entry->getUncompressedLen();
+ void* data = zip->uncompress(entry);
+ void* buf = file->editData(len);
+ memcpy(buf, data, len);
+
+#if 0
+ const int OFF = 0;
+ const unsigned char* p = (unsigned char*)data;
+ const unsigned char* end = p+len;
+ p += OFF;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%03x ", i*0x10 + OFF);
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ free(data);
+
+ count++;
+ }
+
+bail:
+ delete zip;
+ return count;
+}
+
+sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name)
+{
+ sp<AaptSymbols> sym = mSymbols.valueFor(name);
+ if (sym == NULL) {
+ sym = new AaptSymbols();
+ mSymbols.add(name, sym);
+ }
+ return sym;
+}
+
+status_t AaptAssets::buildIncludedResources(Bundle* bundle)
+{
+ if (!mHaveIncludedAssets) {
+ // Add in all includes.
+ const Vector<const char*>& incl = bundle->getPackageIncludes();
+ const size_t N=incl.size();
+ for (size_t i=0; i<N; i++) {
+ if (bundle->getVerbose())
+ printf("Including resources from package: %s\n", incl[i]);
+ if (!mIncludedAssets.addAssetPath(String8(incl[i]), NULL)) {
+ fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
+ incl[i]);
+ return UNKNOWN_ERROR;
+ }
+ }
+ mHaveIncludedAssets = true;
+ }
+
+ return NO_ERROR;
+}
+
+status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file)
+{
+ const ResTable& res = getIncludedResources();
+ // XXX dirty!
+ return const_cast<ResTable&>(res).add(file->getData(), file->getSize(), NULL);
+}
+
+const ResTable& AaptAssets::getIncludedResources() const
+{
+ return mIncludedAssets.getResources(false);
+}
+
+void AaptAssets::print() const
+{
+ printf("Locale/Vendor pairs:\n");
+ const size_t N=mGroupEntries.size();
+ for (size_t i=0; i<N; i++) {
+ printf(" %s/%s\n",
+ mGroupEntries.itemAt(i).locale.string(),
+ mGroupEntries.itemAt(i).vendor.string());
+ }
+
+ printf("\nFiles:\n");
+ AaptDir::print();
+}
+
+bool
+valid_symbol_name(const String8& symbol)
+{
+ static char const * const KEYWORDS[] = {
+ "abstract", "assert", "boolean", "break",
+ "byte", "case", "catch", "char", "class", "const", "continue",
+ "default", "do", "double", "else", "enum", "extends", "final",
+ "finally", "float", "for", "goto", "if", "implements", "import",
+ "instanceof", "int", "interface", "long", "native", "new", "package",
+ "private", "protected", "public", "return", "short", "static",
+ "strictfp", "super", "switch", "synchronized", "this", "throw",
+ "throws", "transient", "try", "void", "volatile", "while",
+ "true", "false", "null",
+ NULL
+ };
+ const char*const* k = KEYWORDS;
+ const char*const s = symbol.string();
+ while (*k) {
+ if (0 == strcmp(s, *k)) {
+ return false;
+ }
+ k++;
+ }
+ return true;
+}
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
new file mode 100644
index 0000000..01c8140
--- /dev/null
+++ b/tools/aapt/AaptAssets.h
@@ -0,0 +1,519 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Information about assets being operated on.
+//
+#ifndef __AAPT_ASSETS_H
+#define __AAPT_ASSETS_H
+
+#include <stdlib.h>
+#include <utils/AssetManager.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/ResourceTypes.h>
+#include <utils/SortedVector.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/RefBase.h>
+#include <utils/ZipFile.h>
+
+#include "Bundle.h"
+#include "SourcePos.h"
+
+using namespace android;
+
+bool valid_symbol_name(const String8& str);
+
+enum {
+ AXIS_NONE = 0,
+ AXIS_MCC = 1,
+ AXIS_MNC,
+ AXIS_LANGUAGE,
+ AXIS_REGION,
+ AXIS_ORIENTATION,
+ AXIS_DENSITY,
+ AXIS_TOUCHSCREEN,
+ AXIS_KEYSHIDDEN,
+ AXIS_KEYBOARD,
+ AXIS_NAVIGATION,
+ AXIS_SCREENSIZE,
+ AXIS_VERSION
+};
+
+/**
+ * This structure contains a specific variation of a single file out
+ * of all the variations it can have that we can have.
+ */
+struct AaptGroupEntry
+{
+public:
+ AaptGroupEntry() { }
+ AaptGroupEntry(const String8& _locale, const String8& _vendor)
+ : locale(_locale), vendor(_vendor) { }
+
+ String8 mcc;
+ String8 mnc;
+ String8 locale;
+ String8 vendor;
+ String8 orientation;
+ String8 density;
+ String8 touchscreen;
+ String8 keysHidden;
+ String8 keyboard;
+ String8 navigation;
+ String8 screenSize;
+ String8 version;
+
+ bool initFromDirName(const char* dir, String8* resType);
+
+ static status_t parseNamePart(const String8& part, int* axis, uint32_t* value);
+
+ static bool getMccName(const char* name, ResTable_config* out = NULL);
+ static bool getMncName(const char* name, ResTable_config* out = NULL);
+ static bool getLocaleName(const char* name, ResTable_config* out = NULL);
+ static bool getOrientationName(const char* name, ResTable_config* out = NULL);
+ static bool getDensityName(const char* name, ResTable_config* out = NULL);
+ static bool getTouchscreenName(const char* name, ResTable_config* out = NULL);
+ static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL);
+ static bool getKeyboardName(const char* name, ResTable_config* out = NULL);
+ static bool getNavigationName(const char* name, ResTable_config* out = NULL);
+ static bool getScreenSizeName(const char* name, ResTable_config* out = NULL);
+ static bool getVersionName(const char* name, ResTable_config* out = NULL);
+
+ int compare(const AaptGroupEntry& o) const;
+
+ ResTable_config toParams() const;
+
+ inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; }
+ inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; }
+ inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; }
+ inline bool operator!=(const AaptGroupEntry& o) const { return compare(o) != 0; }
+ inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; }
+ inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; }
+
+ String8 toString() const;
+ String8 toDirName(const String8& resType) const;
+};
+
+inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
+{
+ return lhs.compare(rhs);
+}
+
+inline int strictly_order_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
+{
+ return compare_type(lhs, rhs) < 0;
+}
+
+class AaptGroup;
+
+/**
+ * A single asset file we know about.
+ */
+class AaptFile : public RefBase
+{
+public:
+ AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry,
+ const String8& resType)
+ : mGroupEntry(groupEntry)
+ , mResourceType(resType)
+ , mSourceFile(sourceFile)
+ , mData(NULL)
+ , mDataSize(0)
+ , mBufferSize(0)
+ , mCompression(ZipEntry::kCompressStored)
+ {
+ //printf("new AaptFile created %s\n", (const char*)sourceFile);
+ }
+ virtual ~AaptFile() { }
+
+ const String8& getPath() const { return mPath; }
+ const AaptGroupEntry& getGroupEntry() const { return mGroupEntry; }
+
+ // Data API. If there is data attached to the file,
+ // getSourceFile() is not used.
+ bool hasData() const { return mData != NULL; }
+ const void* getData() const { return mData; }
+ size_t getSize() const { return mDataSize; }
+ void* editData(size_t size);
+ void* editData(size_t* outSize = NULL);
+ void* padData(size_t wordSize);
+ status_t writeData(const void* data, size_t size);
+ void clearData();
+
+ const String8& getResourceType() const { return mResourceType; }
+
+ // File API. If the file does not hold raw data, this is
+ // a full path to a file on the filesystem that holds its data.
+ const String8& getSourceFile() const { return mSourceFile; }
+
+ String8 getPrintableSource() const;
+
+ // Desired compression method, as per utils/ZipEntry.h. For example,
+ // no compression is ZipEntry::kCompressStored.
+ int getCompressionMethod() const { return mCompression; }
+ void setCompressionMethod(int c) { mCompression = c; }
+private:
+ friend class AaptGroup;
+
+ String8 mPath;
+ AaptGroupEntry mGroupEntry;
+ String8 mResourceType;
+ String8 mSourceFile;
+ void* mData;
+ size_t mDataSize;
+ size_t mBufferSize;
+ int mCompression;
+};
+
+/**
+ * A group of related files (the same file, with different
+ * vendor/locale variations).
+ */
+class AaptGroup : public RefBase
+{
+public:
+ AaptGroup(const String8& leaf, const String8& path)
+ : mLeaf(leaf), mPath(path) { }
+ virtual ~AaptGroup() { }
+
+ const String8& getLeaf() const { return mLeaf; }
+
+ // Returns the relative path after the AaptGroupEntry dirs.
+ const String8& getPath() const { return mPath; }
+
+ const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& getFiles() const
+ { return mFiles; }
+
+ status_t addFile(const sp<AaptFile>& file);
+ void removeFile(size_t index);
+
+ void print() const;
+
+ String8 getPrintableSource() const;
+
+private:
+ String8 mLeaf;
+ String8 mPath;
+
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles;
+};
+
+/**
+ * A single directory of assets, which can contain for files and other
+ * sub-directories.
+ */
+class AaptDir : public RefBase
+{
+public:
+ AaptDir(const String8& leaf, const String8& path)
+ : mLeaf(leaf), mPath(path) { }
+ virtual ~AaptDir() { }
+
+ const String8& getLeaf() const { return mLeaf; }
+
+ const String8& getPath() const { return mPath; }
+
+ const DefaultKeyedVector<String8, sp<AaptGroup> >& getFiles() const { return mFiles; }
+ const DefaultKeyedVector<String8, sp<AaptDir> >& getDirs() const { return mDirs; }
+
+ status_t addFile(const String8& name, const sp<AaptGroup>& file);
+ status_t addDir(const String8& name, const sp<AaptDir>& dir);
+
+ sp<AaptDir> makeDir(const String8& name);
+
+ void removeFile(const String8& name);
+ void removeDir(const String8& name);
+
+ status_t renameFile(const sp<AaptFile>& file, const String8& newName);
+
+ status_t addLeafFile(const String8& leafName,
+ const sp<AaptFile>& file);
+
+ virtual ssize_t slurpFullTree(Bundle* bundle,
+ const String8& srcDir,
+ const AaptGroupEntry& kind,
+ const String8& resType);
+
+ /*
+ * Perform some sanity checks on the names of files and directories here.
+ * In particular:
+ * - Check for illegal chars in filenames.
+ * - Check filename length.
+ * - Check for presence of ".gz" and non-".gz" copies of same file.
+ * - Check for multiple files whose names match in a case-insensitive
+ * fashion (problematic for some systems).
+ *
+ * Comparing names against all other names is O(n^2). We could speed
+ * it up some by sorting the entries and being smarter about what we
+ * compare against, but I'm not expecting to have enough files in a
+ * single directory to make a noticeable difference in speed.
+ *
+ * Note that sorting here is not enough to guarantee that the package
+ * contents are sorted -- subsequent updates can rearrange things.
+ */
+ status_t validate() const;
+
+ void print() const;
+
+ String8 getPrintableSource() const;
+
+private:
+ String8 mLeaf;
+ String8 mPath;
+
+ DefaultKeyedVector<String8, sp<AaptGroup> > mFiles;
+ DefaultKeyedVector<String8, sp<AaptDir> > mDirs;
+};
+
+/**
+ * All information we know about a particular symbol.
+ */
+class AaptSymbolEntry
+{
+public:
+ AaptSymbolEntry()
+ : isPublic(false), typeCode(TYPE_UNKNOWN)
+ {
+ }
+ AaptSymbolEntry(const String8& _name)
+ : name(_name), isPublic(false), typeCode(TYPE_UNKNOWN)
+ {
+ }
+ AaptSymbolEntry(const AaptSymbolEntry& o)
+ : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic)
+ , comment(o.comment), typeComment(o.typeComment)
+ , typeCode(o.typeCode), int32Val(o.int32Val), stringVal(o.stringVal)
+ {
+ }
+ AaptSymbolEntry operator=(const AaptSymbolEntry& o)
+ {
+ sourcePos = o.sourcePos;
+ isPublic = o.isPublic;
+ comment = o.comment;
+ typeComment = o.typeComment;
+ typeCode = o.typeCode;
+ int32Val = o.int32Val;
+ stringVal = o.stringVal;
+ return *this;
+ }
+
+ const String8 name;
+
+ SourcePos sourcePos;
+ bool isPublic;
+
+ String16 comment;
+ String16 typeComment;
+
+ enum {
+ TYPE_UNKNOWN = 0,
+ TYPE_INT32,
+ TYPE_STRING
+ };
+
+ int typeCode;
+
+ // Value. May be one of these.
+ int32_t int32Val;
+ String8 stringVal;
+};
+
+/**
+ * A group of related symbols (such as indices into a string block)
+ * that have been generated from the assets.
+ */
+class AaptSymbols : public RefBase
+{
+public:
+ AaptSymbols() { }
+ virtual ~AaptSymbols() { }
+
+ status_t addSymbol(const String8& name, int32_t value, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.typeCode = AaptSymbolEntry::TYPE_INT32;
+ sym.int32Val = value;
+ return NO_ERROR;
+ }
+
+ status_t addStringSymbol(const String8& name, const String8& value,
+ const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.typeCode = AaptSymbolEntry::TYPE_STRING;
+ sym.stringVal = value;
+ return NO_ERROR;
+ }
+
+ status_t makeSymbolPublic(const String8& name, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.isPublic = true;
+ return NO_ERROR;
+ }
+
+ void appendComment(const String8& name, const String16& comment, const SourcePos& pos) {
+ if (comment.size() <= 0) {
+ return;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ if (sym.comment.size() == 0) {
+ sym.comment = comment;
+ } else {
+ sym.comment.append(String16("\n"));
+ sym.comment.append(comment);
+ }
+ }
+
+ void appendTypeComment(const String8& name, const String16& comment) {
+ if (comment.size() <= 0) {
+ return;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, NULL);
+ if (sym.typeComment.size() == 0) {
+ sym.typeComment = comment;
+ } else {
+ sym.typeComment.append(String16("\n"));
+ sym.typeComment.append(comment);
+ }
+ }
+
+ sp<AaptSymbols> addNestedSymbol(const String8& name, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "nested symbol")) {
+ return NULL;
+ }
+
+ sp<AaptSymbols> sym = mNestedSymbols.valueFor(name);
+ if (sym == NULL) {
+ sym = new AaptSymbols();
+ mNestedSymbols.add(name, sym);
+ }
+
+ return sym;
+ }
+
+ const KeyedVector<String8, AaptSymbolEntry>& getSymbols() const
+ { return mSymbols; }
+ const DefaultKeyedVector<String8, sp<AaptSymbols> >& getNestedSymbols() const
+ { return mNestedSymbols; }
+
+ const String16& getComment(const String8& name) const
+ { return get_symbol(name).comment; }
+ const String16& getTypeComment(const String8& name) const
+ { return get_symbol(name).typeComment; }
+
+private:
+ bool check_valid_symbol_name(const String8& symbol, const SourcePos& pos, const char* label) {
+ if (valid_symbol_name(symbol)) {
+ return true;
+ }
+ pos.error("invalid %s: '%s'\n", label, symbol.string());
+ return false;
+ }
+ AaptSymbolEntry& edit_symbol(const String8& symbol, const SourcePos* pos) {
+ ssize_t i = mSymbols.indexOfKey(symbol);
+ if (i < 0) {
+ i = mSymbols.add(symbol, AaptSymbolEntry(symbol));
+ }
+ AaptSymbolEntry& sym = mSymbols.editValueAt(i);
+ if (pos != NULL && sym.sourcePos.line < 0) {
+ sym.sourcePos = *pos;
+ }
+ return sym;
+ }
+ const AaptSymbolEntry& get_symbol(const String8& symbol) const {
+ ssize_t i = mSymbols.indexOfKey(symbol);
+ if (i >= 0) {
+ return mSymbols.valueAt(i);
+ }
+ return mDefSymbol;
+ }
+
+ KeyedVector<String8, AaptSymbolEntry> mSymbols;
+ DefaultKeyedVector<String8, sp<AaptSymbols> > mNestedSymbols;
+ AaptSymbolEntry mDefSymbol;
+};
+
+class ResourceTypeSet;
+
+/**
+ * Asset hierarchy being operated on.
+ */
+class AaptAssets : public AaptDir
+{
+public:
+ AaptAssets() : AaptDir(String8(), String8()), mHaveIncludedAssets(false) { }
+ virtual ~AaptAssets() { }
+
+ const String8& getPackage() const { return mPackage; }
+ void setPackage(const String8& package) { mPackage = package; mSymbolsPrivatePackage = package; }
+
+ const SortedVector<AaptGroupEntry>& getGroupEntries() const { return mGroupEntries; }
+
+ sp<AaptFile> addFile(const String8& filePath,
+ const AaptGroupEntry& entry,
+ const String8& srcDir,
+ sp<AaptGroup>* outGroup,
+ const String8& resType);
+
+ void addResource(const String8& leafName,
+ const String8& path,
+ const sp<AaptFile>& file,
+ const String8& resType);
+
+ ssize_t slurpFromArgs(Bundle* bundle);
+
+ virtual ssize_t slurpFullTree(Bundle* bundle,
+ const String8& srcDir,
+ const AaptGroupEntry& kind,
+ const String8& resType);
+
+ ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir);
+ ssize_t slurpResourceZip(Bundle* bundle, const char* filename);
+
+ sp<AaptSymbols> getSymbolsFor(const String8& name);
+
+ const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; }
+
+ String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; }
+ void setSymbolsPrivatePackage(const String8& pkg) { mSymbolsPrivatePackage = pkg; }
+
+ status_t buildIncludedResources(Bundle* bundle);
+ status_t addIncludedResources(const sp<AaptFile>& file);
+ const ResTable& getIncludedResources() const;
+
+ void print() const;
+
+ inline const Vector<sp<AaptDir> >& resDirs() { return mDirs; }
+
+ inline sp<AaptAssets> getOverlay() { return mOverlay; }
+ inline void setOverlay(sp<AaptAssets>& overlay) { mOverlay = overlay; }
+
+ inline KeyedVector<String8, sp<ResourceTypeSet> >* getResources() { return mRes; }
+ inline void
+ setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { mRes = res; }
+
+private:
+ String8 mPackage;
+ SortedVector<AaptGroupEntry> mGroupEntries;
+ DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols;
+ String8 mSymbolsPrivatePackage;
+
+ Vector<sp<AaptDir> > mDirs;
+
+ bool mHaveIncludedAssets;
+ AssetManager mIncludedAssets;
+
+ sp<AaptAssets> mOverlay;
+ KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
+};
+
+#endif // __AAPT_ASSETS_H
+
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
new file mode 100644
index 0000000..fdc859c
--- /dev/null
+++ b/tools/aapt/Android.mk
@@ -0,0 +1,52 @@
+#
+# Copyright 2006 The Android Open Source Project
+#
+# Android Asset Packaging Tool
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ AaptAssets.cpp \
+ Command.cpp \
+ Main.cpp \
+ Package.cpp \
+ StringPool.cpp \
+ XMLNode.cpp \
+ ResourceTable.cpp \
+ Images.cpp \
+ Resource.cpp \
+ SourcePos.cpp
+
+LOCAL_CFLAGS += -Wno-format-y2k
+
+LOCAL_C_INCLUDES += external/expat/lib
+LOCAL_C_INCLUDES += external/libpng
+LOCAL_C_INCLUDES += external/zlib
+LOCAL_C_INCLUDES += build/libs/host/include
+
+#LOCAL_WHOLE_STATIC_LIBRARIES :=
+LOCAL_STATIC_LIBRARIES := \
+ libhost \
+ libutils \
+ libcutils \
+ libexpat \
+ libpng
+
+LOCAL_LDLIBS := -lz
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lrt
+endif
+
+ifeq ($(HOST_OS),windows)
+ifeq ($(strip $(USE_CYGWIN),),)
+LOCAL_LDLIBS += -lws2_32
+endif
+endif
+
+LOCAL_MODULE := aapt
+
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
new file mode 100644
index 0000000..2d8471b
--- /dev/null
+++ b/tools/aapt/Bundle.h
@@ -0,0 +1,164 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// State bundle. Used to pass around stuff like command-line args.
+//
+#ifndef __BUNDLE_H
+#define __BUNDLE_H
+
+#include <stdlib.h>
+#include <utils.h> // android
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+/*
+ * Things we can do.
+ */
+typedef enum Command {
+ kCommandUnknown = 0,
+ kCommandVersion,
+ kCommandList,
+ kCommandDump,
+ kCommandAdd,
+ kCommandRemove,
+ kCommandPackage,
+} Command;
+
+/*
+ * Bundle of goodies, including everything specified on the command line.
+ */
+class Bundle {
+public:
+ Bundle(void)
+ : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
+ mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
+ mUpdate(false), mExtending(false),
+ mRequireLocalization(false), mPseudolocalize(false),
+ mCompressionMethod(0), mOutputAPKFile(NULL),
+ mAssetSourceDir(NULL),
+ mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
+ mRClassDir(NULL), mResourceIntermediatesDir(NULL),
+ mArgc(0), mArgv(NULL)
+ {}
+ ~Bundle(void) {}
+
+ /*
+ * Set the command value. Returns "false" if it was previously set.
+ */
+ Command getCommand(void) const { return mCmd; }
+ void setCommand(Command cmd) { mCmd = cmd; }
+
+ /*
+ * Command modifiers. Not all modifiers are appropriate for all
+ * commands.
+ */
+ bool getVerbose(void) const { return mVerbose; }
+ void setVerbose(bool val) { mVerbose = val; }
+ bool getAndroidList(void) const { return mAndroidList; }
+ void setAndroidList(bool val) { mAndroidList = val; }
+ bool getForce(void) const { return mForce; }
+ void setForce(bool val) { mForce = val; }
+ void setGrayscaleTolerance(int val) { mGrayscaleTolerance = val; }
+ int getGrayscaleTolerance() { return mGrayscaleTolerance; }
+ bool getMakePackageDirs(void) const { return mMakePackageDirs; }
+ void setMakePackageDirs(bool val) { mMakePackageDirs = val; }
+ bool getUpdate(void) const { return mUpdate; }
+ void setUpdate(bool val) { mUpdate = val; }
+ bool getExtending(void) const { return mExtending; }
+ void setExtending(bool val) { mExtending = val; }
+ bool getRequireLocalization(void) const { return mRequireLocalization; }
+ void setRequireLocalization(bool val) { mRequireLocalization = val; }
+ bool getPseudolocalize(void) const { return mPseudolocalize; }
+ void setPseudolocalize(bool val) { mPseudolocalize = val; }
+ int getCompressionMethod(void) const { return mCompressionMethod; }
+ void setCompressionMethod(int val) { mCompressionMethod = val; }
+ const char* getOutputAPKFile() const { return mOutputAPKFile; }
+ void setOutputAPKFile(const char* val) { mOutputAPKFile = val; }
+
+ /*
+ * Input options.
+ */
+ const char* getAssetSourceDir() const { return mAssetSourceDir; }
+ void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; }
+ const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; }
+ void addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); }
+ const char* getAndroidManifestFile() const { return mAndroidManifestFile; }
+ void setAndroidManifestFile(const char* file) { mAndroidManifestFile = file; }
+ const char* getPublicOutputFile() const { return mPublicOutputFile; }
+ void setPublicOutputFile(const char* file) { mPublicOutputFile = file; }
+ const char* getRClassDir() const { return mRClassDir; }
+ void setRClassDir(const char* dir) { mRClassDir = dir; }
+ const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; }
+ void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } }
+ const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; }
+ void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; }
+ const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; }
+ void addPackageInclude(const char* file) { mPackageIncludes.add(file); }
+ const android::Vector<const char*>& getJarFiles() const { return mJarFiles; }
+ void addJarFile(const char* file) { mJarFiles.add(file); }
+ const android::Vector<const char*>& getNoCompressExtensions() const { return mNoCompressExtensions; }
+ void addNoCompressExtension(const char* ext) { mNoCompressExtensions.add(ext); }
+
+ /*
+ * Set and get the file specification.
+ *
+ * Note this does NOT make a copy of argv.
+ */
+ void setFileSpec(char* const argv[], int argc) {
+ mArgc = argc;
+ mArgv = argv;
+ }
+ int getFileSpecCount(void) const { return mArgc; }
+ const char* getFileSpecEntry(int idx) const { return mArgv[idx]; }
+ void eatArgs(int n) {
+ if (n > mArgc) n = mArgc;
+ mArgv += n;
+ mArgc -= n;
+ }
+
+#if 0
+ /*
+ * Package count. Nothing to do with anything else here; this is
+ * just a convenient place to stuff it so we don't have to pass it
+ * around everywhere.
+ */
+ int getPackageCount(void) const { return mPackageCount; }
+ void setPackageCount(int val) { mPackageCount = val; }
+#endif
+
+private:
+ /* commands & modifiers */
+ Command mCmd;
+ bool mVerbose;
+ bool mAndroidList;
+ bool mForce;
+ int mGrayscaleTolerance;
+ bool mMakePackageDirs;
+ bool mUpdate;
+ bool mExtending;
+ bool mRequireLocalization;
+ bool mPseudolocalize;
+ int mCompressionMethod;
+ const char* mOutputAPKFile;
+ const char* mAssetSourceDir;
+ const char* mAndroidManifestFile;
+ const char* mPublicOutputFile;
+ const char* mRClassDir;
+ const char* mResourceIntermediatesDir;
+ android::String8 mConfigurations;
+ android::Vector<const char*> mPackageIncludes;
+ android::Vector<const char*> mJarFiles;
+ android::Vector<const char*> mNoCompressExtensions;
+ android::Vector<const char*> mResourceSourceDirs;
+
+ /* file specification */
+ int mArgc;
+ char* const* mArgv;
+
+#if 0
+ /* misc stuff */
+ int mPackageCount;
+#endif
+};
+
+#endif // __BUNDLE_H
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
new file mode 100644
index 0000000..bff0423
--- /dev/null
+++ b/tools/aapt/Command.cpp
@@ -0,0 +1,841 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Android Asset Packaging Tool main entry point.
+//
+#include "Main.h"
+#include "Bundle.h"
+#include "ResourceTable.h"
+#include "XMLNode.h"
+
+#include <utils.h>
+#include <utils/ZipFile.h>
+
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace android;
+
+/*
+ * Show version info. All the cool kids do it.
+ */
+int doVersion(Bundle* bundle)
+{
+ if (bundle->getFileSpecCount() != 0)
+ printf("(ignoring extra arguments)\n");
+ printf("Android Asset Packaging Tool, v0.2\n");
+
+ return 0;
+}
+
+
+/*
+ * Open the file read only. The call fails if the file doesn't exist.
+ *
+ * Returns NULL on failure.
+ */
+ZipFile* openReadOnly(const char* fileName)
+{
+ ZipFile* zip;
+ status_t result;
+
+ zip = new ZipFile;
+ result = zip->open(fileName, ZipFile::kOpenReadOnly);
+ if (result != NO_ERROR) {
+ if (result == NAME_NOT_FOUND)
+ fprintf(stderr, "ERROR: '%s' not found\n", fileName);
+ else if (result == PERMISSION_DENIED)
+ fprintf(stderr, "ERROR: '%s' access denied\n", fileName);
+ else
+ fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n",
+ fileName);
+ delete zip;
+ return NULL;
+ }
+
+ return zip;
+}
+
+/*
+ * Open the file read-write. The file will be created if it doesn't
+ * already exist and "okayToCreate" is set.
+ *
+ * Returns NULL on failure.
+ */
+ZipFile* openReadWrite(const char* fileName, bool okayToCreate)
+{
+ ZipFile* zip = NULL;
+ status_t result;
+ int flags;
+
+ flags = ZipFile::kOpenReadWrite;
+ if (okayToCreate)
+ flags |= ZipFile::kOpenCreate;
+
+ zip = new ZipFile;
+ result = zip->open(fileName, flags);
+ if (result != NO_ERROR) {
+ delete zip;
+ zip = NULL;
+ goto bail;
+ }
+
+bail:
+ return zip;
+}
+
+
+/*
+ * Return a short string describing the compression method.
+ */
+const char* compressionName(int method)
+{
+ if (method == ZipEntry::kCompressStored)
+ return "Stored";
+ else if (method == ZipEntry::kCompressDeflated)
+ return "Deflated";
+ else
+ return "Unknown";
+}
+
+/*
+ * Return the percent reduction in size (0% == no compression).
+ */
+int calcPercent(long uncompressedLen, long compressedLen)
+{
+ if (!uncompressedLen)
+ return 0;
+ else
+ return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5);
+}
+
+/*
+ * Handle the "list" command, which can be a simple file dump or
+ * a verbose listing.
+ *
+ * The verbose listing closely matches the output of the Info-ZIP "unzip"
+ * command.
+ */
+int doList(Bundle* bundle)
+{
+ int result = 1;
+ ZipFile* zip = NULL;
+ const ZipEntry* entry;
+ long totalUncLen, totalCompLen;
+ const char* zipFileName;
+
+ if (bundle->getFileSpecCount() != 1) {
+ fprintf(stderr, "ERROR: specify zip file name (only)\n");
+ goto bail;
+ }
+ zipFileName = bundle->getFileSpecEntry(0);
+
+ zip = openReadOnly(zipFileName);
+ if (zip == NULL)
+ goto bail;
+
+ int count, i;
+
+ if (bundle->getVerbose()) {
+ printf("Archive: %s\n", zipFileName);
+ printf(
+ " Length Method Size Ratio Date Time CRC-32 Name\n");
+ printf(
+ "-------- ------ ------- ----- ---- ---- ------ ----\n");
+ }
+
+ totalUncLen = totalCompLen = 0;
+
+ count = zip->getNumEntries();
+ for (i = 0; i < count; i++) {
+ entry = zip->getEntryByIndex(i);
+ if (bundle->getVerbose()) {
+ char dateBuf[32];
+ time_t when;
+
+ when = entry->getModWhen();
+ strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M",
+ localtime(&when));
+
+ printf("%8ld %-7.7s %7ld %3d%% %s %08lx %s\n",
+ (long) entry->getUncompressedLen(),
+ compressionName(entry->getCompressionMethod()),
+ (long) entry->getCompressedLen(),
+ calcPercent(entry->getUncompressedLen(),
+ entry->getCompressedLen()),
+ dateBuf,
+ entry->getCRC32(),
+ entry->getFileName());
+ } else {
+ printf("%s\n", entry->getFileName());
+ }
+
+ totalUncLen += entry->getUncompressedLen();
+ totalCompLen += entry->getCompressedLen();
+ }
+
+ if (bundle->getVerbose()) {
+ printf(
+ "-------- ------- --- -------\n");
+ printf("%8ld %7ld %2d%% %d files\n",
+ totalUncLen,
+ totalCompLen,
+ calcPercent(totalUncLen, totalCompLen),
+ zip->getNumEntries());
+ }
+
+ if (bundle->getAndroidList()) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(zipFileName), NULL)) {
+ fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n");
+ goto bail;
+ }
+
+ const ResTable& res = assets.getResources(false);
+ if (&res == NULL) {
+ printf("\nNo resource table found.\n");
+ } else {
+ printf("\nResource table:\n");
+ res.print();
+ }
+
+ Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
+ Asset::ACCESS_BUFFER);
+ if (manifestAsset == NULL) {
+ printf("\nNo AndroidManifest.xml found.\n");
+ } else {
+ printf("\nAndroid manifest:\n");
+ ResXMLTree tree;
+ tree.setTo(manifestAsset->getBuffer(true),
+ manifestAsset->getLength());
+ printXMLBlock(&tree);
+ }
+ delete manifestAsset;
+ }
+
+ result = 0;
+
+bail:
+ delete zip;
+ return result;
+}
+
+static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
+{
+ size_t N = tree.getAttributeCount();
+ for (size_t i=0; i<N; i++) {
+ if (tree.getAttributeNameResID(i) == attrRes) {
+ return (ssize_t)i;
+ }
+ }
+ return -1;
+}
+
+static String8 getAttribute(const ResXMLTree& tree, const char* ns,
+ const char* attr, String8* outError)
+{
+ ssize_t idx = tree.indexOfAttribute(ns, attr);
+ if (idx < 0) {
+ return String8();
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType != Res_value::TYPE_STRING) {
+ if (outError != NULL) *outError = "attribute is not a string value";
+ return String8();
+ }
+ }
+ size_t len;
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ return str ? String8(str, len) : String8();
+}
+
+static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return String8();
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType != Res_value::TYPE_STRING) {
+ if (outError != NULL) *outError = "attribute is not a string value";
+ return String8();
+ }
+ }
+ size_t len;
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ return str ? String8(str, len) : String8();
+}
+
+static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return -1;
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType != Res_value::TYPE_INT_DEC) {
+ if (outError != NULL) *outError = "attribute is not an integer value";
+ return -1;
+ }
+ }
+ return value.data;
+}
+
+static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
+ uint32_t attrRes, String8* outError)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return String8();
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType == Res_value::TYPE_STRING) {
+ size_t len;
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ return str ? String8(str, len) : String8();
+ }
+ resTable->resolveReference(&value, 0);
+ if (value.dataType != Res_value::TYPE_STRING) {
+ if (outError != NULL) *outError = "attribute is not a string value";
+ return String8();
+ }
+ }
+ size_t len;
+ const Res_value* value2 = &value;
+ const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
+ return str ? String8(str, len) : String8();
+}
+
+// These are attribute resource constants for the platform, as found
+// in android.R.attr
+enum {
+ NAME_ATTR = 0x01010003,
+ VERSION_CODE_ATTR = 0x0101021b,
+ VERSION_NAME_ATTR = 0x0101021c,
+ LABEL_ATTR = 0x01010001,
+ ICON_ATTR = 0x01010002,
+};
+
+/*
+ * Handle the "dump" command, to extract select data from an archive.
+ */
+int doDump(Bundle* bundle)
+{
+ status_t result = UNKNOWN_ERROR;
+ Asset* asset = NULL;
+
+ if (bundle->getFileSpecCount() < 1) {
+ fprintf(stderr, "ERROR: no dump option specified\n");
+ return 1;
+ }
+
+ if (bundle->getFileSpecCount() < 2) {
+ fprintf(stderr, "ERROR: no dump file specified\n");
+ return 1;
+ }
+
+ const char* option = bundle->getFileSpecEntry(0);
+ const char* filename = bundle->getFileSpecEntry(1);
+
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(filename), NULL)) {
+ fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
+ return 1;
+ }
+
+ const ResTable& res = assets.getResources(false);
+ if (&res == NULL) {
+ fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
+ goto bail;
+ }
+
+ if (strcmp("resources", option) == 0) {
+ res.print();
+
+ } else if (strcmp("xmltree", option) == 0) {
+ if (bundle->getFileSpecCount() < 3) {
+ fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
+ goto bail;
+ }
+
+ for (int i=2; i<bundle->getFileSpecCount(); i++) {
+ const char* resname = bundle->getFileSpecEntry(i);
+ ResXMLTree tree;
+ asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
+ if (asset == NULL) {
+ fprintf(stderr, "ERROR: dump failed because resource %p found\n", resname);
+ goto bail;
+ }
+
+ if (tree.setTo(asset->getBuffer(true),
+ asset->getLength()) != NO_ERROR) {
+ fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
+ goto bail;
+ }
+ tree.restart();
+ printXMLBlock(&tree);
+ delete asset;
+ asset = NULL;
+ }
+
+ } else if (strcmp("xmlstrings", option) == 0) {
+ if (bundle->getFileSpecCount() < 3) {
+ fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
+ goto bail;
+ }
+
+ for (int i=2; i<bundle->getFileSpecCount(); i++) {
+ const char* resname = bundle->getFileSpecEntry(i);
+ ResXMLTree tree;
+ asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
+ if (asset == NULL) {
+ fprintf(stderr, "ERROR: dump failed because resource %p found\n", resname);
+ goto bail;
+ }
+
+ if (tree.setTo(asset->getBuffer(true),
+ asset->getLength()) != NO_ERROR) {
+ fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
+ goto bail;
+ }
+ printStringPool(&tree.getStrings());
+ delete asset;
+ asset = NULL;
+ }
+
+ } else {
+ ResXMLTree tree;
+ asset = assets.openNonAsset("AndroidManifest.xml",
+ Asset::ACCESS_BUFFER);
+ if (asset == NULL) {
+ fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
+ goto bail;
+ }
+
+ if (tree.setTo(asset->getBuffer(true),
+ asset->getLength()) != NO_ERROR) {
+ fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
+ goto bail;
+ }
+ tree.restart();
+
+ if (strcmp("permissions", option) == 0) {
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ //printf("Depth %d tag %s\n", depth, tag.string());
+ if (depth == 1) {
+ if (tag != "manifest") {
+ fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ goto bail;
+ }
+ String8 pkg = getAttribute(tree, NULL, "package", NULL);
+ printf("package: %s\n", pkg.string());
+ } else if (depth == 2 && tag == "permission") {
+ String8 error;
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+ printf("permission: %s\n", name.string());
+ } else if (depth == 2 && tag == "uses-permission") {
+ String8 error;
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+ printf("uses-permission: %s\n", name.string());
+ }
+ }
+ } else if (strcmp("badging", option) == 0) {
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ String8 error;
+ bool withinActivity = false;
+ bool isMainActivity = false;
+ bool isLauncherActivity = false;
+ String8 activityName;
+ String8 activityLabel;
+ String8 activityIcon;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ //printf("Depth %d tag %s\n", depth, tag.string());
+ if (depth == 1) {
+ if (tag != "manifest") {
+ fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ goto bail;
+ }
+ String8 pkg = getAttribute(tree, NULL, "package", NULL);
+ printf("package: name='%s' ", pkg.string());
+ int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
+ goto bail;
+ }
+ if (versionCode > 0) {
+ printf("versionCode='%d' ", versionCode);
+ } else {
+ printf("versionCode='' ");
+ }
+ String8 versionName = getAttribute(tree, VERSION_NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
+ goto bail;
+ }
+ printf("versionName='%s'\n", versionName.string());
+ } else if (depth == 2 && tag == "application") {
+ String8 label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string());
+ goto bail;
+ }
+ printf("application: label='%s' ", label.string());
+
+ String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
+ goto bail;
+ }
+ printf("icon='%s'\n", icon.string());
+ } else if (depth == 3 && tag == "activity") {
+ withinActivity = true;
+ //printf("LOG: withinActivity==true\n");
+
+ activityName = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string());
+ goto bail;
+ }
+
+ activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string());
+ goto bail;
+ }
+
+ activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
+ goto bail;
+ }
+ } else if (depth == 5 && withinActivity) {
+ if (tag == "action") {
+ //printf("LOG: action tag\n");
+ String8 action = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string());
+ goto bail;
+ }
+ if (action == "android.intent.action.MAIN") {
+ isMainActivity = true;
+ //printf("LOG: isMainActivity==true\n");
+ }
+ } else if (tag == "category") {
+ String8 category = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string());
+ goto bail;
+ }
+ if (category == "android.intent.category.LAUNCHER") {
+ isLauncherActivity = true;
+ //printf("LOG: isLauncherActivity==true\n");
+ }
+ }
+ }
+
+ if (depth < 3) {
+ //if (withinActivity) printf("LOG: withinActivity==false\n");
+ withinActivity = false;
+ }
+
+ if (depth < 5) {
+ //if (isMainActivity) printf("LOG: isMainActivity==false\n");
+ //if (isLauncherActivity) printf("LOG: isLauncherActivity==false\n");
+ isMainActivity = false;
+ isLauncherActivity = false;
+ }
+
+ if (withinActivity && isMainActivity && isLauncherActivity) {
+ printf("launchable activity: name='%s' label='%s' icon='%s'\n",
+ activityName.string(), activityLabel.string(),
+ activityIcon.string());
+ }
+ }
+ printf("locales:");
+ Vector<String8> locales;
+ res.getLocales(&locales);
+ const size_t N = locales.size();
+ for (size_t i=0; i<N; i++) {
+ const char* localeStr = locales[i].string();
+ if (localeStr == NULL || strlen(localeStr) == 0) {
+ localeStr = "--_--";
+ }
+ printf(" '%s'", localeStr);
+ }
+ printf("\n");
+ } else if (strcmp("configurations", option) == 0) {
+ Vector<ResTable_config> configs;
+ res.getConfigurations(&configs);
+ const size_t N = configs.size();
+ for (size_t i=0; i<N; i++) {
+ printf("%s\n", configs[i].toString().string());
+ }
+ } else {
+ fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
+ goto bail;
+ }
+ }
+
+ result = NO_ERROR;
+
+bail:
+ if (asset) {
+ delete asset;
+ }
+ return (result != NO_ERROR);
+}
+
+
+/*
+ * Handle the "add" command, which wants to add files to a new or
+ * pre-existing archive.
+ */
+int doAdd(Bundle* bundle)
+{
+ ZipFile* zip = NULL;
+ status_t result = UNKNOWN_ERROR;
+ const char* zipFileName;
+
+ if (bundle->getUpdate()) {
+ /* avoid confusion */
+ fprintf(stderr, "ERROR: can't use '-u' with add\n");
+ goto bail;
+ }
+
+ if (bundle->getFileSpecCount() < 1) {
+ fprintf(stderr, "ERROR: must specify zip file name\n");
+ goto bail;
+ }
+ zipFileName = bundle->getFileSpecEntry(0);
+
+ if (bundle->getFileSpecCount() < 2) {
+ fprintf(stderr, "NOTE: nothing to do\n");
+ goto bail;
+ }
+
+ zip = openReadWrite(zipFileName, true);
+ if (zip == NULL) {
+ fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
+ goto bail;
+ }
+
+ for (int i = 1; i < bundle->getFileSpecCount(); i++) {
+ const char* fileName = bundle->getFileSpecEntry(i);
+
+ if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
+ printf(" '%s'... (from gzip)\n", fileName);
+ result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
+ } else {
+ printf(" '%s'...\n", fileName);
+ result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
+ }
+ if (result != NO_ERROR) {
+ fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
+ if (result == NAME_NOT_FOUND)
+ fprintf(stderr, ": file not found\n");
+ else if (result == ALREADY_EXISTS)
+ fprintf(stderr, ": already exists in archive\n");
+ else
+ fprintf(stderr, "\n");
+ goto bail;
+ }
+ }
+
+ result = NO_ERROR;
+
+bail:
+ delete zip;
+ return (result != NO_ERROR);
+}
+
+
+/*
+ * Delete files from an existing archive.
+ */
+int doRemove(Bundle* bundle)
+{
+ ZipFile* zip = NULL;
+ status_t result = UNKNOWN_ERROR;
+ const char* zipFileName;
+
+ if (bundle->getFileSpecCount() < 1) {
+ fprintf(stderr, "ERROR: must specify zip file name\n");
+ goto bail;
+ }
+ zipFileName = bundle->getFileSpecEntry(0);
+
+ if (bundle->getFileSpecCount() < 2) {
+ fprintf(stderr, "NOTE: nothing to do\n");
+ goto bail;
+ }
+
+ zip = openReadWrite(zipFileName, false);
+ if (zip == NULL) {
+ fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
+ zipFileName);
+ goto bail;
+ }
+
+ for (int i = 1; i < bundle->getFileSpecCount(); i++) {
+ const char* fileName = bundle->getFileSpecEntry(i);
+ ZipEntry* entry;
+
+ entry = zip->getEntryByName(fileName);
+ if (entry == NULL) {
+ printf(" '%s' NOT FOUND\n", fileName);
+ continue;
+ }
+
+ result = zip->remove(entry);
+
+ if (result != NO_ERROR) {
+ fprintf(stderr, "Unable to delete '%s' from '%s'\n",
+ bundle->getFileSpecEntry(i), zipFileName);
+ goto bail;
+ }
+ }
+
+ /* update the archive */
+ zip->flush();
+
+bail:
+ delete zip;
+ return (result != NO_ERROR);
+}
+
+
+/*
+ * Package up an asset directory and associated application files.
+ */
+int doPackage(Bundle* bundle)
+{
+ const char* outputAPKFile;
+ int retVal = 1;
+ status_t err;
+ sp<AaptAssets> assets;
+ int N;
+
+ // -c zz_ZZ means do pseudolocalization
+ ResourceFilter filter;
+ err = filter.parse(bundle->getConfigurations());
+ if (err != NO_ERROR) {
+ goto bail;
+ }
+ if (filter.containsPseudo()) {
+ bundle->setPseudolocalize(true);
+ }
+
+ N = bundle->getFileSpecCount();
+ if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
+ && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) {
+ fprintf(stderr, "ERROR: no input files\n");
+ goto bail;
+ }
+
+ outputAPKFile = bundle->getOutputAPKFile();
+
+ // Make sure the filenames provided exist and are of the appropriate type.
+ if (outputAPKFile) {
+ FileType type;
+ type = getFileType(outputAPKFile);
+ if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
+ fprintf(stderr,
+ "ERROR: output file '%s' exists but is not regular file\n",
+ outputAPKFile);
+ goto bail;
+ }
+ }
+
+ // Load the assets.
+ assets = new AaptAssets();
+ err = assets->slurpFromArgs(bundle);
+ if (err < 0) {
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ assets->print();
+ }
+
+ // If they asked for any files that need to be compiled, do so.
+ if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
+ err = buildResources(bundle, assets);
+ if (err != 0) {
+ goto bail;
+ }
+ }
+
+ // At this point we've read everything and processed everything. From here
+ // on out it's just writing output files.
+ if (SourcePos::hasErrors()) {
+ goto bail;
+ }
+
+ // Write out R.java constants
+ if (assets->getPackage() == assets->getSymbolsPrivatePackage()) {
+ err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
+ if (err < 0) {
+ goto bail;
+ }
+ } else {
+ err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
+ if (err < 0) {
+ goto bail;
+ }
+ err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
+ if (err < 0) {
+ goto bail;
+ }
+ }
+
+ // Write the apk
+ if (outputAPKFile) {
+ err = writeAPK(bundle, assets, String8(outputAPKFile));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
+ goto bail;
+ }
+ }
+
+ retVal = 0;
+bail:
+ if (SourcePos::hasErrors()) {
+ SourcePos::printErrors(stderr);
+ }
+ return retVal;
+}
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
new file mode 100644
index 0000000..edb12ea
--- /dev/null
+++ b/tools/aapt/Images.cpp
@@ -0,0 +1,1082 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#define PNG_INTERNAL
+
+#include "Images.h"
+
+#include <utils/ResourceTypes.h>
+#include <utils/ByteOrder.h>
+
+#include <png.h>
+
+#define NOISY(x) //x
+
+static void
+png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ status_t err = ((AaptFile*)png_ptr->io_ptr)->writeData(data, length);
+ if (err != NO_ERROR) {
+ png_error(png_ptr, "Write Error");
+ }
+}
+
+
+static void
+png_flush_aapt_file(png_structp png_ptr)
+{
+}
+
+// This holds an image as 8bpp RGBA.
+struct image_info
+{
+ image_info() : rows(NULL), is9Patch(false), allocRows(NULL) { }
+ ~image_info() {
+ if (rows && rows != allocRows) {
+ free(rows);
+ }
+ if (allocRows) {
+ for (int i=0; i<(int)allocHeight; i++) {
+ free(allocRows[i]);
+ }
+ free(allocRows);
+ }
+ }
+
+ png_uint_32 width;
+ png_uint_32 height;
+ png_bytepp rows;
+
+ // 9-patch info.
+ bool is9Patch;
+ Res_png_9patch info9Patch;
+
+ png_uint_32 allocHeight;
+ png_bytepp allocRows;
+};
+
+static void read_png(const char* imageName,
+ png_structp read_ptr, png_infop read_info,
+ image_info* outImageInfo)
+{
+ int color_type;
+ int bit_depth, interlace_type, compression_type;
+ int i;
+
+ png_read_info(read_ptr, read_info);
+
+ png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
+ &outImageInfo->height, &bit_depth, &color_type,
+ &interlace_type, &compression_type, NULL);
+
+ //printf("Image %s:\n", imageName);
+ //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
+ // color_type, bit_depth, interlace_type, compression_type);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_palette_to_rgb(read_ptr);
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+ png_set_gray_1_2_4_to_8(read_ptr);
+
+ if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
+ //printf("Has PNG_INFO_tRNS!\n");
+ png_set_tRNS_to_alpha(read_ptr);
+ }
+
+ if (bit_depth == 16)
+ png_set_strip_16(read_ptr);
+
+ if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
+ png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+
+ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(read_ptr);
+
+ png_read_update_info(read_ptr, read_info);
+
+ outImageInfo->rows = (png_bytepp)malloc(
+ outImageInfo->height * png_sizeof(png_bytep));
+ outImageInfo->allocHeight = outImageInfo->height;
+ outImageInfo->allocRows = outImageInfo->rows;
+
+ png_set_rows(read_ptr, read_info, outImageInfo->rows);
+
+ for (i = 0; i < (int)outImageInfo->height; i++)
+ {
+ outImageInfo->rows[i] = (png_bytep)
+ malloc(png_get_rowbytes(read_ptr, read_info));
+ }
+
+ png_read_image(read_ptr, outImageInfo->rows);
+
+ png_read_end(read_ptr, read_info);
+
+ NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
+ imageName,
+ (int)outImageInfo->width, (int)outImageInfo->height,
+ bit_depth, color_type,
+ interlace_type, compression_type));
+
+ png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
+ &outImageInfo->height, &bit_depth, &color_type,
+ &interlace_type, &compression_type, NULL);
+}
+
+static bool is_tick(png_bytep p, bool transparent, const char** outError)
+{
+ if (transparent) {
+ if (p[3] == 0) {
+ return false;
+ }
+ if (p[3] != 0xff) {
+ *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
+ return false;
+ }
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in transparent frame must be black";
+ }
+ return true;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) {
+ return false;
+ }
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black";
+ return false;
+ }
+ return true;
+}
+
+enum {
+ TICK_START,
+ TICK_INSIDE_1,
+ TICK_OUTSIDE_1
+};
+
+static status_t get_horizontal_ticks(
+ png_bytep row, int width, bool transparent, bool required,
+ int32_t* outLeft, int32_t* outRight, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed)
+{
+ int i;
+ *outLeft = *outRight = -1;
+ int state = TICK_START;
+ bool found = false;
+
+ for (i=1; i<width-1; i++) {
+ if (is_tick(row+i*4, transparent, outError)) {
+ if (state == TICK_START ||
+ (state == TICK_OUTSIDE_1 && multipleAllowed)) {
+ *outLeft = i-1;
+ *outRight = width-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TICK_INSIDE_1;
+ } else if (state == TICK_OUTSIDE_1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
+ return UNKNOWN_ERROR;
+ }
+ } else if (*outError == NULL) {
+ if (state == TICK_INSIDE_1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i-1;
+ outRight += 2;
+ outLeft += 2;
+ state = TICK_OUTSIDE_1;
+ }
+ } else {
+ *outLeft = i;
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+static status_t get_vertical_ticks(
+ png_bytepp rows, int offset, int height, bool transparent, bool required,
+ int32_t* outTop, int32_t* outBottom, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed)
+{
+ int i;
+ *outTop = *outBottom = -1;
+ int state = TICK_START;
+ bool found = false;
+
+ for (i=1; i<height-1; i++) {
+ if (is_tick(rows[i]+offset, transparent, outError)) {
+ if (state == TICK_START ||
+ (state == TICK_OUTSIDE_1 && multipleAllowed)) {
+ *outTop = i-1;
+ *outBottom = height-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TICK_INSIDE_1;
+ } else if (state == TICK_OUTSIDE_1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
+ return UNKNOWN_ERROR;
+ }
+ } else if (*outError == NULL) {
+ if (state == TICK_INSIDE_1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i-1;
+ outTop += 2;
+ outBottom += 2;
+ state = TICK_OUTSIDE_1;
+ }
+ } else {
+ *outTop = i;
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+static uint32_t get_color(
+ png_bytepp rows, int left, int top, int right, int bottom)
+{
+ png_bytep color = rows[top] + left*4;
+
+ if (left > right || top > bottom) {
+ return Res_png_9patch::TRANSPARENT_COLOR;
+ }
+
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top]+i*4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return Res_png_9patch::NO_COLOR;
+ }
+ } else if (p[0] != color[0] || p[1] != color[1]
+ || p[2] != color[2] || p[3] != color[3]) {
+ return Res_png_9patch::NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (color[3] == 0) {
+ return Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static void select_patch(
+ int which, int front, int back, int size, int* start, int* end)
+{
+ switch (which) {
+ case 0:
+ *start = 0;
+ *end = front-1;
+ break;
+ case 1:
+ *start = front;
+ *end = back-1;
+ break;
+ case 2:
+ *start = back;
+ *end = size-1;
+ break;
+ }
+}
+
+static uint32_t get_color(image_info* image, int hpatch, int vpatch)
+{
+ int left, right, top, bottom;
+ select_patch(
+ hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
+ image->width, &left, &right);
+ select_patch(
+ vpatch, image->info9Patch.yDivs[0], image->info9Patch.yDivs[1],
+ image->height, &top, &bottom);
+ //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
+ // hpatch, vpatch, left, top, right, bottom);
+ const uint32_t c = get_color(image->rows, left, top, right, bottom);
+ NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
+ return c;
+}
+
+static status_t do_9patch(const char* imageName, image_info* image)
+{
+ image->is9Patch = true;
+
+ int W = image->width;
+ int H = image->height;
+ int i, j;
+
+ int maxSizeXDivs = (W / 2 + 1) * sizeof(int32_t);
+ int maxSizeYDivs = (H / 2 + 1) * sizeof(int32_t);
+ int32_t* xDivs = (int32_t*) malloc(maxSizeXDivs);
+ int32_t* yDivs = (int32_t*) malloc(maxSizeYDivs);
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
+
+ const char* errorMsg = NULL;
+ int errorPixel = -1;
+ const char* errorEdge = "";
+
+ int colorIndex = 0;
+
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
+ &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
+ &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Find left and right of padding area...
+ if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
+ &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Copy patch data into image
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+ image->info9Patch.xDivs = xDivs;
+ image->info9Patch.yDivs = yDivs;
+
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
+
+ NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
+ image->info9Patch.yDivs[0], image->info9Patch.yDivs[1]));
+ NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
+
+ // Remove frame from image.
+ image->rows = (png_bytepp)malloc((H-2) * png_sizeof(png_bytep));
+ for (i=0; i<(H-2); i++) {
+ image->rows[i] = image->allocRows[i+1];
+ memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
+ }
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
+
+ // Fill in color information for each patch.
+
+ uint32_t c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0);
+ j <= numYDivs && top < H;
+ j++) {
+ if (j == numYDivs) {
+ bottom = H;
+ } else {
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0;
+ i <= numXDivs && left < W;
+ i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = get_color(image->rows, left, top, right - 1, bottom - 1);
+ image->info9Patch.colors[colorIndex++] = c;
+ NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
+ left = right;
+ }
+ top = bottom;
+ }
+
+ assert(colorIndex == numColors);
+
+ for (i=0; i<numColors; i++) {
+ if (hasColor) {
+ if (i == 0) printf("Colors in %s:\n ", imageName);
+ printf(" #%08x", image->info9Patch.colors[i]);
+ if (i == numColors - 1) printf("\n");
+ }
+ }
+
+ image->is9Patch = true;
+ image->info9Patch.deviceToFile();
+
+getout:
+ if (errorMsg) {
+ fprintf(stderr,
+ "ERROR: 9-patch image %s malformed.\n"
+ " %s.\n", imageName, errorMsg);
+ if (errorPixel >= 0) {
+ fprintf(stderr,
+ " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
+ } else {
+ fprintf(stderr,
+ " Found along %s edge.\n", errorEdge);
+ }
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
+static void checkNinePatchSerialization(Res_png_9patch* inPatch, void * data)
+{
+ if (sizeof(void*) != sizeof(int32_t)) {
+ // can't deserialize on a non-32 bit system
+ return;
+ }
+ size_t patchSize = inPatch->serializedSize();
+ void * newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+ for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->colors[i] == inPatch->colors[i]);
+ }
+ free(newData);
+}
+
+static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) {
+ if (!(patch1.numXDivs == patch2.numXDivs &&
+ patch1.numYDivs == patch2.numYDivs &&
+ patch1.numColors == patch2.numColors &&
+ patch1.paddingLeft == patch2.paddingLeft &&
+ patch1.paddingRight == patch2.paddingRight &&
+ patch1.paddingTop == patch2.paddingTop &&
+ patch1.paddingBottom == patch2.paddingBottom)) {
+ return false;
+ }
+ for (int i = 0; i < patch1.numColors; i++) {
+ if (patch1.colors[i] != patch2.colors[i]) {
+ return false;
+ }
+ }
+ for (int i = 0; i < patch1.numXDivs; i++) {
+ if (patch1.xDivs[i] != patch2.xDivs[i]) {
+ return false;
+ }
+ }
+ for (int i = 0; i < patch1.numYDivs; i++) {
+ if (patch1.yDivs[i] != patch2.yDivs[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void dump_image(int w, int h, png_bytepp rows, int color_type)
+{
+ int i, j, rr, gg, bb, aa;
+
+ int bpp;
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ bpp = 1;
+ } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ bpp = 2;
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ // We use a padding byte even when there is no alpha
+ bpp = 4;
+ } else {
+ printf("Unknown color type %d.\n", color_type);
+ }
+
+ for (j = 0; j < h; j++) {
+ png_bytep row = rows[j];
+ for (i = 0; i < w; i++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ NOISY(printf("\n"));
+ }
+ }
+ }
+}
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a) ((a)<0?-(a):(a))
+
+static void analyze_image(const char *imageName, image_info &imageInfo, int grayscaleTolerance,
+ png_colorp rgbPalette, png_bytep alphaPalette,
+ int *paletteEntries, bool *hasTransparency, int *colorType,
+ png_bytepp outRows)
+{
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
+
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+
+ // NOISY(printf("Initial image data:\n"));
+ // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
+
+ for (j = 0; j < h; j++) {
+ png_bytep row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa));
+ }
+
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa));
+ isGrayscale = false;
+ }
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa));
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ NOISY(printf("Found 257th color at %d, %d\n", i, j));
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
+ NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
+ NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
+ NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+ paletteSize, 2 * w * h, bpp * w * h));
+ NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+ } else {
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ printf("%s: forcing image to gray (max deviation = %d)\n", imageName, maxGrayDeviation);
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte) (col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ png_bytep row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
+ }
+ }
+}
+
+
+static void write_png(const char* imageName,
+ png_structp write_ptr, png_infop write_info,
+ image_info& imageInfo, int grayscaleTolerance)
+{
+ bool optimize = true;
+ png_uint_32 width, height;
+ int color_type;
+ int bit_depth, interlace_type, compression_type;
+ int i;
+
+ png_unknown_chunk unknowns[1];
+
+ png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep));
+ if (outRows == (png_bytepp) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (i = 0; i < (int) imageInfo.height; i++) {
+ outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
+ if (outRows[i] == (png_bytep) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ }
+
+ png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+
+ NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
+ (int) imageInfo.width, (int) imageInfo.height));
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &color_type, outRows);
+
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB ||
+ color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
+ color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ switch (color_type) {
+ case PNG_COLOR_TYPE_PALETTE:
+ NOISY(printf("Image %s has %d colors%s, using PNG_COLOR_TYPE_PALETTE\n",
+ imageName, paletteEntries,
+ hasTransparency ? " (with alpha)" : ""));
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
+ break;
+ }
+
+ png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
+ 8, color_type, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
+ }
+ png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (imageInfo.is9Patch) {
+ NOISY(printf("Adding 9-patch info...\n"));
+ strcpy((char*)unknowns[0].name, "npTc");
+ unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize();
+ unknowns[0].size = imageInfo.info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data);
+ png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
+ (png_byte*)"npTc", 1);
+ png_set_unknown_chunks(write_ptr, write_info, unknowns, 1);
+ // XXX I can't get this to work without forcibly changing
+ // the location to what I want... which apparently is supposed
+ // to be a private API, but everything else I have tried results
+ // in the location being set to what I -last- wrote so I never
+ // get written. :p
+ png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
+ }
+
+ png_write_info(write_ptr, write_info);
+
+ png_bytepp rows;
+ if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+ rows = imageInfo.rows;
+ } else {
+ rows = outRows;
+ }
+ png_write_image(write_ptr, rows);
+
+// NOISY(printf("Final image data:\n"));
+// dump_image(imageInfo.width, imageInfo.height, rows, color_type);
+
+ png_write_end(write_ptr, write_info);
+
+ for (i = 0; i < (int) imageInfo.height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+
+ png_get_IHDR(write_ptr, write_info, &width, &height,
+ &bit_depth, &color_type, &interlace_type,
+ &compression_type, NULL);
+
+ NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
+ (int)width, (int)height, bit_depth, color_type, interlace_type,
+ compression_type));
+}
+
+status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<AaptFile>& file, String8* outNewLeafName)
+{
+ String8 ext(file->getPath().getPathExtension());
+
+ // We currently only process PNG images.
+ if (strcmp(ext.string(), ".png") != 0) {
+ return NO_ERROR;
+ }
+
+ // Example of renaming a file:
+ //*outNewLeafName = file->getPath().getBasePath().getFileName();
+ //outNewLeafName->append(".nupng");
+
+ String8 printableName(file->getPrintableSource());
+
+ png_structp read_ptr = NULL;
+ png_infop read_info = NULL;
+ FILE* fp;
+
+ image_info imageInfo;
+
+ png_structp write_ptr = NULL;
+ png_infop write_info = NULL;
+
+ status_t error = UNKNOWN_ERROR;
+
+ const size_t nameLen = file->getPath().length();
+
+ fp = fopen(file->getSourceFile().string(), "rb");
+ if (fp == NULL) {
+ fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
+ goto bail;
+ }
+
+ read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
+ (png_error_ptr)NULL);
+ if (!read_ptr) {
+ goto bail;
+ }
+
+ read_info = png_create_info_struct(read_ptr);
+ if (!read_info) {
+ goto bail;
+ }
+
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ goto bail;
+ }
+
+ png_init_io(read_ptr, fp);
+
+ read_png(printableName.string(), read_ptr, read_info, &imageInfo);
+
+ if (nameLen > 6) {
+ const char* name = file->getPath().string();
+ if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
+ if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
+ goto bail;
+ }
+ }
+ }
+
+ write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
+ (png_error_ptr)NULL);
+ if (!write_ptr)
+ {
+ goto bail;
+ }
+
+ write_info = png_create_info_struct(write_ptr);
+ if (!write_info)
+ {
+ goto bail;
+ }
+
+ png_set_write_fn(write_ptr, (void*)file.get(),
+ png_write_aapt_file, png_flush_aapt_file);
+
+ if (setjmp(png_jmpbuf(write_ptr)))
+ {
+ goto bail;
+ }
+
+ write_png(printableName.string(), write_ptr, write_info, imageInfo,
+ bundle->getGrayscaleTolerance());
+
+ error = NO_ERROR;
+
+ if (bundle->getVerbose()) {
+ fseek(fp, 0, SEEK_END);
+ size_t oldSize = (size_t)ftell(fp);
+ size_t newSize = file->getSize();
+ float factor = ((float)newSize)/oldSize;
+ int percent = (int)(factor*100);
+ printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
+ }
+
+bail:
+ if (read_ptr) {
+ png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
+ }
+ if (fp) {
+ fclose(fp);
+ }
+ if (write_ptr) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ }
+
+ if (error != NO_ERROR) {
+ fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
+ file->getPrintableSource().string());
+ }
+ return error;
+}
+
+
+
+status_t postProcessImage(const sp<AaptAssets>& assets,
+ ResourceTable* table, const sp<AaptFile>& file)
+{
+ String8 ext(file->getPath().getPathExtension());
+
+ // At this point, now that we have all the resource data, all we need to
+ // do is compile XML files.
+ if (strcmp(ext.string(), ".xml") == 0) {
+ return compileXmlFile(assets, file, table);
+ }
+
+ return NO_ERROR;
+}
diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h
new file mode 100644
index 0000000..168e22f
--- /dev/null
+++ b/tools/aapt/Images.h
@@ -0,0 +1,18 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef IMAGES_H
+#define IMAGES_H
+
+#include "ResourceTable.h"
+
+status_t preProcessImage(Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<AaptFile>& file, String8* outNewLeafName);
+
+status_t postProcessImage(const sp<AaptAssets>& assets,
+ ResourceTable* table, const sp<AaptFile>& file);
+
+#endif
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
new file mode 100644
index 0000000..71b1a3c
--- /dev/null
+++ b/tools/aapt/Main.cpp
@@ -0,0 +1,369 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Android Asset Packaging Tool main entry point.
+//
+#include "Main.h"
+#include "Bundle.h"
+
+#include <utils.h>
+#include <utils/ZipFile.h>
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+
+using namespace android;
+
+static const char* gProgName = "aapt";
+
+/*
+ * When running under Cygwin on Windows, this will convert slash-based
+ * paths into back-slash-based ones. Otherwise the ApptAssets file comparisons
+ * fail later as they use back-slash separators under Windows.
+ *
+ * This operates in-place on the path string.
+ */
+void convertPath(char *path) {
+ if (path != NULL && OS_PATH_SEPARATOR != '/') {
+ for (; *path; path++) {
+ if (*path == '/') {
+ *path = OS_PATH_SEPARATOR;
+ }
+ }
+ }
+}
+
+/*
+ * Print usage info.
+ */
+void usage(void)
+{
+ fprintf(stderr, "Android Asset Packaging Tool\n\n");
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr,
+ " %s l[ist] [-v] [-a] file.{zip,jar,apk}\n"
+ " List contents of Zip-compatible archive.\n\n", gProgName);
+ fprintf(stderr,
+ " %s d[ump] WHAT file.{apk} [asset [asset ...]]\n"
+ " badging Print the label and icon for the app declared in APK.\n"
+ " permissions Print the permissions from the APK.\n"
+ " resources Print the resource table from the APK.\n"
+ " configurations Print the configurations in the APK.\n"
+ " xmltree Print the compiled xmls in the given assets.\n"
+ " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName);
+ fprintf(stderr,
+ " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
+ " [-0 extension [-0 extension ...]] \\\n"
+ " [-g tolerance] \\\n"
+ " [-j jarfile] \\\n"
+ " [-I base-package [-I base-package ...]] \\\n"
+ " [-A asset-source-dir] [-P public-definitions-file] \\\n"
+ " [-S resource-sources [-S resource-sources ...]] "
+ " [-F apk-file] [-J R-file-dir] \\\n"
+ " [raw-files-dir [raw-files-dir] ...]\n"
+ "\n"
+ " Package the android resources. It will read assets and resources that are\n"
+ " supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R\n"
+ " options control which files are output.\n\n"
+ , gProgName);
+ fprintf(stderr,
+ " %s r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
+ " Delete specified files from Zip-compatible archive.\n\n",
+ gProgName);
+ fprintf(stderr,
+ " %s a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
+ " Add specified files to Zip-compatible archive.\n\n", gProgName);
+ fprintf(stderr,
+ " %s v[ersion]\n"
+ " Print program version.\n\n", gProgName);
+ fprintf(stderr,
+ " Modifiers:\n"
+ " -a print Android-specific data (resources, manifest) when listing\n"
+ " -c specify which configurations to include. The default is all\n"
+ " configurations. The value of the parameter should be a comma\n"
+ " separated list of configuration values. Locales should be specified\n"
+ " as either a language or language-region pair. Some examples:\n"
+ " en\n"
+ " port,en\n"
+ " port,land,en_US\n"
+ " If you put the special locale, zz_ZZ on the list, it will perform\n"
+ " pseudolocalization on the default locale, modifying all of the\n"
+ " strings so you can look for strings that missed the\n"
+ " internationalization process. For example:\n"
+ " port,land,zz_ZZ\n"
+ " -d one or more device assets to include, separated by commas\n"
+ " -f force overwrite of existing files\n"
+ " -g specify a pixel tolerance to force images to grayscale, default 0\n"
+ " -j specify a jar or zip file containing classes to include\n"
+ " -m make package directories under location specified by -J\n"
+#if 0
+ " -p pseudolocalize the default configuration\n"
+#endif
+ " -u update existing packages (add new, replace older, remove deleted files)\n"
+ " -v verbose output\n"
+ " -x create extending (non-application) resource IDs\n"
+ " -z require localization of resource attributes marked with\n"
+ " localization=\"suggested\"\n"
+ " -A additional directory in which to find raw asset files\n"
+ " -F specify the apk file to output\n"
+ " -I add an existing package to base include set\n"
+ " -J specify where to output R.java resource constant definitions\n"
+ " -M specify full path to AndroidManifest.xml to include in zip\n"
+ " -P specify where to output public resource definitions\n"
+ " -S directory in which to find resources. Multiple directories will be scanned"
+ " and the first match found (left to right) will take precedence."
+ " -0 specifies an additional extension for which such files will not\n"
+ " be stored compressed in the .apk. An empty string means to not\n"
+ " compress any files at all.\n");
+}
+
+/*
+ * Dispatch the command.
+ */
+int handleCommand(Bundle* bundle)
+{
+ //printf("--- command %d (verbose=%d force=%d):\n",
+ // bundle->getCommand(), bundle->getVerbose(), bundle->getForce());
+ //for (int i = 0; i < bundle->getFileSpecCount(); i++)
+ // printf(" %d: '%s'\n", i, bundle->getFileSpecEntry(i));
+
+ switch (bundle->getCommand()) {
+ case kCommandVersion: return doVersion(bundle);
+ case kCommandList: return doList(bundle);
+ case kCommandDump: return doDump(bundle);
+ case kCommandAdd: return doAdd(bundle);
+ case kCommandRemove: return doRemove(bundle);
+ case kCommandPackage: return doPackage(bundle);
+ default:
+ fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
+ return 1;
+ }
+}
+
+/*
+ * Parse args.
+ */
+int main(int argc, char* const argv[])
+{
+ char *prog = argv[0];
+ Bundle bundle;
+ bool wantUsage = false;
+ int result = 1; // pessimistically assume an error.
+ int tolerance = 0;
+
+ /* default to compression */
+ bundle.setCompressionMethod(ZipEntry::kCompressDeflated);
+
+ if (argc < 2) {
+ wantUsage = true;
+ goto bail;
+ }
+
+ if (argv[1][0] == 'v')
+ bundle.setCommand(kCommandVersion);
+ else if (argv[1][0] == 'd')
+ bundle.setCommand(kCommandDump);
+ else if (argv[1][0] == 'l')
+ bundle.setCommand(kCommandList);
+ else if (argv[1][0] == 'a')
+ bundle.setCommand(kCommandAdd);
+ else if (argv[1][0] == 'r')
+ bundle.setCommand(kCommandRemove);
+ else if (argv[1][0] == 'p')
+ bundle.setCommand(kCommandPackage);
+ else {
+ fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
+ wantUsage = true;
+ goto bail;
+ }
+ argc -= 2;
+ argv += 2;
+
+ /*
+ * Pull out flags. We support "-fv" and "-f -v".
+ */
+ while (argc && argv[0][0] == '-') {
+ /* flag(s) found */
+ const char* cp = argv[0] +1;
+
+ while (*cp != '\0') {
+ switch (*cp) {
+ case 'v':
+ bundle.setVerbose(true);
+ break;
+ case 'a':
+ bundle.setAndroidList(true);
+ break;
+ case 'c':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-c' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.addConfigurations(argv[0]);
+ break;
+ case 'f':
+ bundle.setForce(true);
+ break;
+ case 'g':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-g' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ tolerance = atoi(argv[0]);
+ bundle.setGrayscaleTolerance(tolerance);
+ printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance);
+ break;
+ case 'm':
+ bundle.setMakePackageDirs(true);
+ break;
+#if 0
+ case 'p':
+ bundle.setPseudolocalize(true);
+ break;
+#endif
+ case 'u':
+ bundle.setUpdate(true);
+ break;
+ case 'x':
+ bundle.setExtending(true);
+ break;
+ case 'z':
+ bundle.setRequireLocalization(true);
+ break;
+ case 'j':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-j' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.addJarFile(argv[0]);
+ break;
+ case 'A':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-A' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setAssetSourceDir(argv[0]);
+ break;
+ case 'I':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-I' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.addPackageInclude(argv[0]);
+ break;
+ case 'F':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-F' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setOutputAPKFile(argv[0]);
+ break;
+ case 'J':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-J' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setRClassDir(argv[0]);
+ break;
+ case 'M':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setAndroidManifestFile(argv[0]);
+ break;
+ case 'P':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-P' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setPublicOutputFile(argv[0]);
+ break;
+ case 'S':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-S' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.addResourceSourceDir(argv[0]);
+ break;
+ case '0':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-e' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ if (argv[0][0] != 0) {
+ bundle.addNoCompressExtension(argv[0]);
+ } else {
+ bundle.setCompressionMethod(ZipEntry::kCompressStored);
+ }
+ break;
+ default:
+ fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp);
+ wantUsage = true;
+ goto bail;
+ }
+
+ cp++;
+ }
+ argc--;
+ argv++;
+ }
+
+ /*
+ * We're past the flags. The rest all goes straight in.
+ */
+ bundle.setFileSpec(argv, argc);
+
+ result = handleCommand(&bundle);
+
+bail:
+ if (wantUsage) {
+ usage();
+ result = 2;
+ }
+
+ //printf("--> returning %d\n", result);
+ return result;
+}
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
new file mode 100644
index 0000000..65c0a8a
--- /dev/null
+++ b/tools/aapt/Main.h
@@ -0,0 +1,41 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Some global defines that don't really merit their own header.
+//
+#ifndef __MAIN_H
+#define __MAIN_H
+
+#include <utils.h>
+#include "Bundle.h"
+#include "AaptAssets.h"
+#include <utils/ZipFile.h>
+
+extern int doVersion(Bundle* bundle);
+extern int doList(Bundle* bundle);
+extern int doDump(Bundle* bundle);
+extern int doAdd(Bundle* bundle);
+extern int doRemove(Bundle* bundle);
+extern int doPackage(Bundle* bundle);
+
+extern int calcPercent(long uncompressedLen, long compressedLen);
+
+extern android::status_t writeAPK(Bundle* bundle,
+ const sp<AaptAssets>& assets,
+ const android::String8& outputFile);
+
+extern android::status_t buildResources(Bundle* bundle,
+ const sp<AaptAssets>& assets);
+
+extern android::status_t writeResourceSymbols(Bundle* bundle,
+ const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate);
+
+extern bool isValidResourceType(const String8& type);
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
+
+extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets);
+
+int dumpResources(Bundle* bundle);
+
+#endif // __MAIN_H
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
new file mode 100644
index 0000000..eb7d6f5
--- /dev/null
+++ b/tools/aapt/Package.cpp
@@ -0,0 +1,464 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Package assets into Zip files.
+//
+#include "Main.h"
+#include "AaptAssets.h"
+#include "ResourceTable.h"
+
+#include <utils.h>
+#include <utils/ZipFile.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <errno.h>
+
+using namespace android;
+
+static const char* kExcludeExtension = ".EXCLUDE";
+
+/* these formats are already compressed, or don't compress well */
+static const char* kNoCompressExt[] = {
+ ".jpg", ".jpeg", ".png", ".gif",
+ ".wav", ".mp2", ".mp3", ".ogg", ".aac",
+ ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
+ ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
+ ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
+ ".amr", ".awb", ".wma", ".wmv"
+};
+
+/* fwd decls, so I can write this downward */
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip,
+ const sp<AaptDir>& dir, const AaptGroupEntry& ge);
+bool processFile(Bundle* bundle, ZipFile* zip,
+ const sp<AaptGroup>& group, const sp<AaptFile>& file);
+bool okayToCompress(Bundle* bundle, const String8& pathName);
+ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
+
+/*
+ * The directory hierarchy looks like this:
+ * "outputDir" and "assetRoot" are existing directories.
+ *
+ * On success, "bundle->numPackages" will be the number of Zip packages
+ * we created.
+ */
+status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
+ const String8& outputFile)
+{
+ status_t result = NO_ERROR;
+ ZipFile* zip = NULL;
+ int count;
+
+ //bundle->setPackageCount(0);
+
+ /*
+ * Prep the Zip archive.
+ *
+ * If the file already exists, fail unless "update" or "force" is set.
+ * If "update" is set, update the contents of the existing archive.
+ * Else, if "force" is set, remove the existing archive.
+ */
+ FileType fileType = getFileType(outputFile.string());
+ if (fileType == kFileTypeNonexistent) {
+ // okay, create it below
+ } else if (fileType == kFileTypeRegular) {
+ if (bundle->getUpdate()) {
+ // okay, open it below
+ } else if (bundle->getForce()) {
+ if (unlink(outputFile.string()) != 0) {
+ fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(),
+ strerror(errno));
+ goto bail;
+ }
+ } else {
+ fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n",
+ outputFile.string());
+ goto bail;
+ }
+ } else {
+ fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string());
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening",
+ outputFile.string());
+ }
+
+ status_t status;
+ zip = new ZipFile;
+ status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate);
+ if (status != NO_ERROR) {
+ fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n",
+ outputFile.string());
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("Writing all files...\n");
+ }
+
+ count = processAssets(bundle, zip, assets);
+ if (count < 0) {
+ fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
+ outputFile.string());
+ result = count;
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
+ }
+
+ count = processJarFiles(bundle, zip);
+ if (count < 0) {
+ fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
+ outputFile.string());
+ result = count;
+ goto bail;
+ }
+
+ if (bundle->getVerbose())
+ printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
+
+ result = NO_ERROR;
+
+ /*
+ * Check for cruft. We set the "marked" flag on all entries we created
+ * or decided not to update. If the entry isn't already slated for
+ * deletion, remove it now.
+ */
+ {
+ if (bundle->getVerbose())
+ printf("Checking for deleted files\n");
+ int i, removed = 0;
+ for (i = 0; i < zip->getNumEntries(); i++) {
+ ZipEntry* entry = zip->getEntryByIndex(i);
+
+ if (!entry->getMarked() && entry->getDeleted()) {
+ if (bundle->getVerbose()) {
+ printf(" (removing crufty '%s')\n",
+ entry->getFileName());
+ }
+ zip->remove(entry);
+ removed++;
+ }
+ }
+ if (bundle->getVerbose() && removed > 0)
+ printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
+ }
+
+ /* tell Zip lib to process deletions and other pending changes */
+ result = zip->flush();
+ if (result != NO_ERROR) {
+ fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
+ goto bail;
+ }
+
+ /* anything here? */
+ if (zip->getNumEntries() == 0) {
+ if (bundle->getVerbose()) {
+ printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string());
+ }
+ delete zip; // close the file so we can remove it in Win32
+ zip = NULL;
+ if (unlink(outputFile.string()) != 0) {
+ fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string());
+ }
+ }
+
+ assert(result == NO_ERROR);
+
+bail:
+ delete zip; // must close before remove in Win32
+ if (result != NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf("Removing %s due to earlier failures\n", outputFile.string());
+ }
+ if (unlink(outputFile.string()) != 0) {
+ fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string());
+ }
+ }
+
+ if (result == NO_ERROR && bundle->getVerbose())
+ printf("Done!\n");
+ return result;
+}
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip,
+ const sp<AaptAssets>& assets)
+{
+ ResourceFilter filter;
+ status_t status = filter.parse(bundle->getConfigurations());
+ if (status != NO_ERROR) {
+ return -1;
+ }
+
+ ssize_t count = 0;
+
+ const size_t N = assets->getGroupEntries().size();
+ for (size_t i=0; i<N; i++) {
+ const AaptGroupEntry& ge = assets->getGroupEntries()[i];
+ if (!filter.match(ge.toParams())) {
+ continue;
+ }
+ ssize_t res = processAssets(bundle, zip, assets, ge);
+ if (res < 0) {
+ return res;
+ }
+ count += res;
+ }
+
+ return count;
+}
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip,
+ const sp<AaptDir>& dir, const AaptGroupEntry& ge)
+{
+ ssize_t count = 0;
+
+ const size_t ND = dir->getDirs().size();
+ size_t i;
+ for (i=0; i<ND; i++) {
+ ssize_t res = processAssets(bundle, zip, dir->getDirs().valueAt(i), ge);
+ if (res < 0) {
+ return res;
+ }
+ count += res;
+ }
+
+ const size_t NF = dir->getFiles().size();
+ for (i=0; i<NF; i++) {
+ sp<AaptGroup> gp = dir->getFiles().valueAt(i);
+ ssize_t fi = gp->getFiles().indexOfKey(ge);
+ if (fi >= 0) {
+ sp<AaptFile> fl = gp->getFiles().valueAt(fi);
+ if (!processFile(bundle, zip, gp, fl)) {
+ return UNKNOWN_ERROR;
+ }
+ count++;
+ }
+ }
+
+ return count;
+}
+
+/*
+ * Process a regular file, adding it to the archive if appropriate.
+ *
+ * If we're in "update" mode, and the file already exists in the archive,
+ * delete the existing entry before adding the new one.
+ */
+bool processFile(Bundle* bundle, ZipFile* zip,
+ const sp<AaptGroup>& group, const sp<AaptFile>& file)
+{
+ const bool hasData = file->hasData();
+
+ String8 storageName(group->getPath());
+ storageName.convertToResPath();
+ ZipEntry* entry;
+ bool fromGzip = false;
+ status_t result;
+
+ /*
+ * See if the filename ends in ".EXCLUDE". We can't use
+ * String8::getPathExtension() because the length of what it considers
+ * to be an extension is capped.
+ *
+ * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives,
+ * so there's no value in adding them (and it makes life easier on
+ * the AssetManager lib if we don't).
+ *
+ * NOTE: this restriction has been removed. If you're in this code, you
+ * should clean this up, but I'm in here getting rid of Path Name, and I
+ * don't want to make other potentially breaking changes --joeo
+ */
+ int fileNameLen = storageName.length();
+ int excludeExtensionLen = strlen(kExcludeExtension);
+ if (fileNameLen > excludeExtensionLen
+ && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen),
+ kExcludeExtension))) {
+ fprintf(stderr, "WARNING: '%s' not added to Zip\n", storageName.string());
+ return true;
+ }
+
+ if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) {
+ fromGzip = true;
+ storageName = storageName.getBasePath();
+ }
+
+ if (bundle->getUpdate()) {
+ entry = zip->getEntryByName(storageName.string());
+ if (entry != NULL) {
+ /* file already exists in archive; there can be only one */
+ if (entry->getMarked()) {
+ fprintf(stderr,
+ "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
+ file->getPrintableSource().string());
+ return false;
+ }
+ if (!hasData) {
+ const String8& srcName = file->getSourceFile();
+ time_t fileModWhen;
+ fileModWhen = getFileModDate(srcName.string());
+ if (fileModWhen == (time_t) -1) { // file existence tested earlier,
+ return false; // not expecting an error here
+ }
+
+ if (fileModWhen > entry->getModWhen()) {
+ // mark as deleted so add() will succeed
+ if (bundle->getVerbose()) {
+ printf(" (removing old '%s')\n", storageName.string());
+ }
+
+ zip->remove(entry);
+ } else {
+ // version in archive is newer
+ if (bundle->getVerbose()) {
+ printf(" (not updating '%s')\n", storageName.string());
+ }
+ entry->setMarked(true);
+ return true;
+ }
+ } else {
+ // Generated files are always replaced.
+ zip->remove(entry);
+ }
+ }
+ }
+
+ //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE);
+
+ if (fromGzip) {
+ result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry);
+ } else if (!hasData) {
+ /* don't compress certain files, e.g. PNGs */
+ int compressionMethod = bundle->getCompressionMethod();
+ if (!okayToCompress(bundle, storageName)) {
+ compressionMethod = ZipEntry::kCompressStored;
+ }
+ result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod,
+ &entry);
+ } else {
+ result = zip->add(file->getData(), file->getSize(), storageName.string(),
+ file->getCompressionMethod(), &entry);
+ }
+ if (result == NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : "");
+ if (entry->getCompressionMethod() == ZipEntry::kCompressStored) {
+ printf(" (not compressed)\n");
+ } else {
+ printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(),
+ entry->getCompressedLen()));
+ }
+ }
+ entry->setMarked(true);
+ } else {
+ if (result == ALREADY_EXISTS) {
+ fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n",
+ file->getPrintableSource().string());
+ } else {
+ fprintf(stderr, " Unable to add '%s': Zip add failed\n",
+ file->getPrintableSource().string());
+ }
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Determine whether or not we want to try to compress this file based
+ * on the file extension.
+ */
+bool okayToCompress(Bundle* bundle, const String8& pathName)
+{
+ String8 ext = pathName.getPathExtension();
+ int i;
+
+ if (ext.length() == 0)
+ return true;
+
+ for (i = 0; i < NELEM(kNoCompressExt); i++) {
+ if (strcasecmp(ext.string(), kNoCompressExt[i]) == 0)
+ return false;
+ }
+
+ const android::Vector<const char*>& others(bundle->getNoCompressExtensions());
+ for (i = 0; i < (int)others.size(); i++) {
+ const char* str = others[i];
+ int pos = pathName.length() - strlen(str);
+ if (pos < 0) {
+ continue;
+ }
+ const char* path = pathName.string();
+ if (strcasecmp(path + pos, str) == 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool endsWith(const char* haystack, const char* needle)
+{
+ size_t a = strlen(haystack);
+ size_t b = strlen(needle);
+ if (a < b) return false;
+ return strcasecmp(haystack+(a-b), needle) == 0;
+}
+
+ssize_t processJarFile(ZipFile* jar, ZipFile* out)
+{
+ status_t err;
+ size_t N = jar->getNumEntries();
+ size_t count = 0;
+ for (size_t i=0; i<N; i++) {
+ ZipEntry* entry = jar->getEntryByIndex(i);
+ const char* storageName = entry->getFileName();
+ if (endsWith(storageName, ".class")) {
+ int compressionMethod = entry->getCompressionMethod();
+ size_t size = entry->getUncompressedLen();
+ const void* data = jar->uncompress(entry);
+ if (data == NULL) {
+ fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n",
+ storageName);
+ return -1;
+ }
+ out->add(data, size, storageName, compressionMethod, NULL);
+ free((void*)data);
+ }
+ count++;
+ }
+ return count;
+}
+
+ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
+{
+ ssize_t err;
+ ssize_t count = 0;
+ const android::Vector<const char*>& jars = bundle->getJarFiles();
+
+ size_t N = jars.size();
+ for (size_t i=0; i<N; i++) {
+ ZipFile jar;
+ err = jar.open(jars[i], ZipFile::kOpenReadOnly);
+ if (err != 0) {
+ fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %zd\n",
+ jars[i], err);
+ return err;
+ }
+ err += processJarFile(&jar, zip);
+ if (err < 0) {
+ fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]);
+ return err;
+ }
+ count += err;
+ }
+
+ return count;
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
new file mode 100644
index 0000000..b2bd9ff
--- /dev/null
+++ b/tools/aapt/Resource.cpp
@@ -0,0 +1,1524 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+#include "Main.h"
+#include "AaptAssets.h"
+#include "StringPool.h"
+#include "XMLNode.h"
+#include "ResourceTable.h"
+#include "Images.h"
+
+#define NOISY(x) // x
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+class PackageInfo
+{
+public:
+ PackageInfo()
+ {
+ }
+ ~PackageInfo()
+ {
+ }
+
+ status_t parsePackage(const sp<AaptGroup>& grp);
+};
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+static String8 parseResourceName(const String8& leaf)
+{
+ const char* firstDot = strchr(leaf.string(), '.');
+ const char* str = leaf.string();
+
+ if (firstDot) {
+ return String8(str, firstDot-str);
+ } else {
+ return String8(str);
+ }
+}
+
+class ResourceTypeSet : public RefBase,
+ public KeyedVector<String8,sp<AaptGroup> >
+{
+public:
+ ResourceTypeSet();
+};
+
+ResourceTypeSet::ResourceTypeSet()
+ :RefBase(),
+ KeyedVector<String8,sp<AaptGroup> >()
+{
+}
+
+class ResourceDirIterator
+{
+public:
+ ResourceDirIterator(const sp<ResourceTypeSet>& set, const String8& resType)
+ : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0)
+ {
+ }
+
+ inline const sp<AaptGroup>& getGroup() const { return mGroup; }
+ inline const sp<AaptFile>& getFile() const { return mFile; }
+
+ inline const String8& getBaseName() const { return mBaseName; }
+ inline const String8& getLeafName() const { return mLeafName; }
+ inline String8 getPath() const { return mPath; }
+ inline const ResTable_config& getParams() const { return mParams; }
+
+ enum {
+ EOD = 1
+ };
+
+ ssize_t next()
+ {
+ while (true) {
+ sp<AaptGroup> group;
+ sp<AaptFile> file;
+
+ // Try to get next file in this current group.
+ if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) {
+ group = mGroup;
+ file = group->getFiles().valueAt(mGroupPos++);
+
+ // Try to get the next group/file in this directory
+ } else if (mSetPos < mSet->size()) {
+ mGroup = group = mSet->valueAt(mSetPos++);
+ if (group->getFiles().size() < 1) {
+ continue;
+ }
+ file = group->getFiles().valueAt(0);
+ mGroupPos = 1;
+
+ // All done!
+ } else {
+ return EOD;
+ }
+
+ mFile = file;
+
+ String8 leaf(group->getLeaf());
+ mLeafName = String8(leaf);
+ mParams = file->getGroupEntry().toParams();
+ NOISY(printf("Dir %s: mcc=%d mnc=%d lang=%c%c cnt=%c%c orient=%d density=%d touch=%d key=%d inp=%d nav=%d\n",
+ group->getPath().string(), mParams.mcc, mParams.mnc,
+ mParams.language[0] ? mParams.language[0] : '-',
+ mParams.language[1] ? mParams.language[1] : '-',
+ mParams.country[0] ? mParams.country[0] : '-',
+ mParams.country[1] ? mParams.country[1] : '-',
+ mParams.orientation,
+ mParams.density, mParams.touchscreen, mParams.keyboard,
+ mParams.inputFlags, mParams.navigation));
+ mPath = "res";
+ mPath.appendPath(file->getGroupEntry().toDirName(mResType));
+ mPath.appendPath(leaf);
+ mBaseName = parseResourceName(leaf);
+ if (mBaseName == "") {
+ fprintf(stderr, "Error: malformed resource filename %s\n",
+ file->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+
+ NOISY(printf("file name=%s\n", mBaseName.string()));
+
+ return NO_ERROR;
+ }
+ }
+
+private:
+ String8 mResType;
+
+ const sp<ResourceTypeSet> mSet;
+ size_t mSetPos;
+
+ sp<AaptGroup> mGroup;
+ size_t mGroupPos;
+
+ sp<AaptFile> mFile;
+ String8 mBaseName;
+ String8 mLeafName;
+ String8 mPath;
+ ResTable_config mParams;
+};
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+bool isValidResourceType(const String8& type)
+{
+ return type == "anim" || type == "drawable" || type == "layout"
+ || type == "values" || type == "xml" || type == "raw"
+ || type == "color" || type == "menu";
+}
+
+static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true)
+{
+ sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc"));
+ sp<AaptFile> file;
+ if (group != NULL) {
+ file = group->getFiles().valueFor(AaptGroupEntry());
+ if (file != NULL) {
+ return file;
+ }
+ }
+
+ if (!makeIfNecessary) {
+ return NULL;
+ }
+ return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(),
+ NULL, String8());
+}
+
+static status_t parsePackage(const sp<AaptAssets>& assets, const sp<AaptGroup>& grp)
+{
+ if (grp->getFiles().size() != 1) {
+ fprintf(stderr, "WARNING: Multiple AndroidManifest.xml files found, using %s\n",
+ grp->getFiles().valueAt(0)->getPrintableSource().string());
+ }
+
+ sp<AaptFile> file = grp->getFiles().valueAt(0);
+
+ ResXMLTree block;
+ status_t err = parseXMLResource(file, &block);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ //printXMLBlock(&block);
+
+ ResXMLTree::event_code_t code;
+ while ((code=block.next()) != ResXMLTree::START_TAG
+ && code != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ }
+
+ size_t len;
+ if (code != ResXMLTree::START_TAG) {
+ fprintf(stderr, "%s:%d: No start tag found\n",
+ file->getPrintableSource().string(), block.getLineNumber());
+ return UNKNOWN_ERROR;
+ }
+ if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
+ fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
+ file->getPrintableSource().string(), block.getLineNumber(),
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
+ if (nameIndex < 0) {
+ fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
+ file->getPrintableSource().string(), block.getLineNumber());
+ return UNKNOWN_ERROR;
+ }
+
+ assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
+
+ return NO_ERROR;
+}
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
+ ResourceTable* table,
+ const sp<ResourceTypeSet>& set,
+ const char* resType)
+{
+ String8 type8(resType);
+ String16 type16(resType);
+
+ bool hasErrors = false;
+
+ ResourceDirIterator it(set, String8(resType));
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf(" (new resource id %s from %s)\n",
+ it.getBaseName().string(), it.getFile()->getPrintableSource().string());
+ }
+ String16 baseName(it.getBaseName());
+ const char16_t* str = baseName.string();
+ const char16_t* const end = str + baseName.size();
+ while (str < end) {
+ if (!((*str >= 'a' && *str <= 'z')
+ || (*str >= '0' && *str <= '9')
+ || *str == '_' || *str == '.')) {
+ fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
+ it.getPath().string());
+ hasErrors = true;
+ }
+ str++;
+ }
+ String8 resPath = it.getPath();
+ resPath.convertToResPath();
+ table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
+ type16,
+ baseName,
+ String16(resPath),
+ NULL,
+ &it.getParams());
+ assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<ResourceTypeSet>& set)
+{
+ ResourceDirIterator it(set, String8("drawable"));
+ Vector<sp<AaptFile> > newNameFiles;
+ Vector<String8> newNamePaths;
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ res = preProcessImage(bundle, assets, it.getFile(), NULL);
+ if (res != NO_ERROR) {
+ return res;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t postProcessImages(const sp<AaptAssets>& assets,
+ ResourceTable* table,
+ const sp<ResourceTypeSet>& set)
+{
+ ResourceDirIterator it(set, String8("drawable"));
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ res = postProcessImage(assets, table, it.getFile());
+ if (res != NO_ERROR) {
+ return res;
+ }
+ }
+
+ return res < NO_ERROR ? res : (status_t)NO_ERROR;
+}
+
+static void collect_files(const sp<AaptDir>& dir,
+ KeyedVector<String8, sp<ResourceTypeSet> >* resources)
+{
+ const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
+ int N = groups.size();
+ for (int i=0; i<N; i++) {
+ String8 leafName = groups.keyAt(i);
+ const sp<AaptGroup>& group = groups.valueAt(i);
+
+ const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
+ = group->getFiles();
+
+ if (files.size() == 0) {
+ continue;
+ }
+
+ String8 resType = files.valueAt(0)->getResourceType();
+
+ ssize_t index = resources->indexOfKey(resType);
+
+ if (index < 0) {
+ sp<ResourceTypeSet> set = new ResourceTypeSet();
+ set->add(leafName, group);
+ resources->add(resType, set);
+ } else {
+ sp<ResourceTypeSet> set = resources->valueAt(index);
+ index = set->indexOfKey(leafName);
+ if (index < 0) {
+ set->add(leafName, group);
+ } else {
+ sp<AaptGroup> existingGroup = set->valueAt(index);
+ int M = files.size();
+ for (int j=0; j<M; j++) {
+ existingGroup->addFile(files.valueAt(j));
+ }
+ }
+ }
+ }
+}
+
+static void collect_files(const sp<AaptAssets>& ass,
+ KeyedVector<String8, sp<ResourceTypeSet> >* resources)
+{
+ const Vector<sp<AaptDir> >& dirs = ass->resDirs();
+ int N = dirs.size();
+
+ for (int i=0; i<N; i++) {
+ sp<AaptDir> d = dirs.itemAt(i);
+ collect_files(d, resources);
+
+ // don't try to include the res dir
+ ass->removeDir(d->getLeaf());
+ }
+}
+
+enum {
+ ATTR_OKAY = -1,
+ ATTR_NOT_FOUND = -2,
+ ATTR_LEADING_SPACES = -3,
+ ATTR_TRAILING_SPACES = -4
+};
+static int validateAttr(const String8& path, const ResXMLParser& parser,
+ const char* ns, const char* attr, const char* validChars, bool required)
+{
+ size_t len;
+
+ ssize_t index = parser.indexOfAttribute(ns, attr);
+ const uint16_t* str;
+ if (index >= 0 && (str=parser.getAttributeStringValue(index, &len)) != NULL) {
+ if (validChars) {
+ for (size_t i=0; i<len; i++) {
+ uint16_t c = str[i];
+ const char* p = validChars;
+ bool okay = false;
+ while (*p) {
+ if (c == *p) {
+ okay = true;
+ break;
+ }
+ p++;
+ }
+ if (!okay) {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid character '%c'.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr, (char)str[i]);
+ return (int)i;
+ }
+ }
+ }
+ if (*str == ' ') {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not start with a space.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_LEADING_SPACES;
+ }
+ if (str[len-1] == ' ') {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not end with a space.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_TRAILING_SPACES;
+ }
+ return ATTR_OKAY;
+ }
+ if (required) {
+ fprintf(stderr, "%s:%d: Tag <%s> missing required attribute %s.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_NOT_FOUND;
+ }
+ return ATTR_OKAY;
+}
+
+static void checkForIds(const String8& path, ResXMLParser& parser)
+{
+ ResXMLTree::event_code_t code;
+ while ((code=parser.next()) != ResXMLTree::END_DOCUMENT
+ && code > ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ ssize_t index = parser.indexOfAttribute(NULL, "id");
+ if (index >= 0) {
+ fprintf(stderr, "%s:%d: WARNING: found plain 'id' attribute; did you mean the new 'android:id' name?\n",
+ path.string(), parser.getLineNumber());
+ }
+ }
+ }
+}
+
+static void applyFileOverlay(const sp<AaptAssets>& assets,
+ const sp<ResourceTypeSet>& baseSet,
+ const char *resType)
+{
+ // Replace any base level files in this category with any found from the overlay
+ // Also add any found only in the overlay.
+ sp<AaptAssets> overlay = assets->getOverlay();
+ String8 resTypeString(resType);
+
+ // work through the linked list of overlays
+ while (overlay.get()) {
+ KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources();
+
+ // get the overlay resources of the requested type
+ ssize_t index = overlayRes->indexOfKey(resTypeString);
+ if (index >= 0) {
+ sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index);
+
+ // for each of the resources, check for a match in the previously built
+ // non-overlay "baseset".
+ size_t overlayCount = overlaySet->size();
+ for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) {
+ size_t baseIndex = baseSet->indexOfKey(overlaySet->keyAt(overlayIndex));
+ if (baseIndex != UNKNOWN_ERROR) {
+ // look for same flavor. For a given file (strings.xml, for example)
+ // there may be a locale specific or other flavors - we want to match
+ // the same flavor.
+ sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
+ sp<AaptGroup> baseGroup = baseSet->valueAt(baseIndex);
+
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
+ baseGroup->getFiles();
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
+ overlayGroup->getFiles();
+ size_t overlayGroupSize = overlayFiles.size();
+ for (size_t overlayGroupIndex = 0;
+ overlayGroupIndex<overlayGroupSize;
+ overlayGroupIndex++) {
+ size_t baseFileIndex =
+ baseFiles.indexOfKey(overlayFiles.keyAt(overlayGroupIndex));
+ if(baseFileIndex < UNKNOWN_ERROR) {
+ baseGroup->removeFile(baseFileIndex);
+ } else {
+ // didn't find a match fall through and add it..
+ }
+ baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex));
+ }
+ } else {
+ // this group doesn't exist (a file that's only in the overlay)
+ // add it
+ baseSet->add(overlaySet->keyAt(overlayIndex),
+ overlaySet->valueAt(overlayIndex));
+ }
+ }
+ // this overlay didn't have resources for this type
+ }
+ // try next overlay
+ overlay = overlay->getOverlay();
+ }
+ return;
+}
+
+#define ASSIGN_IT(n) \
+ do { \
+ ssize_t index = resources->indexOfKey(String8(#n)); \
+ if (index >= 0) { \
+ n ## s = resources->valueAt(index); \
+ } \
+ } while (0)
+
+status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ // First, look for a package file to parse. This is required to
+ // be able to generate the resource information.
+ sp<AaptGroup> androidManifestFile =
+ assets->getFiles().valueFor(String8("AndroidManifest.xml"));
+ if (androidManifestFile == NULL) {
+ fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
+ return UNKNOWN_ERROR;
+ }
+
+ status_t err = parsePackage(assets, androidManifestFile);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Creating resources for package %s\n",
+ assets->getPackage().string()));
+
+ ResourceTable table(bundle, String16(assets->getPackage()));
+ err = table.addIncludedResources(bundle, assets);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Found %d included resource packages\n", (int)table.size()));
+
+ // --------------------------------------------------------------
+ // First, gather all resource information.
+ // --------------------------------------------------------------
+
+ // resType -> leafName -> group
+ KeyedVector<String8, sp<ResourceTypeSet> > *resources =
+ new KeyedVector<String8, sp<ResourceTypeSet> >;
+ collect_files(assets, resources);
+
+ sp<ResourceTypeSet> drawables;
+ sp<ResourceTypeSet> layouts;
+ sp<ResourceTypeSet> anims;
+ sp<ResourceTypeSet> xmls;
+ sp<ResourceTypeSet> raws;
+ sp<ResourceTypeSet> colors;
+ sp<ResourceTypeSet> menus;
+
+ ASSIGN_IT(drawable);
+ ASSIGN_IT(layout);
+ ASSIGN_IT(anim);
+ ASSIGN_IT(xml);
+ ASSIGN_IT(raw);
+ ASSIGN_IT(color);
+ ASSIGN_IT(menu);
+
+ assets->setResources(resources);
+ // now go through any resource overlays and collect their files
+ sp<AaptAssets> current = assets->getOverlay();
+ while(current.get()) {
+ KeyedVector<String8, sp<ResourceTypeSet> > *resources =
+ new KeyedVector<String8, sp<ResourceTypeSet> >;
+ current->setResources(resources);
+ collect_files(current, resources);
+ current = current->getOverlay();
+ }
+ // apply the overlay files to the base set
+ applyFileOverlay(assets, drawables, "drawable");
+ applyFileOverlay(assets, layouts, "layout");
+ applyFileOverlay(assets, anims, "anim");
+ applyFileOverlay(assets, xmls, "xml");
+ applyFileOverlay(assets, raws, "raw");
+ applyFileOverlay(assets, colors, "color");
+ applyFileOverlay(assets, menus, "menu");
+
+ bool hasErrors = false;
+
+ if (drawables != NULL) {
+ err = preProcessImages(bundle, assets, drawables);
+ if (err == NO_ERROR) {
+ err = makeFileResources(bundle, assets, &table, drawables, "drawable");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ } else {
+ hasErrors = true;
+ }
+ }
+
+ if (layouts != NULL) {
+ err = makeFileResources(bundle, assets, &table, layouts, "layout");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (anims != NULL) {
+ err = makeFileResources(bundle, assets, &table, anims, "anim");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (xmls != NULL) {
+ err = makeFileResources(bundle, assets, &table, xmls, "xml");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (raws != NULL) {
+ err = makeFileResources(bundle, assets, &table, raws, "raw");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ // compile resources
+ current = assets;
+ while(current.get()) {
+ KeyedVector<String8, sp<ResourceTypeSet> > *resources =
+ current->getResources();
+
+ ssize_t index = resources->indexOfKey(String8("values"));
+ if (index >= 0) {
+ ResourceDirIterator it(resources->valueAt(index), String8("values"));
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ sp<AaptFile> file = it.getFile();
+ res = compileResourceFile(bundle, assets, file, it.getParams(),
+ (current!=assets), &table);
+ if (res != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+ }
+ current = current->getOverlay();
+ }
+
+ if (colors != NULL) {
+ err = makeFileResources(bundle, assets, &table, colors, "color");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (menus != NULL) {
+ err = makeFileResources(bundle, assets, &table, menus, "menu");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ // --------------------------------------------------------------------
+ // Assignment of resource IDs and initial generation of resource table.
+ // --------------------------------------------------------------------
+
+ if (table.hasResources()) {
+ sp<AaptFile> resFile(getResourceFile(assets));
+ if (resFile == NULL) {
+ fprintf(stderr, "Error: unable to generate entry for resource data\n");
+ return UNKNOWN_ERROR;
+ }
+
+ err = table.assignResourceIds();
+ if (err < NO_ERROR) {
+ return err;
+ }
+ }
+
+ // --------------------------------------------------------------
+ // Finally, we can now we can compile XML files, which may reference
+ // resources.
+ // --------------------------------------------------------------
+
+ if (layouts != NULL) {
+ ResourceDirIterator it(layouts, String8("layout"));
+ while ((err=it.next()) == NO_ERROR) {
+ String8 src = it.getFile()->getPrintableSource();
+ err = compileXmlFile(assets, it.getFile(), &table);
+ if (err == NO_ERROR) {
+ ResXMLTree block;
+ block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+ checkForIds(src, block);
+ } else {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (anims != NULL) {
+ ResourceDirIterator it(anims, String8("anim"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (xmls != NULL) {
+ ResourceDirIterator it(xmls, String8("xml"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (drawables != NULL) {
+ err = postProcessImages(assets, &table, drawables);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (colors != NULL) {
+ ResourceDirIterator it(colors, String8("color"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (menus != NULL) {
+ ResourceDirIterator it(menus, String8("menu"));
+ while ((err=it.next()) == NO_ERROR) {
+ String8 src = it.getFile()->getPrintableSource();
+ err = compileXmlFile(assets, it.getFile(), &table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ ResXMLTree block;
+ block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+ checkForIds(src, block);
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
+ String8 manifestPath(manifestFile->getPrintableSource());
+
+ // Perform a basic validation of the manifest file. This time we
+ // parse it with the comments intact, so that we can use them to
+ // generate java docs... so we are not going to write this one
+ // back out to the final manifest data.
+ err = compileXmlFile(assets, manifestFile, &table,
+ XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+ if (err < NO_ERROR) {
+ return err;
+ }
+ ResXMLTree block;
+ block.setTo(manifestFile->getData(), manifestFile->getSize(), true);
+ String16 manifest16("manifest");
+ String16 permission16("permission");
+ String16 permission_group16("permission-group");
+ String16 uses_permission16("uses-permission");
+ String16 instrumentation16("instrumentation");
+ String16 application16("application");
+ String16 provider16("provider");
+ String16 service16("service");
+ String16 receiver16("receiver");
+ String16 activity16("activity");
+ String16 action16("action");
+ String16 category16("category");
+ String16 data16("scheme");
+ const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
+ const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
+ const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$";
+ const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:";
+ const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;";
+ const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+";
+ const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
+ ResXMLTree::event_code_t code;
+ sp<AaptSymbols> permissionSymbols;
+ sp<AaptSymbols> permissionGroupSymbols;
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code > ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ size_t len;
+ if (block.getElementNamespace(&len) != NULL) {
+ continue;
+ }
+ if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
+ if (validateAttr(manifestPath, block, NULL, "package",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0
+ || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) {
+ const bool isGroup = strcmp16(block.getElementName(&len),
+ permission_group16.string()) == 0;
+ if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
+ isGroup ? packageIdentCharsWithTheStupid
+ : packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ SourcePos srcPos(manifestPath, block.getLineNumber());
+ sp<AaptSymbols> syms;
+ if (!isGroup) {
+ syms = permissionSymbols;
+ if (syms == NULL) {
+ sp<AaptSymbols> symbols =
+ assets->getSymbolsFor(String8("Manifest"));
+ syms = permissionSymbols = symbols->addNestedSymbol(
+ String8("permission"), srcPos);
+ }
+ } else {
+ syms = permissionGroupSymbols;
+ if (syms == NULL) {
+ sp<AaptSymbols> symbols =
+ assets->getSymbolsFor(String8("Manifest"));
+ syms = permissionGroupSymbols = symbols->addNestedSymbol(
+ String8("permission_group"), srcPos);
+ }
+ }
+ size_t len;
+ ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name");
+ const uint16_t* id = block.getAttributeStringValue(index, &len);
+ if (id == NULL) {
+ fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n",
+ manifestPath.string(), block.getLineNumber(),
+ String8(block.getElementName(&len)).string());
+ hasErrors = true;
+ break;
+ }
+ String8 idStr(id);
+ char* p = idStr.lockBuffer(idStr.size());
+ char* e = p + idStr.size();
+ bool begins_with_digit = true; // init to true so an empty string fails
+ while (e > p) {
+ e--;
+ if (*e >= '0' && *e <= '9') {
+ begins_with_digit = true;
+ continue;
+ }
+ if ((*e >= 'a' && *e <= 'z') ||
+ (*e >= 'A' && *e <= 'Z') ||
+ (*e == '_')) {
+ begins_with_digit = false;
+ continue;
+ }
+ if (isGroup && (*e == '-')) {
+ *e = '_';
+ begins_with_digit = false;
+ continue;
+ }
+ e++;
+ break;
+ }
+ idStr.unlockBuffer();
+ // verify that we stopped because we hit a period or
+ // the beginning of the string, and that the
+ // identifier didn't begin with a digit.
+ if (begins_with_digit || (e != p && *(e-1) != '.')) {
+ fprintf(stderr,
+ "%s:%d: Permission name <%s> is not a valid Java symbol\n",
+ manifestPath.string(), block.getLineNumber(), idStr.string());
+ hasErrors = true;
+ }
+ syms->addStringSymbol(String8(e), idStr, srcPos);
+ const uint16_t* cmt = block.getComment(&len);
+ if (cmt != NULL && *cmt != 0) {
+ //printf("Comment of %s: %s\n", String8(e).string(),
+ // String8(cmt).string());
+ syms->appendComment(String8(e), String16(cmt), srcPos);
+ } else {
+ //printf("No comment for %s\n", String8(e).string());
+ }
+ syms->makeSymbolPublic(String8(e), srcPos);
+ } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
+ if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) {
+ if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
+ classIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "targetPackage",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) {
+ if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
+ classIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "permission",
+ packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "process",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) {
+ if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
+ classIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "authorities",
+ authoritiesIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "permission",
+ packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "process",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), service16.string()) == 0
+ || strcmp16(block.getElementName(&len), receiver16.string()) == 0
+ || strcmp16(block.getElementName(&len), activity16.string()) == 0) {
+ if (validateAttr(manifestPath, block, RESOURCES_ANDROID_NAMESPACE, "name",
+ classIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "permission",
+ packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "process",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), action16.string()) == 0
+ || strcmp16(block.getElementName(&len), category16.string()) == 0) {
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "name",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) {
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "mimeType",
+ typeIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, block,
+ RESOURCES_ANDROID_NAMESPACE, "scheme",
+ schemeIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ }
+ }
+ }
+
+ if (table.validateLocalizations()) {
+ hasErrors = true;
+ }
+
+ if (hasErrors) {
+ return UNKNOWN_ERROR;
+ }
+
+ // Generate final compiled manifest file.
+ manifestFile->clearData();
+ err = compileXmlFile(assets, manifestFile, &table);
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ //block.restart();
+ //printXMLBlock(&block);
+
+ // --------------------------------------------------------------
+ // Generate the final resource table.
+ // Re-flatten because we may have added new resource IDs
+ // --------------------------------------------------------------
+
+ if (table.hasResources()) {
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ err = table.addSymbols(symbols);
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ sp<AaptFile> resFile(getResourceFile(assets));
+ if (resFile == NULL) {
+ fprintf(stderr, "Error: unable to generate entry for resource data\n");
+ return UNKNOWN_ERROR;
+ }
+
+ err = table.flatten(bundle, resFile);
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ if (bundle->getPublicOutputFile()) {
+ FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n",
+ (const char*)bundle->getPublicOutputFile(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+ if (bundle->getVerbose()) {
+ printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile());
+ }
+ table.writePublicDefinitions(String16(assets->getPackage()), fp);
+ }
+
+ NOISY(
+ ResTable rt;
+ rt.add(resFile->getData(), resFile->getSize(), NULL);
+ printf("Generated resources:\n");
+ rt.print();
+ )
+
+ // These resources are now considered to be a part of the included
+ // resources, for others to reference.
+ err = assets->addIncludedResources(resFile);
+ if (err < NO_ERROR) {
+ fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
+ return err;
+ }
+ }
+
+ return err;
+}
+
+static const char* getIndentSpace(int indent)
+{
+static const char whitespace[] =
+" ";
+
+ return whitespace + sizeof(whitespace) - 1 - indent*4;
+}
+
+static status_t fixupSymbol(String16* inoutSymbol)
+{
+ inoutSymbol->replaceAll('.', '_');
+ inoutSymbol->replaceAll(':', '_');
+ return NO_ERROR;
+}
+
+static String16 getAttributeComment(const sp<AaptAssets>& assets,
+ const String8& name,
+ String16* outTypeComment = NULL)
+{
+ sp<AaptSymbols> asym = assets->getSymbolsFor(String8("R"));
+ if (asym != NULL) {
+ //printf("Got R symbols!\n");
+ asym = asym->getNestedSymbols().valueFor(String8("attr"));
+ if (asym != NULL) {
+ //printf("Got attrs symbols! comment %s=%s\n",
+ // name.string(), String8(asym->getComment(name)).string());
+ if (outTypeComment != NULL) {
+ *outTypeComment = asym->getTypeComment(name);
+ }
+ return asym->getComment(name);
+ }
+ }
+ return String16();
+}
+
+static status_t writeLayoutClasses(
+ FILE* fp, const sp<AaptAssets>& assets,
+ const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
+{
+ const char* indentStr = getIndentSpace(indent);
+ if (!includePrivate) {
+ fprintf(fp, "%s/** @doconly */\n", indentStr);
+ }
+ fprintf(fp, "%spublic static final class styleable {\n", indentStr);
+ indent++;
+
+ String16 attr16("attr");
+ String16 package16(assets->getPackage());
+
+ indentStr = getIndentSpace(indent);
+ bool hasErrors = false;
+
+ size_t i;
+ size_t N = symbols->getNestedSymbols().size();
+ for (i=0; i<N; i++) {
+ sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+ String16 nclassName16(symbols->getNestedSymbols().keyAt(i));
+ String8 realClassName(nclassName16);
+ if (fixupSymbol(&nclassName16) != NO_ERROR) {
+ hasErrors = true;
+ }
+ String8 nclassName(nclassName16);
+
+ SortedVector<uint32_t> idents;
+ Vector<uint32_t> origOrder;
+ Vector<bool> publicFlags;
+
+ size_t a;
+ size_t NA = nsymbols->getSymbols().size();
+ for (a=0; a<NA; a++) {
+ const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
+ int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
+ ? sym.int32Val : 0;
+ bool isPublic = true;
+ if (code == 0) {
+ String16 name16(sym.name);
+ uint32_t typeSpecFlags;
+ code = assets->getIncludedResources().identifierForName(
+ name16.string(), name16.size(),
+ attr16.string(), attr16.size(),
+ package16.string(), package16.size(), &typeSpecFlags);
+ if (code == 0) {
+ fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
+ nclassName.string(), sym.name.string());
+ hasErrors = true;
+ }
+ isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ }
+ idents.add(code);
+ origOrder.add(code);
+ publicFlags.add(isPublic);
+ }
+
+ NA = idents.size();
+
+ String16 comment = symbols->getComment(realClassName);
+ fprintf(fp, "%s/** ", indentStr);
+ if (comment.size() > 0) {
+ fprintf(fp, "%s\n", String8(comment).string());
+ } else {
+ fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string());
+ }
+ bool hasTable = false;
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ if (!hasTable) {
+ hasTable = true;
+ fprintf(fp,
+ "%s <p>Includes the following attributes:</p>\n"
+ "%s <table border=\"2\" width=\"85%%\" align=\"center\" frame=\"hsides\" rules=\"all\" cellpadding=\"5\">\n"
+ "%s <colgroup align=\"left\" />\n"
+ "%s <colgroup align=\"left\" />\n"
+ "%s <tr><th>Attribute<th>Summary</tr>\n",
+ indentStr,
+ indentStr,
+ indentStr,
+ indentStr,
+ indentStr);
+ }
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ if (comment.size() <= 0) {
+ comment = getAttributeComment(assets, name8);
+ }
+ if (comment.size() > 0) {
+ const char16_t* p = comment.string();
+ while (*p != 0 && *p != '.') {
+ if (*p == '{') {
+ while (*p != 0 && *p != '}') {
+ p++;
+ }
+ } else {
+ p++;
+ }
+ }
+ if (*p == '.') {
+ p++;
+ }
+ comment = String16(comment.string(), p-comment.string());
+ }
+ String16 name(name8);
+ fixupSymbol(&name);
+ fprintf(fp, "%s <tr><th><code>{@link #%s_%s %s:%s}</code><td>%s</tr>\n",
+ indentStr, nclassName.string(),
+ String8(name).string(),
+ assets->getPackage().string(),
+ String8(name).string(),
+ String8(comment).string());
+ }
+ }
+ if (hasTable) {
+ fprintf(fp, "%s </table>\n", indentStr);
+ }
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ String16 name(sym.name);
+ fixupSymbol(&name);
+ fprintf(fp, "%s @see #%s_%s\n",
+ indentStr, nclassName.string(),
+ String8(name).string());
+ }
+ }
+ fprintf(fp, "%s */\n", getIndentSpace(indent));
+
+ fprintf(fp,
+ "%spublic static final int[] %s = {\n"
+ "%s",
+ indentStr, nclassName.string(),
+ getIndentSpace(indent+1));
+
+ for (a=0; a<NA; a++) {
+ if (a != 0) {
+ if ((a&3) == 0) {
+ fprintf(fp, ",\n%s", getIndentSpace(indent+1));
+ } else {
+ fprintf(fp, ", ");
+ }
+ }
+ fprintf(fp, "0x%08x", idents[a]);
+ }
+
+ fprintf(fp, "\n%s};\n", indentStr);
+
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ String16 typeComment;
+ if (comment.size() <= 0) {
+ comment = getAttributeComment(assets, name8, &typeComment);
+ } else {
+ getAttributeComment(assets, name8, &typeComment);
+ }
+ String16 name(name8);
+ if (fixupSymbol(&name) != NO_ERROR) {
+ hasErrors = true;
+ }
+
+ uint32_t typeSpecFlags = 0;
+ String16 name16(sym.name);
+ assets->getIncludedResources().identifierForName(
+ name16.string(), name16.size(),
+ attr16.string(), attr16.size(),
+ package16.string(), package16.size(), &typeSpecFlags);
+ //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
+ // String8(attr16).string(), String8(name16).string(), typeSpecFlags);
+ const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+
+ fprintf(fp, "%s/**\n", indentStr);
+ if (comment.size() > 0) {
+ fprintf(fp, "%s <p>\n%s @attr description\n", indentStr, indentStr);
+ fprintf(fp, "%s %s\n", indentStr, String8(comment).string());
+ } else {
+ fprintf(fp,
+ "%s <p>This symbol is the offset where the {@link %s.R.attr#%s}\n"
+ "%s attribute's value can be found in the {@link #%s} array.\n",
+ indentStr,
+ pub ? assets->getPackage().string()
+ : assets->getSymbolsPrivatePackage().string(),
+ String8(name).string(),
+ indentStr, nclassName.string());
+ }
+ if (typeComment.size() > 0) {
+ fprintf(fp, "\n\n%s %s\n", indentStr, String8(typeComment).string());
+ }
+ if (comment.size() > 0) {
+ if (pub) {
+ fprintf(fp,
+ "%s <p>This corresponds to the global attribute"
+ "%s resource symbol {@link %s.R.attr#%s}.\n",
+ indentStr, indentStr,
+ assets->getPackage().string(),
+ String8(name).string());
+ } else {
+ fprintf(fp,
+ "%s <p>This is a private symbol.\n", indentStr);
+ }
+ }
+ fprintf(fp, "%s @attr name %s:%s\n", indentStr,
+ "android", String8(name).string());
+ fprintf(fp, "%s*/\n", indentStr);
+ fprintf(fp,
+ "%spublic static final int %s_%s = %d;\n",
+ indentStr, nclassName.string(),
+ String8(name).string(), (int)pos);
+ }
+ }
+ }
+
+ indent--;
+ fprintf(fp, "%s};\n", getIndentSpace(indent));
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static status_t writeSymbolClass(
+ FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
+ const sp<AaptSymbols>& symbols, const String8& className, int indent)
+{
+ fprintf(fp, "%spublic %sfinal class %s {\n",
+ getIndentSpace(indent),
+ indent != 0 ? "static " : "", className.string());
+ indent++;
+
+ size_t i;
+ status_t err = NO_ERROR;
+
+ size_t N = symbols->getSymbols().size();
+ for (i=0; i<N; i++) {
+ const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+ if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
+ continue;
+ }
+ if (!includePrivate && !sym.isPublic) {
+ continue;
+ }
+ String16 name(sym.name);
+ String8 realName(name);
+ if (fixupSymbol(&name) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ String16 comment(sym.comment);
+ bool haveComment = false;
+ if (comment.size() > 0) {
+ haveComment = true;
+ fprintf(fp,
+ "%s/** %s\n",
+ getIndentSpace(indent), String8(comment).string());
+ } else if (sym.isPublic && !includePrivate) {
+ sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
+ assets->getPackage().string(), className.string(),
+ String8(sym.name).string());
+ }
+ String16 typeComment(sym.typeComment);
+ if (typeComment.size() > 0) {
+ if (!haveComment) {
+ haveComment = true;
+ fprintf(fp,
+ "%s/** %s\n",
+ getIndentSpace(indent), String8(typeComment).string());
+ } else {
+ fprintf(fp,
+ "%s %s\n",
+ getIndentSpace(indent), String8(typeComment).string());
+ }
+ }
+ if (haveComment) {
+ fprintf(fp,"%s */\n", getIndentSpace(indent));
+ }
+ fprintf(fp, "%spublic static final int %s=0x%08x;\n",
+ getIndentSpace(indent),
+ String8(name).string(), (int)sym.int32Val);
+ }
+
+ for (i=0; i<N; i++) {
+ const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+ if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) {
+ continue;
+ }
+ if (!includePrivate && !sym.isPublic) {
+ continue;
+ }
+ String16 name(sym.name);
+ if (fixupSymbol(&name) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ String16 comment(sym.comment);
+ if (comment.size() > 0) {
+ fprintf(fp,
+ "%s/** %s\n"
+ "%s */\n",
+ getIndentSpace(indent), String8(comment).string(),
+ getIndentSpace(indent));
+ } else if (sym.isPublic && !includePrivate) {
+ sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
+ assets->getPackage().string(), className.string(),
+ String8(sym.name).string());
+ }
+ fprintf(fp, "%spublic static final String %s=\"%s\";\n",
+ getIndentSpace(indent),
+ String8(name).string(), sym.stringVal.string());
+ }
+
+ sp<AaptSymbols> styleableSymbols;
+
+ N = symbols->getNestedSymbols().size();
+ for (i=0; i<N; i++) {
+ sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+ String8 nclassName(symbols->getNestedSymbols().keyAt(i));
+ if (nclassName == "styleable") {
+ styleableSymbols = nsymbols;
+ } else {
+ err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent);
+ }
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ if (styleableSymbols != NULL) {
+ err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ indent--;
+ fprintf(fp, "%s}\n", getIndentSpace(indent));
+ return NO_ERROR;
+}
+
+status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
+ const String8& package, bool includePrivate)
+{
+ if (!bundle->getRClassDir()) {
+ return NO_ERROR;
+ }
+
+ const size_t N = assets->getSymbols().size();
+ for (size_t i=0; i<N; i++) {
+ sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i);
+ String8 className(assets->getSymbols().keyAt(i));
+ String8 dest(bundle->getRClassDir());
+ if (bundle->getMakePackageDirs()) {
+ String8 pkg(package);
+ const char* last = pkg.string();
+ const char* s = last-1;
+ do {
+ s++;
+ if (s > last && (*s == '.' || *s == 0)) {
+ String8 part(last, s-last);
+ dest.appendPath(part);
+#ifdef HAVE_MS_C_RUNTIME
+ _mkdir(dest.string());
+#else
+ mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+ last = s+1;
+ }
+ } while (*s);
+ }
+ dest.appendPath(className);
+ dest.append(".java");
+ FILE* fp = fopen(dest.string(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+ dest.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+ if (bundle->getVerbose()) {
+ printf(" Writing symbols for class %s.\n", className.string());
+ }
+
+ fprintf(fp,
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ " *\n"
+ " * This class was automatically generated by the\n"
+ " * aapt tool from the resource data it found. It\n"
+ " * should not be modified by hand.\n"
+ " */\n"
+ "\n"
+ "package %s;\n\n", package.string());
+
+ status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, className, 0);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ fclose(fp);
+ }
+
+ return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
new file mode 100644
index 0000000..6f71a1e
--- /dev/null
+++ b/tools/aapt/ResourceTable.cpp
@@ -0,0 +1,3491 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "ResourceTable.h"
+
+#include "XMLNode.h"
+
+#include <utils/ByteOrder.h>
+#include <utils/ResourceTypes.h>
+#include <stdarg.h>
+
+#define NOISY(x) //x
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<AaptFile>& target,
+ ResourceTable* table,
+ int options)
+{
+ sp<XMLNode> root = XMLNode::parse(target);
+ if (root == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
+ root->removeWhitespace(true, NULL);
+ } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
+ root->removeWhitespace(false, NULL);
+ }
+
+ bool hasErrors = false;
+
+ if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
+ status_t err = root->assignResourceIds(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ status_t err = root->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+
+ if (hasErrors) {
+ return UNKNOWN_ERROR;
+ }
+
+ NOISY(printf("Input XML Resource:\n"));
+ NOISY(root->print());
+ err = root->flatten(target,
+ (options&XML_COMPILE_STRIP_COMMENTS) != 0,
+ (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Output XML Resource:\n"));
+ NOISY(ResXMLTree tree;
+ tree.setTo(target->getData(), target->getSize());
+ printXMLBlock(&tree));
+
+ target->setCompressionMethod(ZipEntry::kCompressDeflated);
+
+ return err;
+}
+
+#undef NOISY
+#define NOISY(x) //x
+
+struct flag_entry
+{
+ const char16_t* name;
+ size_t nameLen;
+ uint32_t value;
+ const char* description;
+};
+
+static const char16_t referenceArray[] =
+ { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' };
+static const char16_t stringArray[] =
+ { 's', 't', 'r', 'i', 'n', 'g' };
+static const char16_t integerArray[] =
+ { 'i', 'n', 't', 'e', 'g', 'e', 'r' };
+static const char16_t booleanArray[] =
+ { 'b', 'o', 'o', 'l', 'e', 'a', 'n' };
+static const char16_t colorArray[] =
+ { 'c', 'o', 'l', 'o', 'r' };
+static const char16_t floatArray[] =
+ { 'f', 'l', 'o', 'a', 't' };
+static const char16_t dimensionArray[] =
+ { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' };
+static const char16_t fractionArray[] =
+ { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' };
+static const char16_t enumArray[] =
+ { 'e', 'n', 'u', 'm' };
+static const char16_t flagsArray[] =
+ { 'f', 'l', 'a', 'g', 's' };
+
+static const flag_entry gFormatFlags[] = {
+ { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
+ "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
+ "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
+ { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
+ "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
+ { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
+ "an integer value, such as \"<code>100</code>\"." },
+ { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
+ "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
+ { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
+ "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
+ "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
+ { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
+ "a floating point value, such as \"<code>1.2</code>\"."},
+ { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
+ "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
+ "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
+ "in (inches), mm (millimeters)." },
+ { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
+ "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
+ "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
+ "some parent container." },
+ { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
+ { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
+ { NULL, 0, 0, NULL }
+};
+
+static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' };
+
+static const flag_entry l10nRequiredFlags[] = {
+ { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL },
+ { NULL, 0, 0, NULL }
+};
+
+static const char16_t nulStr[] = { 0 };
+
+static uint32_t parse_flags(const char16_t* str, size_t len,
+ const flag_entry* flags, bool* outError = NULL)
+{
+ while (len > 0 && isspace(*str)) {
+ str++;
+ len--;
+ }
+ while (len > 0 && isspace(str[len-1])) {
+ len--;
+ }
+
+ const char16_t* const end = str + len;
+ uint32_t value = 0;
+
+ while (str < end) {
+ const char16_t* div = str;
+ while (div < end && *div != '|') {
+ div++;
+ }
+
+ const flag_entry* cur = flags;
+ while (cur->name) {
+ if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
+ value |= cur->value;
+ break;
+ }
+ cur++;
+ }
+
+ if (!cur->name) {
+ if (outError) *outError = true;
+ return 0;
+ }
+
+ str = div < end ? div+1 : div;
+ }
+
+ if (outError) *outError = false;
+ return value;
+}
+
+static String16 mayOrMust(int type, int flags)
+{
+ if ((type&(~flags)) == 0) {
+ return String16("<p>Must");
+ }
+
+ return String16("<p>May");
+}
+
+static void appendTypeInfo(ResourceTable* outTable, const String16& pkg,
+ const String16& typeName, const String16& ident, int type,
+ const flag_entry* flags)
+{
+ bool hadType = false;
+ while (flags->name) {
+ if ((type&flags->value) != 0 && flags->description != NULL) {
+ String16 fullMsg(mayOrMust(type, flags->value));
+ fullMsg.append(String16(" be "));
+ fullMsg.append(String16(flags->description));
+ outTable->appendTypeComment(pkg, typeName, ident, fullMsg);
+ hadType = true;
+ }
+ flags++;
+ }
+ if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) {
+ outTable->appendTypeComment(pkg, typeName, ident,
+ String16("<p>This may also be a reference to a resource (in the form\n"
+ "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
+ "theme attribute (in the form\n"
+ "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
+ "containing a value of this type."));
+ }
+}
+
+struct PendingAttribute
+{
+ const String16 myPackage;
+ const SourcePos sourcePos;
+ const bool appendComment;
+ int32_t type;
+ String16 ident;
+ String16 comment;
+ bool hasErrors;
+ bool added;
+
+ PendingAttribute(String16 _package, const sp<AaptFile>& in,
+ ResXMLTree& block, bool _appendComment)
+ : myPackage(_package)
+ , sourcePos(in->getPrintableSource(), block.getLineNumber())
+ , appendComment(_appendComment)
+ , type(ResTable_map::TYPE_ANY)
+ , hasErrors(false)
+ , added(false)
+ {
+ }
+
+ status_t createIfNeeded(ResourceTable* outTable)
+ {
+ if (added || hasErrors) {
+ return NO_ERROR;
+ }
+ added = true;
+
+ String16 attr16("attr");
+
+ if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
+ sourcePos.error("Attribute \"%s\" has already been defined\n",
+ String8(ident).string());
+ hasErrors = true;
+ return UNKNOWN_ERROR;
+ }
+
+ char numberStr[16];
+ sprintf(numberStr, "%d", type);
+ status_t err = outTable->addBag(sourcePos, myPackage,
+ attr16, ident, String16(""),
+ String16("^type"),
+ String16(numberStr), NULL, NULL);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ return err;
+ }
+ outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
+ //printf("Attribute %s comment: %s\n", String8(ident).string(),
+ // String8(comment).string());
+ return err;
+ }
+};
+
+static status_t compileAttribute(const sp<AaptFile>& in,
+ ResXMLTree& block,
+ const String16& myPackage,
+ ResourceTable* outTable,
+ String16* outIdent = NULL,
+ bool inStyleable = false)
+{
+ PendingAttribute attr(myPackage, in, block, inStyleable);
+
+ const String16 attr16("attr");
+ const String16 id16("id");
+
+ // Attribute type constants.
+ const String16 enum16("enum");
+ const String16 flag16("flag");
+
+ ResXMLTree::event_code_t code;
+ size_t len;
+ status_t err;
+
+ ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+ if (identIdx >= 0) {
+ attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
+ if (outIdent) {
+ *outIdent = attr.ident;
+ }
+ } else {
+ attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
+ attr.hasErrors = true;
+ }
+
+ attr.comment = String16(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
+ if (typeIdx >= 0) {
+ String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len));
+ attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);
+ if (attr.type == 0) {
+ attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
+ String8(typeStr).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ } else if (!inStyleable) {
+ // Attribute definitions outside of styleables always define the
+ // attribute as a generic value.
+ attr.createIfNeeded(outTable);
+ }
+
+ //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
+
+ ssize_t minIdx = block.indexOfAttribute(NULL, "min");
+ if (minIdx >= 0) {
+ String16 val = String16(block.getAttributeStringValue(minIdx, &len));
+ if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
+ attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
+ String8(val).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ if (!attr.hasErrors) {
+ err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+ String16(""), String16("^min"), String16(val), NULL, NULL);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ }
+
+ ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
+ if (maxIdx >= 0) {
+ String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
+ if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
+ attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
+ String8(val).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ if (!attr.hasErrors) {
+ err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+ String16(""), String16("^max"), String16(val), NULL, NULL);
+ attr.hasErrors = true;
+ }
+ }
+
+ if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
+ attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
+ attr.hasErrors = true;
+ }
+
+ ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
+ if (l10nIdx >= 0) {
+ const uint16_t* str = block.getAttributeStringValue(l10nIdx, &len);
+ bool error;
+ uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
+ if (error) {
+ attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
+ String8(str).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ if (!attr.hasErrors) {
+ char buf[10];
+ sprintf(buf, "%d", l10n_required);
+ err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+ String16(""), String16("^l10n"), String16(buf), NULL, NULL);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ }
+
+ String16 enumOrFlagsComment;
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ uint32_t localType = 0;
+ if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
+ localType = ResTable_map::TYPE_ENUM;
+ } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
+ localType = ResTable_map::TYPE_FLAGS;
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ attr.createIfNeeded(outTable);
+
+ if (attr.type == ResTable_map::TYPE_ANY) {
+ // No type was explicitly stated, so supplying enum tags
+ // implicitly creates an enum or flag.
+ attr.type = 0;
+ }
+
+ if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
+ // Wasn't originally specified as an enum, so update its type.
+ attr.type |= localType;
+ if (!attr.hasErrors) {
+ char numberStr[16];
+ sprintf(numberStr, "%d", attr.type);
+ err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage, attr16, attr.ident, String16(""),
+ String16("^type"), String16(numberStr), NULL, NULL, true);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
+ if (localType == ResTable_map::TYPE_ENUM) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("<enum> attribute can not be used inside a flags format\n");
+ attr.hasErrors = true;
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("<flag> attribute can not be used inside a enum format\n");
+ attr.hasErrors = true;
+ }
+ }
+
+ String16 itemIdent;
+ ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
+ if (itemIdentIdx >= 0) {
+ itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("A 'name' attribute is required for <enum> or <flag>\n");
+ attr.hasErrors = true;
+ }
+
+ String16 value;
+ ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
+ if (valueIdx >= 0) {
+ value = String16(block.getAttributeStringValue(valueIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("A 'value' attribute is required for <enum> or <flag>\n");
+ attr.hasErrors = true;
+ }
+ if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Tag <enum> or <flag> 'value' attribute must be a number,"
+ " not \"%s\"\n",
+ String8(value).string());
+ attr.hasErrors = true;
+ }
+
+ // Make sure an id is defined for this enum/flag identifier...
+ if (!attr.hasErrors && !outTable->hasBagOrEntry(itemIdent, &id16, &myPackage)) {
+ err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage, id16, itemIdent, String16(), NULL);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+
+ if (!attr.hasErrors) {
+ if (enumOrFlagsComment.size() == 0) {
+ enumOrFlagsComment.append(mayOrMust(attr.type,
+ ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
+ enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
+ ? String16(" be one of the following constant values.")
+ : String16(" be one or more (separated by '|') of the following constant values."));
+ enumOrFlagsComment.append(String16("</p>\n<table border=\"2\" width=\"85%\" align=\"center\" frame=\"hsides\" rules=\"all\" cellpadding=\"5\">\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Constant<th>Value<th>Description</tr>"));
+ }
+
+ enumOrFlagsComment.append(String16("\n<tr><th><code>"));
+ enumOrFlagsComment.append(itemIdent);
+ enumOrFlagsComment.append(String16("</code><td>"));
+ enumOrFlagsComment.append(value);
+ enumOrFlagsComment.append(String16("<td>"));
+ if (block.getComment(&len)) {
+ enumOrFlagsComment.append(String16(block.getComment(&len)));
+ }
+ enumOrFlagsComment.append(String16("</tr>"));
+
+ err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage,
+ attr16, attr.ident, String16(""),
+ itemIdent, value, NULL, NULL, false, true);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
+ break;
+ }
+ if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
+ if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Found tag </%s> where </enum> is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Found tag </%s> where </flag> is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+ }
+
+ if (!attr.hasErrors && attr.added) {
+ appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
+ }
+
+ if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
+ enumOrFlagsComment.append(String16("\n</table>"));
+ outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
+ }
+
+
+ return NO_ERROR;
+}
+
+bool localeIsDefined(const ResTable_config& config)
+{
+ return config.locale == 0;
+}
+
+status_t parseAndAddBag(Bundle* bundle,
+ const sp<AaptFile>& in,
+ ResXMLTree* block,
+ const ResTable_config& config,
+ const String16& myPackage,
+ const String16& curType,
+ const String16& ident,
+ const String16& parentIdent,
+ const String16& itemIdent,
+ int32_t curFormat,
+ bool pseudolocalize,
+ const bool overwrite,
+ ResourceTable* outTable)
+{
+ status_t err;
+ const String16 item16("item");
+
+ String16 str;
+ Vector<StringPool::entry_style_span> spans;
+ err = parseStyledString(bundle, in->getPrintableSource().string(),
+ block, item16, &str, &spans,
+ pseudolocalize);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d "
+ " pid=%s, bag=%s, id=%s: %s\n",
+ config.language[0], config.language[1],
+ config.country[0], config.country[1],
+ config.orientation, config.density,
+ String8(parentIdent).string(),
+ String8(ident).string(),
+ String8(itemIdent).string(),
+ String8(str).string()));
+
+ err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
+ myPackage, curType, ident, parentIdent, itemIdent, str,
+ &spans, &config, overwrite, false, curFormat);
+ return err;
+}
+
+
+status_t parseAndAddEntry(Bundle* bundle,
+ const sp<AaptFile>& in,
+ ResXMLTree* block,
+ const ResTable_config& config,
+ const String16& myPackage,
+ const String16& curType,
+ const String16& ident,
+ const String16& curTag,
+ bool curIsStyled,
+ int32_t curFormat,
+ bool pseudolocalize,
+ const bool overwrite,
+ ResourceTable* outTable)
+{
+ status_t err;
+
+ String16 str;
+ Vector<StringPool::entry_style_span> spans;
+ err = parseStyledString(bundle, in->getPrintableSource().string(), block,
+ curTag, &str, curIsStyled ? &spans : NULL,
+ pseudolocalize);
+
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n",
+ config.language[0], config.language[1],
+ config.country[0], config.country[1],
+ config.orientation, config.density,
+ String8(ident).string(), String8(str).string()));
+
+ err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
+ myPackage, curType, ident, str, &spans, &config,
+ false, curFormat, overwrite);
+
+ return err;
+}
+
+status_t compileResourceFile(Bundle* bundle,
+ const sp<AaptAssets>& assets,
+ const sp<AaptFile>& in,
+ const ResTable_config& defParams,
+ const bool overwrite,
+ ResourceTable* outTable)
+{
+ ResXMLTree block;
+ status_t err = parseXMLResource(in, &block, false, true);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // Top-level tag.
+ const String16 resources16("resources");
+
+ // Identifier declaration tags.
+ const String16 declare_styleable16("declare-styleable");
+ const String16 attr16("attr");
+
+ // Data creation organizational tags.
+ const String16 string16("string");
+ const String16 drawable16("drawable");
+ const String16 color16("color");
+ const String16 bool16("bool");
+ const String16 integer16("integer");
+ const String16 dimen16("dimen");
+ const String16 fraction16("fraction");
+ const String16 style16("style");
+ const String16 plurals16("plurals");
+ const String16 array16("array");
+ const String16 string_array16("string-array");
+ const String16 integer_array16("integer-array");
+ const String16 public16("public");
+ const String16 private_symbols16("private-symbols");
+ const String16 skip16("skip");
+ const String16 eat_comment16("eat-comment");
+
+ // Data creation tags.
+ const String16 bag16("bag");
+ const String16 item16("item");
+
+ // Attribute type constants.
+ const String16 enum16("enum");
+
+ // plural values
+ const String16 other16("other");
+ const String16 quantityOther16("^other");
+ const String16 zero16("zero");
+ const String16 quantityZero16("^zero");
+ const String16 one16("one");
+ const String16 quantityOne16("^one");
+ const String16 two16("two");
+ const String16 quantityTwo16("^two");
+ const String16 few16("few");
+ const String16 quantityFew16("^few");
+ const String16 many16("many");
+ const String16 quantityMany16("^many");
+
+ // useful attribute names and special values
+ const String16 name16("name");
+ const String16 translatable16("translatable");
+ const String16 false16("false");
+
+ const String16 myPackage(assets->getPackage());
+
+ bool hasErrors = false;
+
+ uint32_t nextPublicId = 0;
+
+ ResXMLTree::event_code_t code;
+ do {
+ code = block.next();
+ } while (code == ResXMLTree::START_NAMESPACE);
+
+ size_t len;
+ if (code != ResXMLTree::START_TAG) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "No start tag found\n");
+ return UNKNOWN_ERROR;
+ }
+ if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Invalid start tag %s\n", String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ ResTable_config curParams(defParams);
+
+ ResTable_config pseudoParams(curParams);
+ pseudoParams.language[0] = 'z';
+ pseudoParams.language[1] = 'z';
+ pseudoParams.country[0] = 'Z';
+ pseudoParams.country[1] = 'Z';
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ const String16* curTag = NULL;
+ String16 curType;
+ int32_t curFormat = ResTable_map::TYPE_ANY;
+ bool curIsBag = false;
+ bool curIsStyled = false;
+ bool curIsPseudolocalizable = false;
+ bool localHasErrors = false;
+
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 type;
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+ if (typeIdx < 0) {
+ srcPos.error("A 'type' attribute is required for <public>\n");
+ hasErrors = localHasErrors = true;
+ }
+ type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+ String16 name;
+ ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+ if (nameIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <public>\n");
+ hasErrors = localHasErrors = true;
+ }
+ name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+ uint32_t ident = 0;
+ ssize_t identIdx = block.indexOfAttribute(NULL, "id");
+ if (identIdx >= 0) {
+ const char16_t* identStr = block.getAttributeStringValue(identIdx, &len);
+ Res_value identValue;
+ if (!ResTable::stringToInt(identStr, len, &identValue)) {
+ srcPos.error("Given 'id' attribute is not an integer: %s\n",
+ String8(block.getAttributeStringValue(identIdx, &len)).string());
+ hasErrors = localHasErrors = true;
+ } else {
+ ident = identValue.data;
+ nextPublicId = ident+1;
+ }
+ } else if (nextPublicId == 0) {
+ srcPos.error("No 'id' attribute supplied <public>,"
+ " and no previous id defined in this file.\n");
+ hasErrors = localHasErrors = true;
+ } else if (!localHasErrors) {
+ ident = nextPublicId;
+ nextPublicId++;
+ }
+
+ if (!localHasErrors) {
+ err = outTable->addPublic(srcPos, myPackage, type, name, ident);
+ if (err < NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ }
+ if (!localHasErrors) {
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8(type), srcPos);
+ }
+ if (symbols != NULL) {
+ symbols->makeSymbolPublic(String8(name), srcPos);
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ symbols->appendComment(String8(name), comment, srcPos);
+ } else {
+ srcPos.error("Unable to create symbols!\n");
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
+ String16 pkg;
+ ssize_t pkgIdx = block.indexOfAttribute(NULL, "package");
+ if (pkgIdx < 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'package' attribute is required for <private-symbols>\n");
+ hasErrors = localHasErrors = true;
+ }
+ pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
+ if (!localHasErrors) {
+ assets->setSymbolsPrivatePackage(String8(pkg));
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 ident;
+ ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+ if (identIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <declare-styleable>\n");
+ hasErrors = localHasErrors = true;
+ }
+ ident = String16(block.getAttributeStringValue(identIdx, &len));
+
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ if (!localHasErrors) {
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8("styleable"), srcPos);
+ }
+ sp<AaptSymbols> styleSymbols = symbols;
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8(ident), srcPos);
+ }
+ if (symbols == NULL) {
+ srcPos.error("Unable to create symbols!\n");
+ return UNKNOWN_ERROR;
+ }
+
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ styleSymbols->appendComment(String8(ident), comment, srcPos);
+ } else {
+ symbols = NULL;
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+ } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+ } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ String16 itemIdent;
+ err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true);
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+
+ if (symbols != NULL) {
+ SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber());
+ symbols->addSymbol(String8(itemIdent), 0, srcPos);
+ symbols->appendComment(String8(itemIdent), comment, srcPos);
+ //printf("Attribute %s comment: %s\n", String8(itemIdent).string(),
+ // String8(comment).string());
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
+ break;
+ }
+
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found tag </%s> where </attr> is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
+ err = compileAttribute(in, block, myPackage, outTable, NULL);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) {
+ curTag = &item16;
+ ssize_t attri = block.indexOfAttribute(NULL, "type");
+ if (attri >= 0) {
+ curType = String16(block.getAttributeStringValue(attri, &len));
+ ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
+ if (formatIdx >= 0) {
+ String16 formatStr = String16(block.getAttributeStringValue(
+ formatIdx, &len));
+ curFormat = parse_flags(formatStr.string(), formatStr.size(),
+ gFormatFlags);
+ if (curFormat == 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <item> 'format' attribute value \"%s\" not valid\n",
+ String8(formatStr).string());
+ hasErrors = localHasErrors = true;
+ }
+ }
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'type' attribute is required for <item>\n");
+ hasErrors = localHasErrors = true;
+ }
+ curIsStyled = true;
+ } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
+ // Note the existence and locale of every string we process
+ char rawLocale[16];
+ curParams.getLocale(rawLocale);
+ String8 locale(rawLocale);
+ String16 name;
+ String16 translatable;
+
+ size_t n = block.getAttributeCount();
+ for (size_t i = 0; i < n; i++) {
+ size_t length;
+ const uint16_t* attr = block.getAttributeName(i, &length);
+ if (strcmp16(attr, name16.string()) == 0) {
+ name.setTo(block.getAttributeStringValue(i, &length));
+ } else if (strcmp16(attr, translatable16.string()) == 0) {
+ translatable.setTo(block.getAttributeStringValue(i, &length));
+ }
+ }
+
+ if (name.size() > 0) {
+ if (translatable == false16) {
+ // Untranslatable strings must only exist in the default [empty] locale
+ if (locale.size() > 0) {
+ fprintf(stderr, "aapt: warning: string '%s' in %s marked untranslatable but exists"
+ " in locale '%s'\n", String8(name).string(),
+ bundle->getResourceSourceDirs()[0],
+ locale.string());
+ // hasErrors = localHasErrors = true;
+ } else {
+ // Intentionally empty block:
+ //
+ // Don't add untranslatable strings to the localization table; that
+ // way if we later see localizations of them, they'll be flagged as
+ // having no default translation.
+ }
+ } else {
+ outTable->addLocalization(name, locale);
+ }
+ }
+
+ curTag = &string16;
+ curType = string16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
+ curIsStyled = true;
+ curIsPseudolocalizable = true;
+ } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) {
+ curTag = &drawable16;
+ curType = drawable16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+ } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) {
+ curTag = &color16;
+ curType = color16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+ } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
+ curTag = &bool16;
+ curType = bool16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
+ } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
+ curTag = &integer16;
+ curType = integer16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
+ } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) {
+ curTag = &dimen16;
+ curType = dimen16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_DIMENSION;
+ } else if (strcmp16(block.getElementName(&len), fraction16.string()) == 0) {
+ curTag = &fraction16;
+ curType = fraction16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_FRACTION;
+ } else if (strcmp16(block.getElementName(&len), bag16.string()) == 0) {
+ curTag = &bag16;
+ curIsBag = true;
+ ssize_t attri = block.indexOfAttribute(NULL, "type");
+ if (attri >= 0) {
+ curType = String16(block.getAttributeStringValue(attri, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'type' attribute is required for <bag>\n");
+ hasErrors = localHasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) {
+ curTag = &style16;
+ curType = style16;
+ curIsBag = true;
+ } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) {
+ curTag = &plurals16;
+ curType = plurals16;
+ curIsBag = true;
+ } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
+ curTag = &array16;
+ curType = array16;
+ curIsBag = true;
+ ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
+ if (formatIdx >= 0) {
+ String16 formatStr = String16(block.getAttributeStringValue(
+ formatIdx, &len));
+ curFormat = parse_flags(formatStr.string(), formatStr.size(),
+ gFormatFlags);
+ if (curFormat == 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <array> 'format' attribute value \"%s\" not valid\n",
+ String8(formatStr).string());
+ hasErrors = localHasErrors = true;
+ }
+ }
+ } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
+ curTag = &string_array16;
+ curType = array16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
+ curIsBag = true;
+ curIsPseudolocalizable = true;
+ } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
+ curTag = &integer_array16;
+ curType = array16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
+ curIsBag = true;
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found tag %s where item is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ String16 ident;
+ ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+ if (identIdx >= 0) {
+ ident = String16(block.getAttributeStringValue(identIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'name' attribute is required for <%s>\n",
+ String8(*curTag).string());
+ hasErrors = localHasErrors = true;
+ }
+
+ String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
+
+ if (curIsBag) {
+ // Figure out the parent of this bag...
+ String16 parentIdent;
+ ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent");
+ if (parentIdentIdx >= 0) {
+ parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len));
+ } else {
+ ssize_t sep = ident.findLast('.');
+ if (sep >= 0) {
+ parentIdent.setTo(ident, sep);
+ }
+ }
+
+ if (!localHasErrors) {
+ err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage, curType, ident, parentIdent, &curParams);
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ ssize_t elmIndex = 0;
+ char elmIndexStr[14];
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+
+ if (code == ResXMLTree::START_TAG) {
+ if (strcmp16(block.getElementName(&len), item16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <%s> can not appear inside <%s>, only <item>\n",
+ String8(block.getElementName(&len)).string(),
+ String8(*curTag).string());
+ return UNKNOWN_ERROR;
+ }
+
+ String16 itemIdent;
+ if (curType == array16) {
+ sprintf(elmIndexStr, "^index_%d", (int)elmIndex++);
+ itemIdent = String16(elmIndexStr);
+ } else if (curType == plurals16) {
+ ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity");
+ if (itemIdentIdx >= 0) {
+ String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len));
+ if (quantity16 == other16) {
+ itemIdent = quantityOther16;
+ }
+ else if (quantity16 == zero16) {
+ itemIdent = quantityZero16;
+ }
+ else if (quantity16 == one16) {
+ itemIdent = quantityOne16;
+ }
+ else if (quantity16 == two16) {
+ itemIdent = quantityTwo16;
+ }
+ else if (quantity16 == few16) {
+ itemIdent = quantityFew16;
+ }
+ else if (quantity16 == many16) {
+ itemIdent = quantityMany16;
+ }
+ else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Illegal 'quantity' attribute is <item> inside <plurals>\n");
+ hasErrors = localHasErrors = true;
+ }
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'quantity' attribute is required for <item> inside <plurals>\n");
+ hasErrors = localHasErrors = true;
+ }
+ } else {
+ ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
+ if (itemIdentIdx >= 0) {
+ itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'name' attribute is required for <item>\n");
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ ResXMLParser::ResXMLPosition parserPosition;
+ block.getPosition(&parserPosition);
+
+ err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
+ ident, parentIdent, itemIdent, curFormat,
+ false, overwrite, outTable);
+ if (err == NO_ERROR) {
+ if (curIsPseudolocalizable && localeIsDefined(curParams)
+ && bundle->getPseudolocalize()) {
+ // pseudolocalize here
+#if 1
+ block.setPosition(parserPosition);
+ err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
+ curType, ident, parentIdent, itemIdent, curFormat, true,
+ overwrite, outTable);
+#endif
+ }
+ }
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), curTag->string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found tag </%s> where </%s> is expected\n",
+ String8(block.getElementName(&len)).string(),
+ String8(*curTag).string());
+ return UNKNOWN_ERROR;
+ }
+ break;
+ }
+ }
+ } else {
+ ResXMLParser::ResXMLPosition parserPosition;
+ block.getPosition(&parserPosition);
+
+ err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
+ *curTag, curIsStyled, curFormat, false, overwrite, outTable);
+
+ if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
+ hasErrors = localHasErrors = true;
+ }
+ else if (err == NO_ERROR) {
+ if (curIsPseudolocalizable && localeIsDefined(curParams)
+ && bundle->getPseudolocalize()) {
+ // pseudolocalize here
+ block.setPosition(parserPosition);
+ err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
+ ident, *curTag, curIsStyled, curFormat, true, false, outTable);
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ }
+ }
+ }
+
+#if 0
+ if (comment.size() > 0) {
+ printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(),
+ String8(curType).string(), String8(ident).string(),
+ String8(comment).string());
+ }
+#endif
+ if (!localHasErrors) {
+ outTable->appendComment(myPackage, curType, ident, comment, false);
+ }
+ }
+ else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Unexpected end tag %s\n", String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) {
+ }
+ else if (code == ResXMLTree::TEXT) {
+ if (isWhitespace(block.getText(&len))) {
+ continue;
+ }
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found text \"%s\" where item tag is expected\n",
+ String8(block.getText(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage)
+ : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false),
+ mIsAppPackage(!bundle->getExtending()),
+ mNumLocal(0),
+ mBundle(bundle)
+{
+}
+
+status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ status_t err = assets->buildIncludedResources(bundle);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // For future reference to included resources.
+ mAssets = assets;
+
+ const ResTable& incl = assets->getIncludedResources();
+
+ // Retrieve all the packages.
+ const size_t N = incl.getBasePackageCount();
+ for (size_t phase=0; phase<2; phase++) {
+ for (size_t i=0; i<N; i++) {
+ String16 name(incl.getBasePackageName(i));
+ uint32_t id = incl.getBasePackageId(i);
+ // First time through: only add base packages (id
+ // is not 0); second time through add the other
+ // packages.
+ if (phase != 0) {
+ if (id != 0) {
+ // Skip base packages -- already one.
+ id = 0;
+ } else {
+ // Assign a dynamic id.
+ id = mNextPackageId;
+ }
+ } else if (id != 0) {
+ if (id == 127) {
+ if (mHaveAppPackage) {
+ fprintf(stderr, "Included resource have two application packages!\n");
+ return UNKNOWN_ERROR;
+ }
+ mHaveAppPackage = true;
+ }
+ if (mNextPackageId > id) {
+ fprintf(stderr, "Included base package ID %d already in use!\n", id);
+ return UNKNOWN_ERROR;
+ }
+ }
+ if (id != 0) {
+ NOISY(printf("Including package %s with ID=%d\n",
+ String8(name).string(), id));
+ sp<Package> p = new Package(name, id);
+ mPackages.add(name, p);
+ mOrderedPackages.add(p);
+
+ if (id >= mNextPackageId) {
+ mNextPackageId = id+1;
+ }
+ }
+ }
+ }
+
+ // Every resource table always has one first entry, the bag attributes.
+ const SourcePos unknown(String8("????"), 0);
+ sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown);
+
+ return NO_ERROR;
+}
+
+status_t ResourceTable::addPublic(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const uint32_t ident)
+{
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ sourcePos.error("Error declaring public resource %s/%s for included package %s\n",
+ String8(type).string(), String8(name).string(),
+ String8(package).string());
+ return UNKNOWN_ERROR;
+ }
+
+ sp<Type> t = getType(package, type, sourcePos);
+ if (t == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ return t->addPublic(sourcePos, name, ident);
+}
+
+status_t ResourceTable::addEntry(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ const ResTable_config* params,
+ const bool doSetIndex,
+ const int32_t format,
+ const bool overwrite)
+{
+ // Check for adding entries in other packages... for now we do
+ // nothing. We need to do the right thing here to support skinning.
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return NO_ERROR;
+ }
+
+#if 0
+ if (name == String16("left")) {
+ printf("Adding entry left: file=%s, line=%d, type=%s, value=%s\n",
+ sourcePos.file.string(), sourcePos.line, String8(type).string(),
+ String8(value).string());
+ }
+#endif
+
+ sp<Entry> e = getEntry(package, type, name, sourcePos, params, doSetIndex);
+ if (e == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ status_t err = e->setItem(sourcePos, value, style, format, overwrite);
+ if (err == NO_ERROR) {
+ mNumLocal++;
+ }
+ return err;
+}
+
+status_t ResourceTable::startBag(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const ResTable_config* params,
+ bool replace, bool isId)
+{
+ // Check for adding entries in other packages... for now we do
+ // nothing. We need to do the right thing here to support skinning.
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return NO_ERROR;
+ }
+
+#if 0
+ if (name == String16("left")) {
+ printf("Adding bag left: file=%s, line=%d, type=%s\n",
+ sourcePos.file.striing(), sourcePos.line, String8(type).string());
+ }
+#endif
+
+ sp<Entry> e = getEntry(package, type, name, sourcePos, params);
+ if (e == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ // If a parent is explicitly specified, set it.
+ if (bagParent.size() > 0) {
+ String16 curPar = e->getParent();
+ if (curPar.size() > 0 && curPar != bagParent) {
+ sourcePos.error("Conflicting parents specified, was '%s', now '%s'\n",
+ String8(e->getParent()).string(),
+ String8(bagParent).string());
+ return UNKNOWN_ERROR;
+ }
+ e->setParent(bagParent);
+ }
+
+ return e->makeItABag(sourcePos);
+}
+
+status_t ResourceTable::addBag(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const String16& bagKey,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ const ResTable_config* params,
+ bool replace, bool isId, const int32_t format)
+{
+ // Check for adding entries in other packages... for now we do
+ // nothing. We need to do the right thing here to support skinning.
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return NO_ERROR;
+ }
+
+#if 0
+ if (name == String16("left")) {
+ printf("Adding bag left: file=%s, line=%d, type=%s\n",
+ sourcePos.file.striing(), sourcePos.line, String8(type).string());
+ }
+#endif
+
+ sp<Entry> e = getEntry(package, type, name, sourcePos, params);
+ if (e == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ // If a parent is explicitly specified, set it.
+ if (bagParent.size() > 0) {
+ String16 curPar = e->getParent();
+ if (curPar.size() > 0 && curPar != bagParent) {
+ sourcePos.error("Conflicting parents specified, was '%s', now '%s'\n",
+ String8(e->getParent()).string(),
+ String8(bagParent).string());
+ return UNKNOWN_ERROR;
+ }
+ e->setParent(bagParent);
+ }
+
+ const bool first = e->getBag().indexOfKey(bagKey) < 0;
+ status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format);
+ if (err == NO_ERROR && first) {
+ mNumLocal++;
+ }
+ return err;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& package,
+ const String16& type,
+ const String16& name) const
+{
+ // First look for this in the included resources...
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) return true;
+ }
+ }
+
+ return false;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& ref,
+ const String16* defType,
+ const String16* defPackage)
+{
+ String16 package, type, name;
+ if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name,
+ defType, defPackage ? defPackage:&mAssetsPackage, NULL)) {
+ return false;
+ }
+ return hasBagOrEntry(package, type, name);
+}
+
+bool ResourceTable::appendComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment,
+ bool onlyIfEmpty)
+{
+ if (comment.size() <= 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) {
+ c->appendComment(comment, onlyIfEmpty);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool ResourceTable::appendTypeComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment)
+{
+ if (comment.size() <= 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) {
+ c->appendTypeComment(comment);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+size_t ResourceTable::size() const {
+ return mPackages.size();
+}
+
+size_t ResourceTable::numLocalResources() const {
+ return mNumLocal;
+}
+
+bool ResourceTable::hasResources() const {
+ return mNumLocal > 0;
+}
+
+sp<AaptFile> ResourceTable::flatten(Bundle* bundle)
+{
+ sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
+ status_t err = flatten(bundle, data);
+ return err == NO_ERROR ? data : NULL;
+}
+
+inline uint32_t ResourceTable::getResId(const sp<Package>& p,
+ const sp<Type>& t,
+ uint32_t nameId)
+{
+ return makeResId(p->getAssignedId(), t->getIndex(), nameId);
+}
+
+uint32_t ResourceTable::getResId(const String16& package,
+ const String16& type,
+ const String16& name,
+ bool onlyPublic) const
+{
+ sp<Package> p = mPackages.valueFor(package);
+ if (p == NULL) return 0;
+
+ // First look for this in the included resources...
+ uint32_t specFlags = 0;
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size(),
+ &specFlags);
+ if (rid != 0) {
+ if (onlyPublic) {
+ if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ return 0;
+ }
+ }
+
+ if (Res_INTERNALID(rid)) {
+ return rid;
+ }
+ return Res_MAKEID(p->getAssignedId()-1,
+ Res_GETTYPE(rid),
+ Res_GETENTRY(rid));
+ }
+
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t == NULL) return 0;
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c == NULL) return 0;
+ int32_t ei = c->getEntryIndex();
+ if (ei < 0) return 0;
+ return getResId(p, t, ei);
+}
+
+uint32_t ResourceTable::getResId(const String16& ref,
+ const String16* defType,
+ const String16* defPackage,
+ const char** outErrorMsg,
+ bool onlyPublic) const
+{
+ String16 package, type, name;
+ if (!ResTable::expandResourceRef(
+ ref.string(), ref.size(), &package, &type, &name,
+ defType, defPackage ? defPackage:&mAssetsPackage,
+ outErrorMsg)) {
+ NOISY(printf("Expanding resource: ref=%s\n",
+ String8(ref).string()));
+ NOISY(printf("Expanding resource: defType=%s\n",
+ defType ? String8(*defType).string() : "NULL"));
+ NOISY(printf("Expanding resource: defPackage=%s\n",
+ defPackage ? String8(*defPackage).string() : "NULL"));
+ NOISY(printf("Expanding resource: ref=%s\n", String8(ref).string()));
+ NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string()));
+ return 0;
+ }
+ uint32_t res = getResId(package, type, name, onlyPublic);
+ NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), res));
+ if (res == 0) {
+ if (outErrorMsg)
+ *outErrorMsg = "No resource found that matches the given name";
+ }
+ return res;
+}
+
+bool ResourceTable::isValidResourceName(const String16& s)
+{
+ const char16_t* p = s.string();
+ bool first = true;
+ while (*p) {
+ if ((*p >= 'a' && *p <= 'z')
+ || (*p >= 'A' && *p <= 'Z')
+ || *p == '_'
+ || (!first && *p >= '0' && *p <= '9')) {
+ first = false;
+ p++;
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
+ const String16& str,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID,
+ const Vector<StringPool::entry_style_span>* style,
+ String16* outStr, void* accessorCookie,
+ uint32_t attrType)
+{
+ String16 finalStr;
+
+ bool res = true;
+ if (style == NULL || style->size() == 0) {
+ // Text is not styled so it can be any type... let's figure it out.
+ res = mAssets->getIncludedResources()
+ .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces,
+ coerceType, attrID, NULL, &mAssetsPackage, this,
+ accessorCookie, attrType);
+ } else {
+ // Styled text can only be a string, and while collecting the style
+ // information we have already processed that string!
+ outValue->size = sizeof(Res_value);
+ outValue->res0 = 0;
+ outValue->dataType = outValue->TYPE_STRING;
+ outValue->data = 0;
+ finalStr = str;
+ }
+
+ if (!res) {
+ return false;
+ }
+
+ if (outValue->dataType == outValue->TYPE_STRING) {
+ // Should do better merging styles.
+ if (pool) {
+ if (style != NULL && style->size() > 0) {
+ outValue->data = pool->add(finalStr, *style);
+ } else {
+ outValue->data = pool->add(finalStr, true);
+ }
+ } else {
+ // Caller will fill this in later.
+ outValue->data = 0;
+ }
+
+ if (outStr) {
+ *outStr = finalStr;
+ }
+
+ }
+
+ return true;
+}
+
+uint32_t ResourceTable::getCustomResource(
+ const String16& package, const String16& type, const String16& name) const
+{
+ //printf("getCustomResource: %s %s %s\n", String8(package).string(),
+ // String8(type).string(), String8(name).string());
+ sp<Package> p = mPackages.valueFor(package);
+ if (p == NULL) return 0;
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t == NULL) return 0;
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c == NULL) return 0;
+ int32_t ei = c->getEntryIndex();
+ if (ei < 0) return 0;
+ return getResId(p, t, ei);
+}
+
+uint32_t ResourceTable::getCustomResourceWithCreation(
+ const String16& package, const String16& type, const String16& name,
+ const bool createIfNotFound)
+{
+ uint32_t resId = getCustomResource(package, type, name);
+ if (resId != 0 || !createIfNotFound) {
+ return resId;
+ }
+ String16 value("false");
+
+ status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
+ if (status == NO_ERROR) {
+ resId = getResId(package, type, name);
+ return resId;
+ }
+ return 0;
+}
+
+uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const
+{
+ return origPackage;
+}
+
+bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType)
+{
+ //printf("getAttributeType #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) {
+ //printf("getAttributeType #%08x (%s): #%08x\n", attrID,
+ // String8(getEntry(attrID)->getName()).string(), value.data);
+ *outType = value.data;
+ return true;
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin)
+{
+ //printf("getAttributeMin #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) {
+ *outMin = value.data;
+ return true;
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax)
+{
+ //printf("getAttributeMax #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) {
+ *outMax = value.data;
+ return true;
+ }
+ return false;
+}
+
+uint32_t ResourceTable::getAttributeL10N(uint32_t attrID)
+{
+ //printf("getAttributeL10N #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) {
+ return value.data;
+ }
+ return ResTable_map::L10N_NOT_REQUIRED;
+}
+
+bool ResourceTable::getLocalizationSetting()
+{
+ return mBundle->getRequireLocalization();
+}
+
+void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...)
+{
+ if (accessorCookie != NULL && fmt != NULL) {
+ AccessorCookie* ac = (AccessorCookie*)accessorCookie;
+ int retval=0;
+ char buf[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n",
+ buf, ac->attr.string(), ac->value.string());
+ }
+}
+
+bool ResourceTable::getAttributeKeys(
+ uint32_t attrID, Vector<String16>* outKeys)
+{
+ sp<const Entry> e = getEntry(attrID);
+ if (e != NULL) {
+ const size_t N = e->getBag().size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = e->getBag().keyAt(i);
+ if (key.size() > 0 && key.string()[0] != '^') {
+ outKeys->add(key);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeEnum(
+ uint32_t attrID, const char16_t* name, size_t nameLen,
+ Res_value* outValue)
+{
+ //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string());
+ String16 nameStr(name, nameLen);
+ sp<const Entry> e = getEntry(attrID);
+ if (e != NULL) {
+ const size_t N = e->getBag().size();
+ for (size_t i=0; i<N; i++) {
+ //printf("Comparing %s to %s\n", String8(name, nameLen).string(),
+ // String8(e->getBag().keyAt(i)).string());
+ if (e->getBag().keyAt(i) == nameStr) {
+ return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue);
+ }
+ }
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeFlags(
+ uint32_t attrID, const char16_t* name, size_t nameLen,
+ Res_value* outValue)
+{
+ outValue->dataType = Res_value::TYPE_INT_HEX;
+ outValue->data = 0;
+
+ //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string());
+ String16 nameStr(name, nameLen);
+ sp<const Entry> e = getEntry(attrID);
+ if (e != NULL) {
+ const size_t N = e->getBag().size();
+
+ const char16_t* end = name + nameLen;
+ const char16_t* pos = name;
+ bool failed = false;
+ while (pos < end && !failed) {
+ const char16_t* start = pos;
+ end++;
+ while (pos < end && *pos != '|') {
+ pos++;
+ }
+
+ String16 nameStr(start, pos-start);
+ size_t i;
+ for (i=0; i<N; i++) {
+ //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(),
+ // String8(e->getBag().keyAt(i)).string());
+ if (e->getBag().keyAt(i) == nameStr) {
+ Res_value val;
+ bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val);
+ if (!got) {
+ return false;
+ }
+ //printf("Got value: 0x%08x\n", val.data);
+ outValue->data |= val.data;
+ break;
+ }
+ }
+
+ if (i >= N) {
+ // Didn't find this flag identifier.
+ return false;
+ }
+ if (pos < end) {
+ pos++;
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
+status_t ResourceTable::assignResourceIds()
+{
+ const size_t N = mOrderedPackages.size();
+ size_t pi;
+ status_t firstError = NO_ERROR;
+
+ // First generate all bag attributes and assign indices.
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p == NULL || p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ status_t err = p->applyPublicTypeOrder();
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+
+ // Generate attributes...
+ const size_t N = p->getOrderedTypes().size();
+ size_t ti;
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ const size_t N = t->getOrderedConfigs().size();
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
+ const size_t N = c->getEntries().size();
+ for (size_t ei=0; ei<N; ei++) {
+ sp<Entry> e = c->getEntries().valueAt(ei);
+ if (e == NULL) {
+ continue;
+ }
+ status_t err = e->generateAttributes(this, p->getName());
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+ }
+ }
+ }
+
+ const SourcePos unknown(String8("????"), 0);
+ sp<Type> attr = p->getType(String16("attr"), unknown);
+
+ // Assign indices...
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ err = t->applyPublicEntryOrder();
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+
+ const size_t N = t->getOrderedConfigs().size();
+ t->setIndex(ti+1);
+
+ LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
+ "First type is not attr!");
+
+ for (size_t ei=0; ei<N; ei++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
+ if (c == NULL) {
+ continue;
+ }
+ c->setEntryIndex(ei);
+ }
+ }
+
+ // Assign resource IDs to keys in bags...
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ const size_t N = t->getOrderedConfigs().size();
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ //printf("Ordered config #%d: %p\n", ci, c.get());
+ const size_t N = c->getEntries().size();
+ for (size_t ei=0; ei<N; ei++) {
+ sp<Entry> e = c->getEntries().valueAt(ei);
+ if (e == NULL) {
+ continue;
+ }
+ status_t err = e->assignResourceIds(this, p->getName());
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+ }
+ }
+ }
+ }
+ return firstError;
+}
+
+status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) {
+ const size_t N = mOrderedPackages.size();
+ size_t pi;
+
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ const size_t N = p->getOrderedTypes().size();
+ size_t ti;
+
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ const size_t N = t->getOrderedConfigs().size();
+ sp<AaptSymbols> typeSymbols;
+ typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
+ uint32_t rid = getResId(p, t, ci);
+ if (rid == 0) {
+ return UNKNOWN_ERROR;
+ }
+ if (Res_GETPACKAGE(rid) == (size_t)(p->getAssignedId()-1)) {
+ typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
+
+ String16 comment(c->getComment());
+ typeSymbols->appendComment(String8(c->getName()), comment, c->getPos());
+ //printf("Type symbol %s comment: %s\n", String8(e->getName()).string(),
+ // String8(comment).string());
+ comment = c->getTypeComment();
+ typeSymbols->appendTypeComment(String8(c->getName()), comment);
+ } else {
+#if 0
+ printf("**** NO MATCH: 0x%08x vs 0x%08x\n",
+ Res_GETPACKAGE(rid), p->getAssignedId());
+#endif
+ }
+ }
+ }
+ }
+ return NO_ERROR;
+}
+
+
+void
+ResourceTable::addLocalization(const String16& name, const String8& locale)
+{
+ mLocalizations[name].insert(locale);
+}
+
+
+/*!
+ * Flag various sorts of localization problems. '+' indicates checks already implemented;
+ * '-' indicates checks that will be implemented in the future.
+ *
+ * + A localized string for which no default-locale version exists => warning
+ * + A string for which no version in an explicitly-requested locale exists => warning
+ * + A localized translation of an translateable="false" string => warning
+ * - A localized string not provided in every locale used by the table
+ */
+status_t
+ResourceTable::validateLocalizations(void)
+{
+ status_t err = NO_ERROR;
+ const String8 defaultLocale;
+
+ // For all strings...
+ for (map<String16, set<String8> >::iterator nameIter = mLocalizations.begin();
+ nameIter != mLocalizations.end();
+ nameIter++) {
+ const set<String8>& configSet = nameIter->second; // naming convenience
+
+ // Look for strings with no default localization
+ if (configSet.count(defaultLocale) == 0) {
+ fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:",
+ String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]);
+ for (set<String8>::iterator locales = configSet.begin();
+ locales != configSet.end();
+ locales++) {
+ fprintf(stdout, " %s", (*locales).string());
+ }
+ fprintf(stdout, "\n");
+ // !!! TODO: throw an error here in some circumstances
+ }
+
+ // Check that all requested localizations are present for this string
+ if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) {
+ const char* allConfigs = mBundle->getConfigurations();
+ const char* start = allConfigs;
+ const char* comma;
+
+ do {
+ String8 config;
+ comma = strchr(start, ',');
+ if (comma != NULL) {
+ config.setTo(start, comma - start);
+ start = comma + 1;
+ } else {
+ config.setTo(start);
+ }
+
+ // don't bother with the pseudolocale "zz_ZZ"
+ if (config != "zz_ZZ") {
+ if (configSet.find(config) == configSet.end()) {
+ // okay, no specific localization found. it's possible that we are
+ // requiring a specific regional localization [e.g. de_DE] but there is an
+ // available string in the generic language localization [e.g. de];
+ // consider that string to have fulfilled the localization requirement.
+ String8 region(config.string(), 2);
+ if (configSet.find(region) == configSet.end()) {
+ if (configSet.count(defaultLocale) == 0) {
+ fprintf(stdout, "aapt: error: "
+ "*** string '%s' has no default or required localization "
+ "for '%s' in %s\n",
+ String8(nameIter->first).string(),
+ config.string(),
+ mBundle->getResourceSourceDirs()[0]);
+ err = UNKNOWN_ERROR;
+ }
+ }
+ }
+ }
+ } while (comma != NULL);
+ }
+ }
+
+ return err;
+}
+
+
+status_t
+ResourceFilter::parse(const char* arg)
+{
+ if (arg == NULL) {
+ return 0;
+ }
+
+ const char* p = arg;
+ const char* q;
+
+ while (true) {
+ q = strchr(p, ',');
+ if (q == NULL) {
+ q = p + strlen(p);
+ }
+
+ String8 part(p, q-p);
+
+ if (part == "zz_ZZ") {
+ mContainsPseudo = true;
+ }
+ int axis;
+ uint32_t value;
+ if (AaptGroupEntry::parseNamePart(part, &axis, &value)) {
+ fprintf(stderr, "Invalid configuration: %s\n", arg);
+ fprintf(stderr, " ");
+ for (int i=0; i<p-arg; i++) {
+ fprintf(stderr, " ");
+ }
+ for (int i=0; i<q-p; i++) {
+ fprintf(stderr, "^");
+ }
+ fprintf(stderr, "\n");
+ return 1;
+ }
+
+ ssize_t index = mData.indexOfKey(axis);
+ if (index < 0) {
+ mData.add(axis, SortedVector<uint32_t>());
+ }
+ SortedVector<uint32_t>& sv = mData.editValueFor(axis);
+ sv.add(value);
+ // if it's a locale with a region, also match an unmodified locale of the
+ // same language
+ if (axis == AXIS_LANGUAGE) {
+ if (value & 0xffff0000) {
+ sv.add(value & 0x0000ffff);
+ }
+ }
+ p = q;
+ if (!*p) break;
+ p++;
+ }
+
+ return NO_ERROR;
+}
+
+bool
+ResourceFilter::match(int axis, uint32_t value)
+{
+ if (value == 0) {
+ // they didn't specify anything so take everything
+ return true;
+ }
+ ssize_t index = mData.indexOfKey(axis);
+ if (index < 0) {
+ // we didn't request anything on this axis so take everything
+ return true;
+ }
+ const SortedVector<uint32_t>& sv = mData.valueAt(index);
+ return sv.indexOf(value) >= 0;
+}
+
+bool
+ResourceFilter::match(const ResTable_config& config)
+{
+ if (config.locale) {
+ uint32_t locale = (config.country[1] << 24) | (config.country[0] << 16)
+ | (config.language[1] << 8) | (config.language[0]);
+ if (!match(AXIS_LANGUAGE, locale)) {
+ return false;
+ }
+ }
+ if (!match(AXIS_ORIENTATION, config.orientation)) {
+ return false;
+ }
+ if (!match(AXIS_DENSITY, config.density)) {
+ return false;
+ }
+ if (!match(AXIS_TOUCHSCREEN, config.touchscreen)) {
+ return false;
+ }
+ if (!match(AXIS_KEYSHIDDEN, config.inputFlags)) {
+ return false;
+ }
+ if (!match(AXIS_KEYBOARD, config.keyboard)) {
+ return false;
+ }
+ if (!match(AXIS_NAVIGATION, config.navigation)) {
+ return false;
+ }
+ if (!match(AXIS_SCREENSIZE, config.screenSize)) {
+ return false;
+ }
+ if (!match(AXIS_VERSION, config.version)) {
+ return false;
+ }
+ return true;
+}
+
+status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
+{
+ ResourceFilter filter;
+ status_t err = filter.parse(bundle->getConfigurations());
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ const size_t N = mOrderedPackages.size();
+ size_t pi;
+
+ // Iterate through all data, collecting all values (strings,
+ // references, etc).
+ StringPool valueStrings;
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ StringPool typeStrings;
+ StringPool keyStrings;
+
+ const size_t N = p->getOrderedTypes().size();
+ for (size_t ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ typeStrings.add(String16("<empty>"), false);
+ continue;
+ }
+ typeStrings.add(t->getName(), false);
+
+ const size_t N = t->getOrderedConfigs().size();
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
+ const size_t N = c->getEntries().size();
+ for (size_t ei=0; ei<N; ei++) {
+ ConfigDescription config = c->getEntries().keyAt(ei);
+ if (!filter.match(config)) {
+ continue;
+ }
+ sp<Entry> e = c->getEntries().valueAt(ei);
+ if (e == NULL) {
+ continue;
+ }
+ e->setNameIndex(keyStrings.add(e->getName(), true));
+ status_t err = e->prepareFlatten(&valueStrings, this);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+ }
+ }
+
+ p->setTypeStrings(typeStrings.createStringBlock());
+ p->setKeyStrings(keyStrings.createStringBlock());
+ }
+
+ ssize_t strAmt = 0;
+
+ // Now build the array of package chunks.
+ Vector<sp<AaptFile> > flatPackages;
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ const size_t N = p->getTypeStrings().size();
+
+ const size_t baseSize = sizeof(ResTable_package);
+
+ // Start the package data.
+ sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
+ ResTable_package* header = (ResTable_package*)data->editData(baseSize);
+ if (header == NULL) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
+ return NO_MEMORY;
+ }
+ memset(header, 0, sizeof(*header));
+ header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
+ header->header.headerSize = htods(sizeof(*header));
+ header->id = htodl(p->getAssignedId());
+ strcpy16_htod(header->name, p->getName().string());
+
+ // Write the string blocks.
+ const size_t typeStringsStart = data->getSize();
+ sp<AaptFile> strFile = p->getTypeStringsData();
+ ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** type strings: %d\n", amt);
+ #endif
+ strAmt += amt;
+ if (amt < 0) {
+ return amt;
+ }
+ const size_t keyStringsStart = data->getSize();
+ strFile = p->getKeyStringsData();
+ amt = data->writeData(strFile->getData(), strFile->getSize());
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** key strings: %d\n", amt);
+ #endif
+ strAmt += amt;
+ if (amt < 0) {
+ return amt;
+ }
+
+ // Build the type chunks inside of this package.
+ for (size_t ti=0; ti<N; ti++) {
+ // Retrieve them in the same order as the type string block.
+ size_t len;
+ String16 typeName(p->getTypeStrings().stringAt(ti, &len));
+ sp<Type> t = p->getTypes().valueFor(typeName);
+ LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
+ "Type name %s not found",
+ String8(typeName).string());
+
+ const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
+
+ // First write the typeSpec chunk, containing information about
+ // each resource entry in this type.
+ {
+ const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
+ const size_t typeSpecStart = data->getSize();
+ ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
+ (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
+ if (tsHeader == NULL) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n");
+ return NO_MEMORY;
+ }
+ memset(tsHeader, 0, sizeof(*tsHeader));
+ tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
+ tsHeader->header.headerSize = htods(sizeof(*tsHeader));
+ tsHeader->header.size = htodl(typeSpecSize);
+ tsHeader->id = ti+1;
+ tsHeader->entryCount = htodl(N);
+
+ uint32_t* typeSpecFlags = (uint32_t*)
+ (((uint8_t*)data->editData())
+ + typeSpecStart + sizeof(ResTable_typeSpec));
+ memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
+
+ for (size_t ei=0; ei<N; ei++) {
+ sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+ if (cl->getPublic()) {
+ typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
+ }
+ const size_t CN = cl->getEntries().size();
+ for (size_t ci=0; ci<CN; ci++) {
+ if (!filter.match(cl->getEntries().keyAt(ci))) {
+ continue;
+ }
+ for (size_t cj=ci+1; cj<CN; cj++) {
+ if (!filter.match(cl->getEntries().keyAt(cj))) {
+ continue;
+ }
+ typeSpecFlags[ei] |= htodl(
+ cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
+ }
+ }
+ }
+ }
+
+ // We need to write one type chunk for each configuration for
+ // which we have entries in this type.
+ const size_t NC = t->getUniqueConfigs().size();
+
+ const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
+
+ for (size_t ci=0; ci<NC; ci++) {
+ ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
+
+ NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
+ "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d\n",
+ ti+1,
+ config.mcc, config.mnc,
+ config.language[0] ? config.language[0] : '-',
+ config.language[1] ? config.language[1] : '-',
+ config.country[0] ? config.country[0] : '-',
+ config.country[1] ? config.country[1] : '-',
+ config.orientation,
+ config.touchscreen,
+ config.density,
+ config.keyboard,
+ config.inputFlags,
+ config.navigation,
+ config.screenWidth,
+ config.screenHeight));
+
+ if (!filter.match(config)) {
+ continue;
+ }
+
+ const size_t typeStart = data->getSize();
+
+ ResTable_type* tHeader = (ResTable_type*)
+ (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
+ if (tHeader == NULL) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
+ return NO_MEMORY;
+ }
+
+ memset(tHeader, 0, sizeof(*tHeader));
+ tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
+ tHeader->header.headerSize = htods(sizeof(*tHeader));
+ tHeader->id = ti+1;
+ tHeader->entryCount = htodl(N);
+ tHeader->entriesStart = htodl(typeSize);
+ tHeader->config = config;
+ NOISY(printf("Writing type %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
+ "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d\n",
+ ti+1,
+ tHeader->config.mcc, tHeader->config.mnc,
+ tHeader->config.language[0] ? tHeader->config.language[0] : '-',
+ tHeader->config.language[1] ? tHeader->config.language[1] : '-',
+ tHeader->config.country[0] ? tHeader->config.country[0] : '-',
+ tHeader->config.country[1] ? tHeader->config.country[1] : '-',
+ tHeader->config.orientation,
+ tHeader->config.touchscreen,
+ tHeader->config.density,
+ tHeader->config.keyboard,
+ tHeader->config.inputFlags,
+ tHeader->config.navigation,
+ tHeader->config.screenWidth,
+ tHeader->config.screenHeight));
+ tHeader->config.swapHtoD();
+
+ // Build the entries inside of this type.
+ for (size_t ei=0; ei<N; ei++) {
+ sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+ sp<Entry> e = cl->getEntries().valueFor(config);
+
+ // Set the offset for this entry in its type.
+ uint32_t* index = (uint32_t*)
+ (((uint8_t*)data->editData())
+ + typeStart + sizeof(ResTable_type));
+ if (e != NULL) {
+ index[ei] = htodl(data->getSize()-typeStart-typeSize);
+
+ // Create the entry.
+ ssize_t amt = e->flatten(bundle, data, cl->getPublic());
+ if (amt < 0) {
+ return amt;
+ }
+ } else {
+ index[ei] = htodl(ResTable_type::NO_ENTRY);
+ }
+ }
+
+ // Fill in the rest of the type information.
+ tHeader = (ResTable_type*)
+ (((uint8_t*)data->editData()) + typeStart);
+ tHeader->header.size = htodl(data->getSize()-typeStart);
+ }
+ }
+
+ // Fill in the rest of the package information.
+ header = (ResTable_package*)data->editData();
+ header->header.size = htodl(data->getSize());
+ header->typeStrings = htodl(typeStringsStart);
+ header->lastPublicType = htodl(p->getTypeStrings().size());
+ header->keyStrings = htodl(keyStringsStart);
+ header->lastPublicKey = htodl(p->getKeyStrings().size());
+
+ flatPackages.add(data);
+ }
+
+ // And now write out the final chunks.
+ const size_t dataStart = dest->getSize();
+
+ {
+ // blah
+ ResTable_header header;
+ memset(&header, 0, sizeof(header));
+ header.header.type = htods(RES_TABLE_TYPE);
+ header.header.headerSize = htods(sizeof(header));
+ header.packageCount = htodl(flatPackages.size());
+ status_t err = dest->writeData(&header, sizeof(header));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
+ return err;
+ }
+ }
+
+ ssize_t strStart = dest->getSize();
+ err = valueStrings.writeStringBlock(dest);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ ssize_t amt = (dest->getSize()-strStart);
+ strAmt += amt;
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** value strings: %d\n", amt);
+ fprintf(stderr, "**** total strings: %d\n", strAmt);
+ #endif
+
+ for (pi=0; pi<flatPackages.size(); pi++) {
+ err = dest->writeData(flatPackages[pi]->getData(),
+ flatPackages[pi]->getSize());
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n");
+ return err;
+ }
+ }
+
+ ResTable_header* header = (ResTable_header*)
+ (((uint8_t*)dest->getData()) + dataStart);
+ header->header.size = htodl(dest->getSize() - dataStart);
+
+ NOISY(aout << "Resource table:"
+ << HexDump(dest->getData(), dest->getSize()) << endl);
+
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** total resource table size: %d / %d%% strings\n",
+ dest->getSize(), (strAmt*100)/dest->getSize());
+ #endif
+
+ return NO_ERROR;
+}
+
+void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp)
+{
+ fprintf(fp,
+ "<!-- This file contains <public> resource definitions for all\n"
+ " resources that were generated from the source data. -->\n"
+ "\n"
+ "<resources>\n");
+
+ writePublicDefinitions(package, fp, true);
+ writePublicDefinitions(package, fp, false);
+
+ fprintf(fp,
+ "\n"
+ "</resources>\n");
+}
+
+void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub)
+{
+ bool didHeader = false;
+
+ sp<Package> pkg = mPackages.valueFor(package);
+ if (pkg != NULL) {
+ const size_t NT = pkg->getOrderedTypes().size();
+ for (size_t i=0; i<NT; i++) {
+ sp<Type> t = pkg->getOrderedTypes().itemAt(i);
+ if (t == NULL) {
+ continue;
+ }
+
+ bool didType = false;
+
+ const size_t NC = t->getOrderedConfigs().size();
+ for (size_t j=0; j<NC; j++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(j);
+ if (c == NULL) {
+ continue;
+ }
+
+ if (c->getPublic() != pub) {
+ continue;
+ }
+
+ if (!didType) {
+ fprintf(fp, "\n");
+ didType = true;
+ }
+ if (!didHeader) {
+ if (pub) {
+ fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n");
+ fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n");
+ } else {
+ fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n");
+ fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n");
+ }
+ didHeader = true;
+ }
+ if (!pub) {
+ const size_t NE = c->getEntries().size();
+ for (size_t k=0; k<NE; k++) {
+ const SourcePos& pos = c->getEntries().valueAt(k)->getPos();
+ if (pos.file != "") {
+ fprintf(fp," <!-- Declared at %s:%d -->\n",
+ pos.file.string(), pos.line);
+ }
+ }
+ }
+ fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n",
+ String8(t->getName()).string(),
+ String8(c->getName()).string(),
+ getResId(pkg, t, c->getEntryIndex()));
+ }
+ }
+ }
+}
+
+ResourceTable::Item::Item(const SourcePos& _sourcePos,
+ bool _isId,
+ const String16& _value,
+ const Vector<StringPool::entry_style_span>* _style,
+ int32_t _format)
+ : sourcePos(_sourcePos)
+ , isId(_isId)
+ , value(_value)
+ , format(_format)
+ , bagKeyId(0)
+ , evaluating(false)
+{
+ if (_style) {
+ style = *_style;
+ }
+}
+
+status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
+{
+ if (mType == TYPE_BAG) {
+ return NO_ERROR;
+ }
+ if (mType == TYPE_UNKNOWN) {
+ mType = TYPE_BAG;
+ return NO_ERROR;
+ }
+ sourcePos.error("Resource entry %s is already defined as a single item.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(),
+ mItem.sourcePos.file.string(), mItem.sourcePos.line);
+ return UNKNOWN_ERROR;
+}
+
+status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ int32_t format,
+ const bool overwrite)
+{
+ Item item(sourcePos, false, value, style);
+
+ if (mType == TYPE_BAG) {
+ const Item& item(mBag.valueAt(0));
+ sourcePos.error("Resource entry %s is already defined as a bag.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(),
+ item.sourcePos.file.string(), item.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+ if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
+ sourcePos.error("Resource entry %s is already defined.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(),
+ mItem.sourcePos.file.string(), mItem.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+
+ mType = TYPE_ITEM;
+ mItem = item;
+ mItemFormat = format;
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
+ const String16& key, const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ bool replace, bool isId, int32_t format)
+{
+ status_t err = makeItABag(sourcePos);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ Item item(sourcePos, isId, value, style, format);
+
+ // XXX NOTE: there is an error if you try to have a bag with two keys,
+ // one an attr and one an id, with the same name. Not something we
+ // currently ever have to worry about.
+ ssize_t origKey = mBag.indexOfKey(key);
+ if (origKey >= 0) {
+ if (!replace) {
+ const Item& item(mBag.valueAt(origKey));
+ sourcePos.error("Resource entry %s already has bag item %s.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(), String8(key).string(),
+ item.sourcePos.file.string(), item.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+ //printf("Replacing %s with %s\n",
+ // String8(mBag.valueFor(key).value).string(), String8(value).string());
+ mBag.replaceValueFor(key, item);
+ }
+
+ mBag.add(key, item);
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::generateAttributes(ResourceTable* table,
+ const String16& package)
+{
+ const String16 attr16("attr");
+ const String16 id16("id");
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = mBag.keyAt(i);
+ const Item& it = mBag.valueAt(i);
+ if (it.isId) {
+ if (!table->hasBagOrEntry(key, &id16, &package)) {
+ String16 value("false");
+ status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package,
+ id16, key, value);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+ } else if (!table->hasBagOrEntry(key, &attr16, &package)) {
+
+#if 1
+// fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n",
+// String8(key).string());
+// const Item& item(mBag.valueAt(i));
+// fprintf(stderr, "Referenced from file %s line %d\n",
+// item.sourcePos.file.string(), item.sourcePos.line);
+// return UNKNOWN_ERROR;
+#else
+ char numberStr[16];
+ sprintf(numberStr, "%d", ResTable_map::TYPE_ANY);
+ status_t err = table->addBag(SourcePos("<generated>", 0), package,
+ attr16, key, String16(""),
+ String16("^type"),
+ String16(numberStr), NULL, NULL);
+ if (err != NO_ERROR) {
+ return err;
+ }
+#endif
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
+ const String16& package)
+{
+ bool hasErrors = false;
+
+ if (mType == TYPE_BAG) {
+ const char* errorMsg;
+ const String16 style16("style");
+ const String16 attr16("attr");
+ const String16 id16("id");
+ mParentId = 0;
+ if (mParent.size() > 0) {
+ mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
+ if (mParentId == 0) {
+ mPos.error("Error retrieving parent for item: %s '%s'.\n",
+ errorMsg, String8(mParent).string());
+ hasErrors = true;
+ }
+ }
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = mBag.keyAt(i);
+ Item& it = mBag.editValueAt(i);
+ it.bagKeyId = table->getResId(key,
+ it.isId ? &id16 : &attr16, NULL, &errorMsg);
+ //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId);
+ if (it.bagKeyId == 0) {
+ it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
+ String8(it.isId ? id16 : attr16).string(),
+ String8(key).string());
+ hasErrors = true;
+ }
+ }
+ }
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table)
+{
+ if (mType == TYPE_ITEM) {
+ Item& it = mItem;
+ AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
+ if (!table->stringToValue(&it.parsedValue, strings,
+ it.value, false, true, 0,
+ &it.style, NULL, &ac, mItemFormat)) {
+ return UNKNOWN_ERROR;
+ }
+ } else if (mType == TYPE_BAG) {
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = mBag.keyAt(i);
+ Item& it = mBag.editValueAt(i);
+ AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
+ if (!table->stringToValue(&it.parsedValue, strings,
+ it.value, false, true, it.bagKeyId,
+ &it.style, NULL, &ac, it.format)) {
+ return UNKNOWN_ERROR;
+ }
+ }
+ } else {
+ mPos.error("Error: entry %s is not a single item or a bag.\n",
+ String8(mName).string());
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
+ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic)
+{
+ size_t amt = 0;
+ ResTable_entry header;
+ memset(&header, 0, sizeof(header));
+ header.size = htods(sizeof(header));
+ const type ty = this != NULL ? mType : TYPE_ITEM;
+ if (this != NULL) {
+ if (ty == TYPE_BAG) {
+ header.flags |= htods(header.FLAG_COMPLEX);
+ }
+ if (isPublic) {
+ header.flags |= htods(header.FLAG_PUBLIC);
+ }
+ header.key.index = htodl(mNameIndex);
+ }
+ if (ty != TYPE_BAG) {
+ status_t err = data->writeData(&header, sizeof(header));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
+ return err;
+ }
+
+ const Item& it = mItem;
+ Res_value par;
+ memset(&par, 0, sizeof(par));
+ par.size = htods(it.parsedValue.size);
+ par.dataType = it.parsedValue.dataType;
+ par.res0 = it.parsedValue.res0;
+ par.data = htodl(it.parsedValue.data);
+ #if 0
+ printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n",
+ String8(mName).string(), it.parsedValue.dataType,
+ it.parsedValue.data, par.res0);
+ #endif
+ err = data->writeData(&par, it.parsedValue.size);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating Res_value\n");
+ return err;
+ }
+ amt += it.parsedValue.size;
+ } else {
+ size_t N = mBag.size();
+ size_t i;
+ // Create correct ordering of items.
+ KeyedVector<uint32_t, const Item*> items;
+ for (i=0; i<N; i++) {
+ const Item& it = mBag.valueAt(i);
+ items.add(it.bagKeyId, &it);
+ }
+ N = items.size();
+
+ ResTable_map_entry mapHeader;
+ memcpy(&mapHeader, &header, sizeof(header));
+ mapHeader.size = htods(sizeof(mapHeader));
+ mapHeader.parent.ident = htodl(mParentId);
+ mapHeader.count = htodl(N);
+ status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
+ return err;
+ }
+
+ for (i=0; i<N; i++) {
+ const Item& it = *items.valueAt(i);
+ ResTable_map map;
+ map.name.ident = htodl(it.bagKeyId);
+ map.value.size = htods(it.parsedValue.size);
+ map.value.dataType = it.parsedValue.dataType;
+ map.value.res0 = it.parsedValue.res0;
+ map.value.data = htodl(it.parsedValue.data);
+ err = data->writeData(&map, sizeof(map));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating Res_value\n");
+ return err;
+ }
+ amt += sizeof(map);
+ }
+ }
+ return amt;
+}
+
+void ResourceTable::ConfigList::appendComment(const String16& comment,
+ bool onlyIfEmpty)
+{
+ if (comment.size() <= 0) {
+ return;
+ }
+ if (onlyIfEmpty && mComment.size() > 0) {
+ return;
+ }
+ if (mComment.size() > 0) {
+ mComment.append(String16("\n"));
+ }
+ mComment.append(comment);
+}
+
+void ResourceTable::ConfigList::appendTypeComment(const String16& comment)
+{
+ if (comment.size() <= 0) {
+ return;
+ }
+ if (mTypeComment.size() > 0) {
+ mTypeComment.append(String16("\n"));
+ }
+ mTypeComment.append(comment);
+}
+
+status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos,
+ const String16& name,
+ const uint32_t ident)
+{
+ #if 0
+ int32_t entryIdx = Res_GETENTRY(ident);
+ if (entryIdx < 0) {
+ sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n",
+ String8(mName).string(), String8(name).string(), ident);
+ return UNKNOWN_ERROR;
+ }
+ #endif
+
+ int32_t typeIdx = Res_GETTYPE(ident);
+ if (typeIdx >= 0) {
+ typeIdx++;
+ if (mPublicIndex > 0 && mPublicIndex != typeIdx) {
+ sourcePos.error("Public resource %s/%s has conflicting type codes for its"
+ " public identifiers (0x%x vs 0x%x).\n",
+ String8(mName).string(), String8(name).string(),
+ mPublicIndex, typeIdx);
+ return UNKNOWN_ERROR;
+ }
+ mPublicIndex = typeIdx;
+ }
+
+ if (mFirstPublicSourcePos == NULL) {
+ mFirstPublicSourcePos = new SourcePos(sourcePos);
+ }
+
+ if (mPublic.indexOfKey(name) < 0) {
+ mPublic.add(name, Public(sourcePos, String16(), ident));
+ } else {
+ Public& p = mPublic.editValueFor(name);
+ if (p.ident != ident) {
+ sourcePos.error("Public resource %s/%s has conflicting public identifiers"
+ " (0x%08x vs 0x%08x).\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(), String8(name).string(), p.ident, ident,
+ p.sourcePos.file.string(), p.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
+ const SourcePos& sourcePos,
+ const ResTable_config* config,
+ bool doSetIndex)
+{
+ int pos = -1;
+ sp<ConfigList> c = mConfigs.valueFor(entry);
+ if (c == NULL) {
+ c = new ConfigList(entry, sourcePos);
+ mConfigs.add(entry, c);
+ pos = (int)mOrderedConfigs.size();
+ mOrderedConfigs.add(c);
+ if (doSetIndex) {
+ c->setEntryIndex(pos);
+ }
+ }
+
+ ConfigDescription cdesc;
+ if (config) cdesc = *config;
+
+ sp<Entry> e = c->getEntries().valueFor(cdesc);
+ if (e == NULL) {
+ if (config != NULL) {
+ NOISY(printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c "
+ "orien:%d touch:%d density:%d key:%d inp:%d nav:%d w:%d h:%d\n",
+ sourcePos.file.string(), sourcePos.line,
+ config->mcc, config->mnc,
+ config->language[0] ? config->language[0] : '-',
+ config->language[1] ? config->language[1] : '-',
+ config->country[0] ? config->country[0] : '-',
+ config->country[1] ? config->country[1] : '-',
+ config->orientation,
+ config->touchscreen,
+ config->density,
+ config->keyboard,
+ config->inputFlags,
+ config->navigation,
+ config->screenWidth,
+ config->screenHeight));
+ } else {
+ NOISY(printf("New entry at %s:%d: NULL config\n",
+ sourcePos.file.string(), sourcePos.line));
+ }
+ e = new Entry(entry, sourcePos);
+ c->addEntry(cdesc, e);
+ /*
+ if (doSetIndex) {
+ if (pos < 0) {
+ for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) {
+ if (mOrderedConfigs[pos] == c) {
+ break;
+ }
+ }
+ if (pos >= (int)mOrderedConfigs.size()) {
+ sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry");
+ return NULL;
+ }
+ }
+ e->setEntryIndex(pos);
+ }
+ */
+ }
+
+ mUniqueConfigs.add(cdesc);
+
+ return e;
+}
+
+status_t ResourceTable::Type::applyPublicEntryOrder()
+{
+ size_t N = mOrderedConfigs.size();
+ Vector<sp<ConfigList> > origOrder(mOrderedConfigs);
+ bool hasError = false;
+
+ size_t i;
+ for (i=0; i<N; i++) {
+ mOrderedConfigs.replaceAt(NULL, i);
+ }
+
+ const size_t NP = mPublic.size();
+ //printf("Ordering %d configs from %d public defs\n", N, NP);
+ size_t j;
+ for (j=0; j<NP; j++) {
+ const String16& name = mPublic.keyAt(j);
+ const Public& p = mPublic.valueAt(j);
+ int32_t idx = Res_GETENTRY(p.ident);
+ //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n",
+ // String8(mName).string(), String8(name).string(), p.ident, N);
+ bool found = false;
+ for (i=0; i<N; i++) {
+ sp<ConfigList> e = origOrder.itemAt(i);
+ //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
+ if (e->getName() == name) {
+ if (idx >= (int32_t)mOrderedConfigs.size()) {
+ p.sourcePos.error("Public entry identifier 0x%x entry index "
+ "is larger than available symbols (index %d, total symbols %d).\n",
+ p.ident, idx, mOrderedConfigs.size());
+ hasError = true;
+ } else if (mOrderedConfigs.itemAt(idx) == NULL) {
+ e->setPublic(true);
+ e->setPublicSourcePos(p.sourcePos);
+ mOrderedConfigs.replaceAt(e, idx);
+ origOrder.removeAt(i);
+ N--;
+ found = true;
+ break;
+ } else {
+ sp<ConfigList> oe = mOrderedConfigs.itemAt(idx);
+
+ p.sourcePos.error("Multiple entry names declared for public entry"
+ " identifier 0x%x in type %s (%s vs %s).\n"
+ "%s:%d: Originally defined here.",
+ idx+1, String8(mName).string(),
+ String8(oe->getName()).string(),
+ String8(name).string(),
+ oe->getPublicSourcePos().file.string(),
+ oe->getPublicSourcePos().line);
+ hasError = true;
+ }
+ }
+ }
+
+ if (!found) {
+ p.sourcePos.error("Public symbol %s/%s declared here is not defined.",
+ String8(mName).string(), String8(name).string());
+ hasError = true;
+ }
+ }
+
+ //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size());
+
+ if (N != origOrder.size()) {
+ printf("Internal error: remaining private symbol count mismatch\n");
+ N = origOrder.size();
+ }
+
+ j = 0;
+ for (i=0; i<N; i++) {
+ sp<ConfigList> e = origOrder.itemAt(i);
+ // There will always be enough room for the remaining entries.
+ while (mOrderedConfigs.itemAt(j) != NULL) {
+ j++;
+ }
+ mOrderedConfigs.replaceAt(e, j);
+ j++;
+ }
+
+ return hasError ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+ResourceTable::Package::Package(const String16& name, ssize_t includedId)
+ : mName(name), mIncludedId(includedId),
+ mTypeStringsMapping(0xffffffff),
+ mKeyStringsMapping(0xffffffff)
+{
+}
+
+sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
+ const SourcePos& sourcePos,
+ bool doSetIndex)
+{
+ sp<Type> t = mTypes.valueFor(type);
+ if (t == NULL) {
+ t = new Type(type, sourcePos);
+ mTypes.add(type, t);
+ mOrderedTypes.add(t);
+ if (doSetIndex) {
+ // For some reason the type's index is set to one plus the index
+ // in the mOrderedTypes list, rather than just the index.
+ t->setIndex(mOrderedTypes.size());
+ }
+ }
+ return t;
+}
+
+status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data)
+{
+ mTypeStringsData = data;
+ status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: Type string data is corrupt!\n");
+ }
+ return err;
+}
+
+status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data)
+{
+ mKeyStringsData = data;
+ status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: Key string data is corrupt!\n");
+ }
+ return err;
+}
+
+status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data,
+ ResStringPool* strings,
+ DefaultKeyedVector<String16, uint32_t>* mappings)
+{
+ if (data->getData() == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ NOISY(aout << "Setting restable string pool: "
+ << HexDump(data->getData(), data->getSize()) << endl);
+
+ status_t err = strings->setTo(data->getData(), data->getSize());
+ if (err == NO_ERROR) {
+ const size_t N = strings->size();
+ for (size_t i=0; i<N; i++) {
+ size_t len;
+ mappings->add(String16(strings->stringAt(i, &len)), i);
+ }
+ }
+ return err;
+}
+
+status_t ResourceTable::Package::applyPublicTypeOrder()
+{
+ size_t N = mOrderedTypes.size();
+ Vector<sp<Type> > origOrder(mOrderedTypes);
+
+ size_t i;
+ for (i=0; i<N; i++) {
+ mOrderedTypes.replaceAt(NULL, i);
+ }
+
+ for (i=0; i<N; i++) {
+ sp<Type> t = origOrder.itemAt(i);
+ int32_t idx = t->getPublicIndex();
+ if (idx > 0) {
+ idx--;
+ while (idx >= (int32_t)mOrderedTypes.size()) {
+ mOrderedTypes.add();
+ }
+ if (mOrderedTypes.itemAt(idx) != NULL) {
+ sp<Type> ot = mOrderedTypes.itemAt(idx);
+ t->getFirstPublicSourcePos().error("Multiple type names declared for public type"
+ " identifier 0x%x (%s vs %s).\n"
+ "%s:%d: Originally defined here.",
+ idx, String8(ot->getName()).string(),
+ String8(t->getName()).string(),
+ ot->getFirstPublicSourcePos().file.string(),
+ ot->getFirstPublicSourcePos().line);
+ return UNKNOWN_ERROR;
+ }
+ mOrderedTypes.replaceAt(t, idx);
+ origOrder.removeAt(i);
+ i--;
+ N--;
+ }
+ }
+
+ size_t j=0;
+ for (i=0; i<N; i++) {
+ sp<Type> t = origOrder.itemAt(i);
+ // There will always be enough room for the remaining types.
+ while (mOrderedTypes.itemAt(j) != NULL) {
+ j++;
+ }
+ mOrderedTypes.replaceAt(t, j);
+ }
+
+ return NO_ERROR;
+}
+
+sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
+{
+ sp<Package> p = mPackages.valueFor(package);
+ if (p == NULL) {
+ if (mIsAppPackage) {
+ if (mHaveAppPackage) {
+ fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n"
+ "Use -x to create extended resources.\n");
+ return NULL;
+ }
+ mHaveAppPackage = true;
+ p = new Package(package, 127);
+ } else {
+ p = new Package(package, mNextPackageId);
+ }
+ //printf("*** NEW PACKAGE: \"%s\" id=%d\n",
+ // String8(package).string(), p->getAssignedId());
+ mPackages.add(package, p);
+ mOrderedPackages.add(p);
+ mNextPackageId++;
+ }
+ return p;
+}
+
+sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
+ const String16& type,
+ const SourcePos& sourcePos,
+ bool doSetIndex)
+{
+ sp<Package> p = getPackage(package);
+ if (p == NULL) {
+ return NULL;
+ }
+ return p->getType(type, sourcePos, doSetIndex);
+}
+
+sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
+ const String16& type,
+ const String16& name,
+ const SourcePos& sourcePos,
+ const ResTable_config* config,
+ bool doSetIndex)
+{
+ sp<Type> t = getType(package, type, sourcePos, doSetIndex);
+ if (t == NULL) {
+ return NULL;
+ }
+ return t->getEntry(name, sourcePos, config, doSetIndex);
+}
+
+sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
+ const ResTable_config* config) const
+{
+ int pid = Res_GETPACKAGE(resID)+1;
+ const size_t N = mOrderedPackages.size();
+ size_t i;
+ sp<Package> p;
+ for (i=0; i<N; i++) {
+ sp<Package> check = mOrderedPackages[i];
+ if (check->getAssignedId() == pid) {
+ p = check;
+ break;
+ }
+
+ }
+ if (p == NULL) {
+ fprintf(stderr, "WARNING: Package not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ int tid = Res_GETTYPE(resID);
+ if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) {
+ fprintf(stderr, "WARNING: Type not found for resource #%08x\n", resID);
+ return NULL;
+ }
+ sp<Type> t = p->getOrderedTypes()[tid];
+
+ int eid = Res_GETENTRY(resID);
+ if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) {
+ fprintf(stderr, "WARNING: Entry not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ sp<ConfigList> c = t->getOrderedConfigs()[eid];
+ if (c == NULL) {
+ fprintf(stderr, "WARNING: Entry not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ ConfigDescription cdesc;
+ if (config) cdesc = *config;
+ sp<Entry> e = c->getEntries().valueFor(cdesc);
+ if (c == NULL) {
+ fprintf(stderr, "WARNING: Entry configuration not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ return e;
+}
+
+const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const
+{
+ sp<const Entry> e = getEntry(resID);
+ if (e == NULL) {
+ return NULL;
+ }
+
+ const size_t N = e->getBag().size();
+ for (size_t i=0; i<N; i++) {
+ const Item& it = e->getBag().valueAt(i);
+ if (it.bagKeyId == 0) {
+ fprintf(stderr, "WARNING: ID not yet assigned to '%s' in bag '%s'\n",
+ String8(e->getName()).string(),
+ String8(e->getBag().keyAt(i)).string());
+ }
+ if (it.bagKeyId == attrID) {
+ return &it;
+ }
+ }
+
+ return NULL;
+}
+
+bool ResourceTable::getItemValue(
+ uint32_t resID, uint32_t attrID, Res_value* outValue)
+{
+ const Item* item = getItem(resID, attrID);
+
+ bool res = false;
+ if (item != NULL) {
+ if (item->evaluating) {
+ sp<const Entry> e = getEntry(resID);
+ const size_t N = e->getBag().size();
+ size_t i;
+ for (i=0; i<N; i++) {
+ if (&e->getBag().valueAt(i) == item) {
+ break;
+ }
+ }
+ fprintf(stderr, "WARNING: Circular reference detected in key '%s' of bag '%s'\n",
+ String8(e->getName()).string(),
+ String8(e->getBag().keyAt(i)).string());
+ return false;
+ }
+ item->evaluating = true;
+ res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId);
+ NOISY(
+ if (res) {
+ printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n",
+ resID, attrID, String8(getEntry(resID)->getName()).string(),
+ outValue->dataType, outValue->data);
+ } else {
+ printf("getItemValue of #%08x[#%08x]: failed\n",
+ resID, attrID);
+ }
+ );
+ item->evaluating = false;
+ }
+ return res;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
new file mode 100644
index 0000000..e8fbd9b
--- /dev/null
+++ b/tools/aapt/ResourceTable.h
@@ -0,0 +1,534 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef RESOURCE_TABLE_H
+#define RESOURCE_TABLE_H
+
+#include "StringPool.h"
+#include "SourcePos.h"
+
+#include <set>
+#include <map>
+
+using namespace std;
+
+class ResourceTable;
+
+enum {
+ XML_COMPILE_STRIP_COMMENTS = 1<<0,
+ XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
+ XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
+ XML_COMPILE_STRIP_WHITESPACE = 1<<3,
+ XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
+
+ XML_COMPILE_STANDARD_RESOURCE =
+ XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
+};
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<AaptFile>& target,
+ ResourceTable* table,
+ int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileResourceFile(Bundle* bundle,
+ const sp<AaptAssets>& assets,
+ const sp<AaptFile>& in,
+ const ResTable_config& defParams,
+ const bool overwrite,
+ ResourceTable* outTable);
+
+struct AccessorCookie
+{
+ SourcePos sourcePos;
+ String8 attr;
+ String8 value;
+
+ AccessorCookie(const SourcePos&p, const String8& a, const String8& v)
+ :sourcePos(p),
+ attr(a),
+ value(v)
+ {
+ }
+};
+
+class ResourceTable : public ResTable::Accessor
+{
+public:
+ class Package;
+ class Type;
+ class Entry;
+
+ ResourceTable(Bundle* bundle, const String16& assetsPackage);
+
+ status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
+
+ status_t addPublic(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const uint32_t ident);
+
+ status_t addEntry(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ const ResTable_config* params = NULL,
+ const bool doSetIndex = false,
+ const int32_t format = ResTable_map::TYPE_ANY,
+ const bool overwrite = false);
+
+ status_t startBag(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const ResTable_config* params = NULL,
+ bool replace = false,
+ bool isId = false);
+
+ status_t addBag(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const String16& bagKey,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ const ResTable_config* params = NULL,
+ bool replace = false,
+ bool isId = false,
+ const int32_t format = ResTable_map::TYPE_ANY);
+
+ bool hasBagOrEntry(const String16& package,
+ const String16& type,
+ const String16& name) const;
+
+ bool hasBagOrEntry(const String16& ref,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL);
+
+ bool appendComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment,
+ bool onlyIfEmpty = false);
+
+ bool appendTypeComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment);
+
+ size_t size() const;
+ size_t numLocalResources() const;
+ bool hasResources() const;
+
+ sp<AaptFile> flatten(Bundle*);
+
+ static inline uint32_t makeResId(uint32_t packageId,
+ uint32_t typeId,
+ uint32_t nameId)
+ {
+ return nameId | (typeId<<16) | (packageId<<24);
+ }
+
+ static inline uint32_t getResId(const sp<Package>& p,
+ const sp<Type>& t,
+ uint32_t nameId);
+
+ uint32_t getResId(const String16& package,
+ const String16& type,
+ const String16& name,
+ bool onlyPublic = false) const;
+
+ uint32_t getResId(const String16& ref,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL,
+ const char** outErrorMsg = NULL,
+ bool onlyPublic = false) const;
+
+ static bool isValidResourceName(const String16& s);
+
+ bool stringToValue(Res_value* outValue, StringPool* pool,
+ const String16& str,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ String16* outStr = NULL, void* accessorCookie = NULL,
+ uint32_t attrType = ResTable_map::TYPE_ANY);
+
+ status_t assignResourceIds();
+ status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL);
+ void addLocalization(const String16& name, const String8& locale);
+ status_t validateLocalizations(void);
+
+ status_t flatten(Bundle*, const sp<AaptFile>& dest);
+
+ void writePublicDefinitions(const String16& package, FILE* fp);
+
+ virtual uint32_t getCustomResource(const String16& package,
+ const String16& type,
+ const String16& name) const;
+ virtual uint32_t getCustomResourceWithCreation(const String16& package,
+ const String16& type,
+ const String16& name,
+ const bool createIfNeeded);
+ virtual uint32_t getRemappedPackage(uint32_t origPackage) const;
+ virtual bool getAttributeType(uint32_t attrID, uint32_t* outType);
+ virtual bool getAttributeMin(uint32_t attrID, uint32_t* outMin);
+ virtual bool getAttributeMax(uint32_t attrID, uint32_t* outMax);
+ virtual bool getAttributeKeys(uint32_t attrID, Vector<String16>* outKeys);
+ virtual bool getAttributeEnum(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue);
+ virtual bool getAttributeFlags(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue);
+ virtual uint32_t getAttributeL10N(uint32_t attrID);
+
+ virtual bool getLocalizationSetting();
+ virtual void reportError(void* accessorCookie, const char* fmt, ...);
+
+ void setCurrentXmlPos(const SourcePos& pos) { mCurrentXmlPos = pos; }
+
+ class Item {
+ public:
+ Item() : isId(false), format(ResTable_map::TYPE_ANY), bagKeyId(0), evaluating(false)
+ { memset(&parsedValue, 0, sizeof(parsedValue)); }
+ Item(const SourcePos& pos,
+ bool _isId,
+ const String16& _value,
+ const Vector<StringPool::entry_style_span>* _style = NULL,
+ int32_t format = ResTable_map::TYPE_ANY);
+ Item(const Item& o) : sourcePos(o.sourcePos),
+ isId(o.isId), value(o.value), style(o.style),
+ format(o.format), bagKeyId(o.bagKeyId), evaluating(false) {
+ memset(&parsedValue, 0, sizeof(parsedValue));
+ }
+ ~Item() { }
+
+ Item& operator=(const Item& o) {
+ sourcePos = o.sourcePos;
+ isId = o.isId;
+ value = o.value;
+ style = o.style;
+ format = o.format;
+ bagKeyId = o.bagKeyId;
+ parsedValue = o.parsedValue;
+ return *this;
+ }
+
+ SourcePos sourcePos;
+ mutable bool isId;
+ String16 value;
+ Vector<StringPool::entry_style_span> style;
+ int32_t format;
+ uint32_t bagKeyId;
+ mutable bool evaluating;
+ Res_value parsedValue;
+ };
+
+ class Entry : public RefBase {
+ public:
+ Entry(const String16& name, const SourcePos& pos)
+ : mName(name), mType(TYPE_UNKNOWN),
+ mItemFormat(ResTable_map::TYPE_ANY), mNameIndex(-1), mPos(pos)
+ { }
+ virtual ~Entry() { }
+
+ enum type {
+ TYPE_UNKNOWN = 0,
+ TYPE_ITEM,
+ TYPE_BAG
+ };
+
+ String16 getName() const { return mName; }
+ type getType() const { return mType; }
+
+ void setParent(const String16& parent) { mParent = parent; }
+ String16 getParent() const { return mParent; }
+
+ status_t makeItABag(const SourcePos& sourcePos);
+
+ status_t setItem(const SourcePos& pos,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ int32_t format = ResTable_map::TYPE_ANY,
+ const bool overwrite = false);
+
+ status_t addToBag(const SourcePos& pos,
+ const String16& key, const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ bool replace=false, bool isId = false,
+ int32_t format = ResTable_map::TYPE_ANY);
+
+ // Index of the entry's name string in the key pool.
+ int32_t getNameIndex() const { return mNameIndex; }
+ void setNameIndex(int32_t index) { mNameIndex = index; }
+
+ const Item* getItem() const { return mType == TYPE_ITEM ? &mItem : NULL; }
+ const KeyedVector<String16, Item>& getBag() const { return mBag; }
+
+ status_t generateAttributes(ResourceTable* table,
+ const String16& package);
+
+ status_t assignResourceIds(ResourceTable* table,
+ const String16& package);
+
+ status_t prepareFlatten(StringPool* strings, ResourceTable* table);
+
+ ssize_t flatten(Bundle*, const sp<AaptFile>& data, bool isPublic);
+
+ const SourcePos& getPos() const { return mPos; }
+
+ private:
+ String16 mName;
+ String16 mParent;
+ type mType;
+ Item mItem;
+ int32_t mItemFormat;
+ KeyedVector<String16, Item> mBag;
+ int32_t mNameIndex;
+ uint32_t mParentId;
+ SourcePos mPos;
+ };
+
+ struct ConfigDescription : public ResTable_config {
+ ConfigDescription() {
+ memset(this, 0, sizeof(*this));
+ size = sizeof(ResTable_config);
+ }
+ ConfigDescription(const ResTable_config&o) {
+ *static_cast<ResTable_config*>(this) = o;
+ size = sizeof(ResTable_config);
+ }
+ ConfigDescription(const ConfigDescription&o) {
+ *static_cast<ResTable_config*>(this) = o;
+ }
+
+ ConfigDescription& operator=(const ResTable_config& o) {
+ *static_cast<ResTable_config*>(this) = o;
+ size = sizeof(ResTable_config);
+ return *this;
+ }
+ ConfigDescription& operator=(const ConfigDescription& o) {
+ *static_cast<ResTable_config*>(this) = o;
+ return *this;
+ }
+
+ inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
+ inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
+ inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
+ inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
+ inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
+ inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
+ };
+
+ class ConfigList : public RefBase {
+ public:
+ ConfigList(const String16& name, const SourcePos& pos)
+ : mName(name), mPos(pos), mPublic(false), mEntryIndex(-1) { }
+ virtual ~ConfigList() { }
+
+ String16 getName() const { return mName; }
+ const SourcePos& getPos() const { return mPos; }
+
+ void appendComment(const String16& comment, bool onlyIfEmpty = false);
+ const String16& getComment() const { return mComment; }
+
+ void appendTypeComment(const String16& comment);
+ const String16& getTypeComment() const { return mTypeComment; }
+
+ // Index of this entry in its Type.
+ int32_t getEntryIndex() const { return mEntryIndex; }
+ void setEntryIndex(int32_t index) { mEntryIndex = index; }
+
+ void setPublic(bool pub) { mPublic = pub; }
+ bool getPublic() const { return mPublic; }
+ void setPublicSourcePos(const SourcePos& pos) { mPublicSourcePos = pos; }
+ const SourcePos& getPublicSourcePos() { return mPublicSourcePos; }
+
+ void addEntry(const ResTable_config& config, const sp<Entry>& entry) {
+ mEntries.add(config, entry);
+ }
+
+ const DefaultKeyedVector<ConfigDescription, sp<Entry> >& getEntries() const { return mEntries; }
+ private:
+ const String16 mName;
+ const SourcePos mPos;
+ String16 mComment;
+ String16 mTypeComment;
+ bool mPublic;
+ SourcePos mPublicSourcePos;
+ int32_t mEntryIndex;
+ DefaultKeyedVector<ConfigDescription, sp<Entry> > mEntries;
+ };
+
+ class Public {
+ public:
+ Public() : sourcePos(), ident(0) { }
+ Public(const SourcePos& pos,
+ const String16& _comment,
+ uint32_t _ident)
+ : sourcePos(pos),
+ comment(_comment), ident(_ident) { }
+ Public(const Public& o) : sourcePos(o.sourcePos),
+ comment(o.comment), ident(o.ident) { }
+ ~Public() { }
+
+ Public& operator=(const Public& o) {
+ sourcePos = o.sourcePos;
+ comment = o.comment;
+ ident = o.ident;
+ return *this;
+ }
+
+ SourcePos sourcePos;
+ String16 comment;
+ uint32_t ident;
+ };
+
+ class Type : public RefBase {
+ public:
+ Type(const String16& name, const SourcePos& pos)
+ : mName(name), mFirstPublicSourcePos(NULL), mPublicIndex(-1), mIndex(-1), mPos(pos)
+ { }
+ virtual ~Type() { delete mFirstPublicSourcePos; }
+
+ status_t addPublic(const SourcePos& pos,
+ const String16& name,
+ const uint32_t ident);
+
+ String16 getName() const { return mName; }
+ sp<Entry> getEntry(const String16& entry,
+ const SourcePos& pos,
+ const ResTable_config* config = NULL,
+ bool doSetIndex = false);
+
+ const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; }
+
+ int32_t getPublicIndex() const { return mPublicIndex; }
+
+ int32_t getIndex() const { return mIndex; }
+ void setIndex(int32_t index) { mIndex = index; }
+
+ status_t applyPublicEntryOrder();
+
+ const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; }
+
+ const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; }
+ const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; }
+
+ const SourcePos& getPos() const { return mPos; }
+ private:
+ String16 mName;
+ SourcePos* mFirstPublicSourcePos;
+ DefaultKeyedVector<String16, Public> mPublic;
+ SortedVector<ConfigDescription> mUniqueConfigs;
+ DefaultKeyedVector<String16, sp<ConfigList> > mConfigs;
+ Vector<sp<ConfigList> > mOrderedConfigs;
+ int32_t mPublicIndex;
+ int32_t mIndex;
+ SourcePos mPos;
+ };
+
+ class Package : public RefBase {
+ public:
+ Package(const String16& name, ssize_t includedId=-1);
+ virtual ~Package() { }
+
+ String16 getName() const { return mName; }
+ sp<Type> getType(const String16& type,
+ const SourcePos& pos,
+ bool doSetIndex = false);
+
+ ssize_t getAssignedId() const { return mIncludedId; }
+
+ const ResStringPool& getTypeStrings() const { return mTypeStrings; }
+ uint32_t indexOfTypeString(const String16& s) const { return mTypeStringsMapping.valueFor(s); }
+ const sp<AaptFile> getTypeStringsData() const { return mTypeStringsData; }
+ status_t setTypeStrings(const sp<AaptFile>& data);
+
+ const ResStringPool& getKeyStrings() const { return mKeyStrings; }
+ uint32_t indexOfKeyString(const String16& s) const { return mKeyStringsMapping.valueFor(s); }
+ const sp<AaptFile> getKeyStringsData() const { return mKeyStringsData; }
+ status_t setKeyStrings(const sp<AaptFile>& data);
+
+ status_t applyPublicTypeOrder();
+
+ const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; }
+ const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; }
+
+ private:
+ status_t setStrings(const sp<AaptFile>& data,
+ ResStringPool* strings,
+ DefaultKeyedVector<String16, uint32_t>* mappings);
+
+ const String16 mName;
+ const ssize_t mIncludedId;
+ DefaultKeyedVector<String16, sp<Type> > mTypes;
+ Vector<sp<Type> > mOrderedTypes;
+ sp<AaptFile> mTypeStringsData;
+ sp<AaptFile> mKeyStringsData;
+ ResStringPool mTypeStrings;
+ ResStringPool mKeyStrings;
+ DefaultKeyedVector<String16, uint32_t> mTypeStringsMapping;
+ DefaultKeyedVector<String16, uint32_t> mKeyStringsMapping;
+ };
+
+private:
+ void writePublicDefinitions(const String16& package, FILE* fp, bool pub);
+ sp<Package> getPackage(const String16& package);
+ sp<Type> getType(const String16& package,
+ const String16& type,
+ const SourcePos& pos,
+ bool doSetIndex = false);
+ sp<Entry> getEntry(const String16& package,
+ const String16& type,
+ const String16& name,
+ const SourcePos& pos,
+ const ResTable_config* config = NULL,
+ bool doSetIndex = false);
+ sp<const Entry> getEntry(uint32_t resID,
+ const ResTable_config* config = NULL) const;
+ const Item* getItem(uint32_t resID, uint32_t attrID) const;
+ bool getItemValue(uint32_t resID, uint32_t attrID,
+ Res_value* outValue);
+
+
+ String16 mAssetsPackage;
+ sp<AaptAssets> mAssets;
+ DefaultKeyedVector<String16, sp<Package> > mPackages;
+ Vector<sp<Package> > mOrderedPackages;
+ uint32_t mNextPackageId;
+ bool mHaveAppPackage;
+ bool mIsAppPackage;
+ size_t mNumLocal;
+ SourcePos mCurrentXmlPos;
+ Bundle* mBundle;
+
+ // key = string resource name, value = set of locales in which that name is defined
+ map<String16, set<String8> > mLocalizations;
+};
+
+class ResourceFilter
+{
+public:
+ ResourceFilter() : mData(), mContainsPseudo(false) {}
+ status_t parse(const char* arg);
+ bool match(int axis, uint32_t value);
+ bool match(const ResTable_config& config);
+ inline bool containsPseudo() { return mContainsPseudo; }
+
+private:
+ KeyedVector<int,SortedVector<uint32_t> > mData;
+ bool mContainsPseudo;
+};
+
+
+#endif
diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp
new file mode 100644
index 0000000..2761d18
--- /dev/null
+++ b/tools/aapt/SourcePos.cpp
@@ -0,0 +1,171 @@
+#include "SourcePos.h"
+
+#include <stdarg.h>
+#include <vector>
+
+using namespace std;
+
+
+// ErrorPos
+// =============================================================================
+struct ErrorPos
+{
+ String8 file;
+ int line;
+ String8 error;
+ bool fatal;
+
+ ErrorPos();
+ ErrorPos(const ErrorPos& that);
+ ErrorPos(const String8& file, int line, const String8& error, bool fatal);
+ ~ErrorPos();
+ bool operator<(const ErrorPos& rhs) const;
+ bool operator==(const ErrorPos& rhs) const;
+ ErrorPos& operator=(const ErrorPos& rhs);
+
+ void print(FILE* to) const;
+};
+
+static vector<ErrorPos> g_errors;
+
+ErrorPos::ErrorPos()
+ :line(-1), fatal(false)
+{
+}
+
+ErrorPos::ErrorPos(const ErrorPos& that)
+ :file(that.file),
+ line(that.line),
+ error(that.error),
+ fatal(that.fatal)
+{
+}
+
+ErrorPos::ErrorPos(const String8& f, int l, const String8& e, bool fat)
+ :file(f),
+ line(l),
+ error(e),
+ fatal(fat)
+{
+}
+
+ErrorPos::~ErrorPos()
+{
+}
+
+bool
+ErrorPos::operator<(const ErrorPos& rhs) const
+{
+ if (this->file < rhs.file) return true;
+ if (this->file == rhs.file) {
+ if (this->line < rhs.line) return true;
+ if (this->line == rhs.line) {
+ if (this->error < rhs.error) return true;
+ }
+ }
+ return false;
+}
+
+bool
+ErrorPos::operator==(const ErrorPos& rhs) const
+{
+ return this->file == rhs.file
+ && this->line == rhs.line
+ && this->error == rhs.error;
+}
+
+ErrorPos&
+ErrorPos::operator=(const ErrorPos& rhs)
+{
+ this->file = rhs.file;
+ this->line = rhs.line;
+ this->error = rhs.error;
+ return *this;
+}
+
+void
+ErrorPos::print(FILE* to) const
+{
+ const char* type = fatal ? "ERROR" : "WARNING";
+
+ if (this->line >= 0) {
+ fprintf(to, "%s:%d: %s %s\n", this->file.string(), this->line, type, this->error.string());
+ } else {
+ fprintf(to, "%s: %s %s\n", this->file.string(), type, this->error.string());
+ }
+}
+
+// SourcePos
+// =============================================================================
+SourcePos::SourcePos(const String8& f, int l)
+ : file(f), line(l)
+{
+}
+
+SourcePos::SourcePos(const SourcePos& that)
+ : file(that.file), line(that.line)
+{
+}
+
+SourcePos::SourcePos()
+ : file("???", 0), line(-1)
+{
+}
+
+SourcePos::~SourcePos()
+{
+}
+
+int
+SourcePos::error(const char* fmt, ...) const
+{
+ int retval=0;
+ char buf[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ char* p = buf + retval - 1;
+ while (p > buf && *p == '\n') {
+ *p = '\0';
+ p--;
+ }
+ g_errors.push_back(ErrorPos(this->file, this->line, String8(buf), true));
+ return retval;
+}
+
+int
+SourcePos::warning(const char* fmt, ...) const
+{
+ int retval=0;
+ char buf[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ char* p = buf + retval - 1;
+ while (p > buf && *p == '\n') {
+ *p = '\0';
+ p--;
+ }
+ ErrorPos(this->file, this->line, String8(buf), false).print(stderr);
+ return retval;
+}
+
+bool
+SourcePos::hasErrors()
+{
+ return g_errors.size() > 0;
+}
+
+void
+SourcePos::printErrors(FILE* to)
+{
+ vector<ErrorPos>::const_iterator it;
+ for (it=g_errors.begin(); it!=g_errors.end(); it++) {
+ it->print(to);
+ }
+}
+
+
+
diff --git a/tools/aapt/SourcePos.h b/tools/aapt/SourcePos.h
new file mode 100644
index 0000000..33f72a9
--- /dev/null
+++ b/tools/aapt/SourcePos.h
@@ -0,0 +1,28 @@
+#ifndef SOURCEPOS_H
+#define SOURCEPOS_H
+
+#include <utils/String8.h>
+#include <stdio.h>
+
+using namespace android;
+
+class SourcePos
+{
+public:
+ String8 file;
+ int line;
+
+ SourcePos(const String8& f, int l);
+ SourcePos(const SourcePos& that);
+ SourcePos();
+ ~SourcePos();
+
+ int error(const char* fmt, ...) const;
+ int warning(const char* fmt, ...) const;
+
+ static bool hasErrors();
+ static void printErrors(FILE* to);
+};
+
+
+#endif // SOURCEPOS_H
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
new file mode 100644
index 0000000..878d3b1
--- /dev/null
+++ b/tools/aapt/StringPool.cpp
@@ -0,0 +1,369 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "StringPool.h"
+
+#include <utils/ByteOrder.h>
+
+#define NOISY(x) //x
+
+void strcpy16_htod(uint16_t* dst, const uint16_t* src)
+{
+ while (*src) {
+ char16_t s = htods(*src);
+ *dst++ = s;
+ src++;
+ }
+ *dst = 0;
+}
+
+void printStringPool(const ResStringPool* pool)
+{
+ const size_t NS = pool->size();
+ for (size_t s=0; s<NS; s++) {
+ size_t len;
+ printf("String #%d: %s\n", s,
+ String8(pool->stringAt(s, &len)).string());
+ }
+}
+
+StringPool::StringPool(bool sorted)
+ : mSorted(sorted), mValues(-1), mIdents(-1)
+{
+}
+
+ssize_t StringPool::add(const String16& value, bool mergeDuplicates)
+{
+ return add(String16(), value, mergeDuplicates);
+}
+
+ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans)
+{
+ ssize_t res = add(String16(), value, false);
+ if (res >= 0) {
+ addStyleSpans(res, spans);
+ }
+ return res;
+}
+
+ssize_t StringPool::add(const String16& ident, const String16& value,
+ bool mergeDuplicates)
+{
+ if (ident.size() > 0) {
+ ssize_t idx = mIdents.valueFor(ident);
+ if (idx >= 0) {
+ fprintf(stderr, "ERROR: Duplicate string identifier %s\n",
+ String8(mEntries[idx].value).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ ssize_t vidx = mValues.indexOfKey(value);
+ ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1;
+ ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1;
+ if (eidx < 0) {
+ eidx = mEntries.add(entry(value));
+ if (eidx < 0) {
+ fprintf(stderr, "Failure adding string %s\n", String8(value).string());
+ return eidx;
+ }
+ }
+
+ const bool first = vidx < 0;
+ if (first || !mergeDuplicates) {
+ pos = mEntryArray.add(eidx);
+ if (first) {
+ vidx = mValues.add(value, pos);
+ const size_t N = mEntryArrayToValues.size();
+ for (size_t i=0; i<N; i++) {
+ size_t& e = mEntryArrayToValues.editItemAt(i);
+ if ((ssize_t)e >= vidx) {
+ e++;
+ }
+ }
+ }
+ mEntryArrayToValues.add(vidx);
+ if (!mSorted) {
+ entry& ent = mEntries.editItemAt(eidx);
+ ent.indices.add(pos);
+ }
+ }
+
+ if (ident.size() > 0) {
+ mIdents.add(ident, vidx);
+ }
+
+ NOISY(printf("Adding string %s to pool: pos=%d eidx=%d vidx=%d\n",
+ String8(value).string(), pos, eidx, vidx));
+
+ return pos;
+}
+
+status_t StringPool::addStyleSpan(size_t idx, const String16& name,
+ uint32_t start, uint32_t end)
+{
+ entry_style_span span;
+ span.name = name;
+ span.span.firstChar = start;
+ span.span.lastChar = end;
+ return addStyleSpan(idx, span);
+}
+
+status_t StringPool::addStyleSpans(size_t idx, const Vector<entry_style_span>& spans)
+{
+ const size_t N=spans.size();
+ for (size_t i=0; i<N; i++) {
+ status_t err = addStyleSpan(idx, spans[i]);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span)
+{
+ LOG_ALWAYS_FATAL_IF(mSorted, "Can't use styles with sorted string pools.");
+
+ // Place blank entries in the span array up to this index.
+ while (mEntryStyleArray.size() <= idx) {
+ mEntryStyleArray.add();
+ }
+
+ entry_style& style = mEntryStyleArray.editItemAt(idx);
+ style.spans.add(span);
+ return NO_ERROR;
+}
+
+size_t StringPool::size() const
+{
+ return mSorted ? mValues.size() : mEntryArray.size();
+}
+
+const StringPool::entry& StringPool::entryAt(size_t idx) const
+{
+ if (!mSorted) {
+ return mEntries[mEntryArray[idx]];
+ } else {
+ return mEntries[mEntryArray[mValues.valueAt(idx)]];
+ }
+}
+
+size_t StringPool::countIdentifiers() const
+{
+ return mIdents.size();
+}
+
+sp<AaptFile> StringPool::createStringBlock()
+{
+ sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
+ String8());
+ status_t err = writeStringBlock(pool);
+ return err == NO_ERROR ? pool : NULL;
+}
+
+status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
+{
+ // Allow appending. Sorry this is a little wacky.
+ if (pool->getSize() > 0) {
+ sp<AaptFile> block = createStringBlock();
+ if (block == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ ssize_t res = pool->writeData(block->getData(), block->getSize());
+ return (res >= 0) ? (status_t)NO_ERROR : res;
+ }
+
+ // First we need to add all style span names to the string pool.
+ // We do this now (instead of when the span is added) so that these
+ // will appear at the end of the pool, not disrupting the order
+ // our client placed their own strings in it.
+
+ const size_t STYLES = mEntryStyleArray.size();
+ size_t i;
+
+ for (i=0; i<STYLES; i++) {
+ entry_style& style = mEntryStyleArray.editItemAt(i);
+ const size_t N = style.spans.size();
+ for (size_t i=0; i<N; i++) {
+ entry_style_span& span = style.spans.editItemAt(i);
+ ssize_t idx = add(span.name, true);
+ if (idx < 0) {
+ fprintf(stderr, "Error adding span for style tag '%s'\n",
+ String8(span.name).string());
+ return idx;
+ }
+ span.span.name.index = (uint32_t)idx;
+ }
+ }
+
+ const size_t ENTRIES = size();
+
+ // Now build the pool of unique strings.
+
+ const size_t STRINGS = mEntries.size();
+ const size_t preSize = sizeof(ResStringPool_header)
+ + (sizeof(uint32_t)*ENTRIES)
+ + (sizeof(uint32_t)*STYLES);
+ if (pool->editData(preSize) == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string pool\n");
+ return NO_MEMORY;
+ }
+
+ size_t strPos = 0;
+ for (i=0; i<STRINGS; i++) {
+ entry& ent = mEntries.editItemAt(i);
+ const size_t strSize = (ent.value.size());
+ const size_t lenSize = strSize > 0x7fff ? sizeof(uint32_t) : sizeof(uint16_t);
+ const size_t totalSize = lenSize + ((strSize+1)*sizeof(uint16_t));
+
+ ent.offset = strPos;
+ uint16_t* dat = (uint16_t*)pool->editData(preSize + strPos + totalSize);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string pool\n");
+ return NO_MEMORY;
+ }
+ dat += (preSize+strPos)/sizeof(uint16_t);
+ if (lenSize > sizeof(uint16_t)) {
+ *dat = htods(0x8000 | ((strSize>>16)&0x7ffff));
+ dat++;
+ }
+ *dat++ = htods(strSize);
+ strcpy16_htod(dat, ent.value);
+
+ strPos += lenSize + (strSize+1)*sizeof(uint16_t);
+ }
+
+ // Pad ending string position up to a uint32_t boundary.
+
+ if (strPos&0x3) {
+ size_t padPos = ((strPos+3)&~0x3);
+ uint8_t* dat = (uint8_t*)pool->editData(preSize + padPos);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory padding string pool\n");
+ return NO_MEMORY;
+ }
+ memset(dat+preSize+strPos, 0, padPos-strPos);
+ strPos = padPos;
+ }
+
+ // Build the pool of style spans.
+
+ size_t styPos = strPos;
+ for (i=0; i<STYLES; i++) {
+ entry_style& ent = mEntryStyleArray.editItemAt(i);
+ const size_t N = ent.spans.size();
+ const size_t totalSize = (N*sizeof(ResStringPool_span))
+ + sizeof(ResStringPool_ref);
+
+ ent.offset = styPos-strPos;
+ uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + totalSize);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string styles\n");
+ return NO_MEMORY;
+ }
+ ResStringPool_span* span = (ResStringPool_span*)(dat+preSize+styPos);
+ for (size_t i=0; i<N; i++) {
+ span->name.index = htodl(ent.spans[i].span.name.index);
+ span->firstChar = htodl(ent.spans[i].span.firstChar);
+ span->lastChar = htodl(ent.spans[i].span.lastChar);
+ span++;
+ }
+ span->name.index = htodl(ResStringPool_span::END);
+
+ styPos += totalSize;
+ }
+
+ if (STYLES > 0) {
+ // Add full terminator at the end (when reading we validate that
+ // the end of the pool is fully terminated to simplify error
+ // checking).
+ size_t extra = sizeof(ResStringPool_span)-sizeof(ResStringPool_ref);
+ uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + extra);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string styles\n");
+ return NO_MEMORY;
+ }
+ uint32_t* p = (uint32_t*)(dat+preSize+styPos);
+ while (extra > 0) {
+ *p++ = htodl(ResStringPool_span::END);
+ extra -= sizeof(uint32_t);
+ }
+ styPos += extra;
+ }
+
+ // Write header.
+
+ ResStringPool_header* header =
+ (ResStringPool_header*)pool->padData(sizeof(uint32_t));
+ if (header == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string pool\n");
+ return NO_MEMORY;
+ }
+ memset(header, 0, sizeof(*header));
+ header->header.type = htods(RES_STRING_POOL_TYPE);
+ header->header.headerSize = htods(sizeof(*header));
+ header->header.size = htodl(pool->getSize());
+ header->stringCount = htodl(ENTRIES);
+ header->styleCount = htodl(STYLES);
+ if (mSorted) {
+ header->flags |= htodl(ResStringPool_header::SORTED_FLAG);
+ }
+ header->stringsStart = htodl(preSize);
+ header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
+
+ // Write string index array.
+
+ uint32_t* index = (uint32_t*)(header+1);
+ if (mSorted) {
+ for (i=0; i<ENTRIES; i++) {
+ entry& ent = const_cast<entry&>(entryAt(i));
+ ent.indices.clear();
+ ent.indices.add(i);
+ *index++ = htodl(ent.offset);
+ }
+ } else {
+ for (i=0; i<ENTRIES; i++) {
+ entry& ent = mEntries.editItemAt(mEntryArray[i]);
+ *index++ = htodl(ent.offset);
+ NOISY(printf("Writing entry #%d: \"%s\" ent=%d off=%d\n", i,
+ String8(ent.value).string(),
+ mEntryArray[i], ent.offset));
+ }
+ }
+
+ // Write style index array.
+
+ if (mSorted) {
+ for (i=0; i<STYLES; i++) {
+ LOG_ALWAYS_FATAL("Shouldn't be here!");
+ }
+ } else {
+ for (i=0; i<STYLES; i++) {
+ *index++ = htodl(mEntryStyleArray[i].offset);
+ }
+ }
+
+ return NO_ERROR;
+}
+
+ssize_t StringPool::offsetForString(const String16& val) const
+{
+ const Vector<size_t>* indices = offsetsForString(val);
+ ssize_t res = indices != NULL && indices->size() > 0 ? indices->itemAt(0) : -1;
+ NOISY(printf("Offset for string %s: %d (%s)\n", String8(val).string(), res,
+ res >= 0 ? String8(mEntries[mEntryArray[res]].value).string() : String8()));
+ return res;
+}
+
+const Vector<size_t>* StringPool::offsetsForString(const String16& val) const
+{
+ ssize_t pos = mValues.valueFor(val);
+ if (pos < 0) {
+ return NULL;
+ }
+ return &mEntries[mEntryArray[pos]].indices;
+}
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
new file mode 100644
index 0000000..9082b37
--- /dev/null
+++ b/tools/aapt/StringPool.h
@@ -0,0 +1,148 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef STRING_POOL_H
+#define STRING_POOL_H
+
+#include "Main.h"
+#include "AaptAssets.h"
+
+#include <utils/ResourceTypes.h>
+#include <utils/String16.h>
+#include <utils/TextOutput.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <expat.h>
+
+using namespace android;
+
+#define PRINT_STRING_METRICS 0
+
+void strcpy16_htod(uint16_t* dst, const uint16_t* src);
+
+void printStringPool(const ResStringPool* pool);
+
+/**
+ * The StringPool class is used as an intermediate representation for
+ * generating the string pool resource data structure that can be parsed with
+ * ResStringPool in include/utils/ResourceTypes.h.
+ */
+class StringPool
+{
+public:
+ struct entry {
+ entry() : offset(0) { }
+ entry(const String16& _value) : value(_value), offset(0) { }
+ entry(const entry& o) : value(o.value), offset(o.offset), indices(o.indices) { }
+
+ String16 value;
+ size_t offset;
+ Vector<size_t> indices;
+ };
+
+ struct entry_style_span {
+ String16 name;
+ ResStringPool_span span;
+ };
+
+ struct entry_style {
+ entry_style() : offset(0) { }
+
+ entry_style(const entry_style& o) : offset(o.offset), spans(o.spans) { }
+
+ size_t offset;
+ Vector<entry_style_span> spans;
+ };
+
+ /**
+ * If 'sorted' is true, then the final strings in the resource data
+ * structure will be generated in sorted order. This allow for fast
+ * lookup with ResStringPool::indexOfString() (O(log n)), at the expense
+ * of support for styled string entries (which requires the same string
+ * be included multiple times in the pool).
+ */
+ explicit StringPool(bool sorted = false);
+
+ /**
+ * Add a new string to the pool. If mergeDuplicates is true, thenif
+ * the string already exists the existing entry for it will be used;
+ * otherwise, or if the value doesn't already exist, a new entry is
+ * created.
+ *
+ * Returns the index in the entry array of the new string entry. Note that
+ * if this string pool is sorted, the returned index will not be valid
+ * when the pool is finally written.
+ */
+ ssize_t add(const String16& value, bool mergeDuplicates = false);
+
+ ssize_t add(const String16& value, const Vector<entry_style_span>& spans);
+
+ ssize_t add(const String16& ident, const String16& value,
+ bool mergeDuplicates = false);
+
+ status_t addStyleSpan(size_t idx, const String16& name,
+ uint32_t start, uint32_t end);
+ status_t addStyleSpans(size_t idx, const Vector<entry_style_span>& spans);
+ status_t addStyleSpan(size_t idx, const entry_style_span& span);
+
+ size_t size() const;
+
+ const entry& entryAt(size_t idx) const;
+
+ size_t countIdentifiers() const;
+
+ sp<AaptFile> createStringBlock();
+
+ status_t writeStringBlock(const sp<AaptFile>& pool);
+
+ /**
+ * Find out an offset in the pool for a particular string. If the string
+ * pool is sorted, this can not be called until after createStringBlock()
+ * or writeStringBlock() has been called
+ * (which determines the offsets). In the case of a string that appears
+ * multiple times in the pool, the first offset will be returned. Returns
+ * -1 if the string does not exist.
+ */
+ ssize_t offsetForString(const String16& val) const;
+
+ /**
+ * Find all of the offsets in the pool for a particular string. If the
+ * string pool is sorted, this can not be called until after
+ * createStringBlock() or writeStringBlock() has been called
+ * (which determines the offsets). Returns NULL if the string does not exist.
+ */
+ const Vector<size_t>* offsetsForString(const String16& val) const;
+
+private:
+ const bool mSorted;
+ // Raw array of unique strings, in some arbitrary order.
+ Vector<entry> mEntries;
+ // Array of indices into mEntries, in the order they were
+ // added to the pool. This can be different than mEntries
+ // if the same string was added multiple times (it will appear
+ // once in mEntries, with multiple occurrences in this array).
+ Vector<size_t> mEntryArray;
+ // Optional style span information associated with each index of
+ // mEntryArray.
+ Vector<entry_style> mEntryStyleArray;
+ // Mapping from indices in mEntryArray to indices in mValues.
+ Vector<size_t> mEntryArrayToValues;
+ // Unique set of all the strings added to the pool, mapped to
+ // the first index of mEntryArray where the value was added.
+ DefaultKeyedVector<String16, ssize_t> mValues;
+ // Unique set of all (optional) identifiers of strings in the
+ // pool, mapping to indices in mEntries.
+ DefaultKeyedVector<String16, ssize_t> mIdents;
+
+};
+
+#endif
+
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
new file mode 100644
index 0000000..d476567
--- /dev/null
+++ b/tools/aapt/XMLNode.cpp
@@ -0,0 +1,1295 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "XMLNode.h"
+#include "ResourceTable.h"
+
+#include <host/pseudolocalize.h>
+#include <utils/ByteOrder.h>
+#include <errno.h>
+#include <string.h>
+
+#ifndef HAVE_MS_C_RUNTIME
+#define O_BINARY 0
+#endif
+
+#define NOISY(x) //x
+#define NOISY_PARSE(x) //x
+
+const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/";
+const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/";
+
+const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
+const char* const ALLOWED_XLIFF_ELEMENTS[] = {
+ "bpt",
+ "ept",
+ "it",
+ "ph",
+ "g",
+ "bx",
+ "ex",
+ "x"
+ };
+
+bool isWhitespace(const char16_t* str)
+{
+ while (*str != 0 && *str < 128 && isspace(*str)) {
+ str++;
+ }
+ return *str == 0;
+}
+
+static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE);
+static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE);
+
+String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic)
+{
+ //printf("%s starts with %s?\n", String8(namespaceUri).string(),
+ // String8(RESOURCES_PREFIX).string());
+ size_t prefixSize;
+ bool isPublic = true;
+ if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
+ prefixSize = RESOURCES_PREFIX.size();
+ } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) {
+ isPublic = false;
+ prefixSize = RESOURCES_PRV_PREFIX.size();
+ } else {
+ if (outIsPublic) *outIsPublic = isPublic; // = true
+ return String16();
+ }
+
+ //printf("YES!\n");
+ //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string());
+ if (outIsPublic) *outIsPublic = isPublic;
+ return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize);
+}
+
+status_t parseStyledString(Bundle* bundle,
+ const char* fileName,
+ ResXMLTree* inXml,
+ const String16& endTag,
+ String16* outString,
+ Vector<StringPool::entry_style_span>* outSpans,
+ bool pseudolocalize)
+{
+ Vector<StringPool::entry_style_span> spanStack;
+ String16 curString;
+ String16 rawString;
+ const char* errorMsg;
+ int xliffDepth = 0;
+ bool firstTime = true;
+
+ size_t len;
+ ResXMLTree::event_code_t code;
+ while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+
+ if (code == ResXMLTree::TEXT) {
+ String16 text(inXml->getText(&len));
+ if (firstTime && text.size() > 0) {
+ firstTime = false;
+ if (text.string()[0] == '@') {
+ // If this is a resource reference, don't do the pseudoloc.
+ pseudolocalize = false;
+ }
+ }
+ if (xliffDepth == 0 && pseudolocalize) {
+ std::string orig(String8(text).string());
+ std::string pseudo = pseudolocalize_string(orig);
+ curString.append(String16(String8(pseudo.c_str())));
+ } else {
+ curString.append(text);
+ }
+ } else if (code == ResXMLTree::START_TAG) {
+ const String16 element16(inXml->getElementName(&len));
+ const String8 element8(element16);
+
+ size_t nslen;
+ const uint16_t* ns = inXml->getElementNamespace(&nslen);
+ if (ns == NULL) {
+ ns = (const uint16_t*)"\0\0";
+ nslen = 0;
+ }
+ const String8 nspace(String16(ns, nslen));
+ if (nspace == XLIFF_XMLNS) {
+ const int N = sizeof(ALLOWED_XLIFF_ELEMENTS)/sizeof(ALLOWED_XLIFF_ELEMENTS[0]);
+ for (int i=0; i<N; i++) {
+ if (element8 == ALLOWED_XLIFF_ELEMENTS[i]) {
+ xliffDepth++;
+ // in this case, treat it like it was just text, in other words, do nothing
+ // here and silently drop this element
+ goto moveon;
+ }
+ }
+ {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found unsupported XLIFF tag <%s>\n",
+ element8.string());
+ return UNKNOWN_ERROR;
+ }
+moveon:
+ continue;
+ }
+
+ if (outSpans == NULL) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found style tag <%s> where styles are not allowed\n", element8.string());
+ return UNKNOWN_ERROR;
+ }
+
+ if (!ResTable::collectString(outString, curString.string(),
+ curString.size(), false, &errorMsg, true)) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
+ errorMsg, String8(curString).string());
+ return UNKNOWN_ERROR;
+ }
+ rawString.append(curString);
+ curString = String16();
+
+ StringPool::entry_style_span span;
+ span.name = element16;
+ for (size_t ai=0; ai<inXml->getAttributeCount(); ai++) {
+ span.name.append(String16(";"));
+ const char16_t* str = inXml->getAttributeName(ai, &len);
+ span.name.append(str, len);
+ span.name.append(String16("="));
+ str = inXml->getAttributeStringValue(ai, &len);
+ span.name.append(str, len);
+ }
+ //printf("Span: %s\n", String8(span.name).string());
+ span.span.firstChar = span.span.lastChar = outString->size();
+ spanStack.push(span);
+
+ } else if (code == ResXMLTree::END_TAG) {
+ size_t nslen;
+ const uint16_t* ns = inXml->getElementNamespace(&nslen);
+ if (ns == NULL) {
+ ns = (const uint16_t*)"\0\0";
+ nslen = 0;
+ }
+ const String8 nspace(String16(ns, nslen));
+ if (nspace == XLIFF_XMLNS) {
+ xliffDepth--;
+ continue;
+ }
+ if (!ResTable::collectString(outString, curString.string(),
+ curString.size(), false, &errorMsg, true)) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
+ errorMsg, String8(curString).string());
+ return UNKNOWN_ERROR;
+ }
+ rawString.append(curString);
+ curString = String16();
+
+ if (spanStack.size() == 0) {
+ if (strcmp16(inXml->getElementName(&len), endTag.string()) != 0) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found tag %s where <%s> close is expected\n",
+ String8(inXml->getElementName(&len)).string(),
+ String8(endTag).string());
+ return UNKNOWN_ERROR;
+ }
+ break;
+ }
+ StringPool::entry_style_span span = spanStack.top();
+ String16 spanTag;
+ ssize_t semi = span.name.findFirst(';');
+ if (semi >= 0) {
+ spanTag.setTo(span.name.string(), semi);
+ } else {
+ spanTag.setTo(span.name);
+ }
+ if (strcmp16(inXml->getElementName(&len), spanTag.string()) != 0) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found close tag %s where close tag %s is expected\n",
+ String8(inXml->getElementName(&len)).string(),
+ String8(spanTag).string());
+ return UNKNOWN_ERROR;
+ }
+ bool empty = true;
+ if (outString->size() > 0) {
+ span.span.lastChar = outString->size()-1;
+ if (span.span.lastChar >= span.span.firstChar) {
+ empty = false;
+ outSpans->add(span);
+ }
+ }
+ spanStack.pop();
+
+ if (empty) {
+ fprintf(stderr, "%s:%d: WARNING: empty '%s' span found in text '%s'\n",
+ fileName, inXml->getLineNumber(),
+ String8(spanTag).string(), String8(*outString).string());
+
+ }
+ } else if (code == ResXMLTree::START_NAMESPACE) {
+ // nothing
+ }
+ }
+
+ if (code == ResXMLTree::BAD_DOCUMENT) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Error parsing XML\n");
+ }
+
+ if (outSpans != NULL && outSpans->size() > 0) {
+ if (curString.size() > 0) {
+ if (!ResTable::collectString(outString, curString.string(),
+ curString.size(), false, &errorMsg, true)) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "%s (in %s)\n",
+ errorMsg, String8(curString).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ } else {
+ // There is no style information, so string processing will happen
+ // later as part of the overall type conversion. Return to the
+ // client the raw unprocessed text.
+ rawString.append(curString);
+ outString->setTo(rawString);
+ }
+
+ return NO_ERROR;
+}
+
+struct namespace_entry {
+ String8 prefix;
+ String8 uri;
+};
+
+static String8 make_prefix(int depth)
+{
+ String8 prefix;
+ int i;
+ for (i=0; i<depth; i++) {
+ prefix.append(" ");
+ }
+ return prefix;
+}
+
+static String8 build_namespace(const Vector<namespace_entry>& namespaces,
+ const uint16_t* ns)
+{
+ String8 str;
+ if (ns != NULL) {
+ str = String8(ns);
+ const size_t N = namespaces.size();
+ for (size_t i=0; i<N; i++) {
+ const namespace_entry& ne = namespaces.itemAt(i);
+ if (ne.uri == str) {
+ str = ne.prefix;
+ break;
+ }
+ }
+ str.append(":");
+ }
+ return str;
+}
+
+void printXMLBlock(ResXMLTree* block)
+{
+ block->restart();
+
+ Vector<namespace_entry> namespaces;
+
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ String8 prefix = make_prefix(depth);
+ int i;
+ if (code == ResXMLTree::START_TAG) {
+ size_t len;
+ const uint16_t* ns16 = block->getElementNamespace(&len);
+ String8 elemNs = build_namespace(namespaces, ns16);
+ const uint16_t* com16 = block->getComment(&len);
+ if (com16) {
+ printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string());
+ }
+ printf("%sE: %s%s (line=%d)\n", prefix.string(), elemNs.string(),
+ String8(block->getElementName(&len)).string(),
+ block->getLineNumber());
+ int N = block->getAttributeCount();
+ depth++;
+ prefix = make_prefix(depth);
+ for (i=0; i<N; i++) {
+ uint32_t res = block->getAttributeNameResID(i);
+ ns16 = block->getAttributeNamespace(i, &len);
+ String8 ns = build_namespace(namespaces, ns16);
+ String8 name(block->getAttributeName(i, &len));
+ printf("%sA: ", prefix.string());
+ if (res) {
+ printf("%s%s(0x%08x)", ns.string(), name.string(), res);
+ } else {
+ printf("%s%s", ns.string(), name.string());
+ }
+ Res_value value;
+ block->getAttributeValue(i, &value);
+ if (value.dataType == Res_value::TYPE_NULL) {
+ printf("=(null)");
+ } else if (value.dataType == Res_value::TYPE_REFERENCE) {
+ printf("=@0x%x", (int)value.data);
+ } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
+ printf("=?0x%x", (int)value.data);
+ } else if (value.dataType == Res_value::TYPE_STRING) {
+ printf("=\"%s\"",
+ String8(block->getAttributeStringValue(i, &len)).string());
+ } else {
+ printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
+ }
+ const char16_t* val = block->getAttributeStringValue(i, &len);
+ if (val != NULL) {
+ printf(" (Raw: \"%s\")", String8(val).string());
+ }
+ printf("\n");
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ depth--;
+ } else if (code == ResXMLTree::START_NAMESPACE) {
+ namespace_entry ns;
+ size_t len;
+ const uint16_t* prefix16 = block->getNamespacePrefix(&len);
+ if (prefix16) {
+ ns.prefix = String8(prefix16);
+ } else {
+ ns.prefix = "<DEF>";
+ }
+ ns.uri = String8(block->getNamespaceUri(&len));
+ namespaces.push(ns);
+ printf("%sN: %s=%s\n", prefix.string(), ns.prefix.string(),
+ ns.uri.string());
+ depth++;
+ } else if (code == ResXMLTree::END_NAMESPACE) {
+ depth--;
+ const namespace_entry& ns = namespaces.top();
+ size_t len;
+ const uint16_t* prefix16 = block->getNamespacePrefix(&len);
+ String8 pr;
+ if (prefix16) {
+ pr = String8(prefix16);
+ } else {
+ pr = "<DEF>";
+ }
+ if (ns.prefix != pr) {
+ prefix = make_prefix(depth);
+ printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n",
+ prefix.string(), pr.string(), ns.prefix.string());
+ }
+ String8 uri = String8(block->getNamespaceUri(&len));
+ if (ns.uri != uri) {
+ prefix = make_prefix(depth);
+ printf("%s *** BAD END NS URI: found=%s, expected=%s\n",
+ prefix.string(), uri.string(), ns.uri.string());
+ }
+ namespaces.pop();
+ } else if (code == ResXMLTree::TEXT) {
+ size_t len;
+ printf("%sC: \"%s\"\n", prefix.string(), String8(block->getText(&len)).string());
+ }
+ }
+
+ block->restart();
+}
+
+status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
+ bool stripAll, bool keepComments,
+ const char** cDataTags)
+{
+ sp<XMLNode> root = XMLNode::parse(file);
+ if (root == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ root->removeWhitespace(stripAll, cDataTags);
+
+ NOISY(printf("Input XML from %s:\n", (const char*)file->getPrintableSource()));
+ NOISY(root->print());
+ sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
+ status_t err = root->flatten(rsc, !keepComments, false);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Output XML:\n"));
+ NOISY(printXMLBlock(outTree));
+
+ return NO_ERROR;
+}
+
+sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
+{
+ char buf[16384];
+ int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
+ strerror(errno));
+ return NULL;
+ }
+
+ XML_Parser parser = XML_ParserCreateNS(NULL, 1);
+ ParseState state;
+ state.filename = file->getPrintableSource();
+ state.parser = parser;
+ XML_SetUserData(parser, &state);
+ XML_SetElementHandler(parser, startElement, endElement);
+ XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
+ XML_SetCharacterDataHandler(parser, characterData);
+ XML_SetCommentHandler(parser, commentData);
+
+ ssize_t len;
+ bool done;
+ do {
+ len = read(fd, buf, sizeof(buf));
+ done = len < (ssize_t)sizeof(buf);
+ if (len < 0) {
+ SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno));
+ close(fd);
+ return NULL;
+ }
+ if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
+ SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
+ "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
+ close(fd);
+ return NULL;
+ }
+ } while (!done);
+
+ XML_ParserFree(parser);
+ if (state.root == NULL) {
+ SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
+ }
+ close(fd);
+ return state.root;
+}
+
+XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
+ : mNextAttributeIndex(0x80000000)
+ , mFilename(filename)
+ , mStartLineNumber(0)
+ , mEndLineNumber(0)
+{
+ if (isNamespace) {
+ mNamespacePrefix = s1;
+ mNamespaceUri = s2;
+ } else {
+ mNamespaceUri = s1;
+ mElementName = s2;
+ }
+}
+
+XMLNode::XMLNode(const String8& filename)
+ : mFilename(filename)
+{
+}
+
+XMLNode::type XMLNode::getType() const
+{
+ if (mElementName.size() != 0) {
+ return TYPE_ELEMENT;
+ }
+ if (mNamespaceUri.size() != 0) {
+ return TYPE_NAMESPACE;
+ }
+ return TYPE_CDATA;
+}
+
+const String16& XMLNode::getNamespacePrefix() const
+{
+ return mNamespacePrefix;
+}
+
+const String16& XMLNode::getNamespaceUri() const
+{
+ return mNamespaceUri;
+}
+
+const String16& XMLNode::getElementNamespace() const
+{
+ return mNamespaceUri;
+}
+
+const String16& XMLNode::getElementName() const
+{
+ return mElementName;
+}
+
+const Vector<sp<XMLNode> >& XMLNode::getChildren() const
+{
+ return mChildren;
+}
+
+const Vector<XMLNode::attribute_entry>&
+ XMLNode::getAttributes() const
+{
+ return mAttributes;
+}
+
+const String16& XMLNode::getCData() const
+{
+ return mChars;
+}
+
+const String16& XMLNode::getComment() const
+{
+ return mComment;
+}
+
+int32_t XMLNode::getStartLineNumber() const
+{
+ return mStartLineNumber;
+}
+
+int32_t XMLNode::getEndLineNumber() const
+{
+ return mEndLineNumber;
+}
+
+status_t XMLNode::addChild(const sp<XMLNode>& child)
+{
+ if (getType() == TYPE_CDATA) {
+ SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
+ return UNKNOWN_ERROR;
+ }
+ //printf("Adding child %p to parent %p\n", child.get(), this);
+ mChildren.add(child);
+ return NO_ERROR;
+}
+
+status_t XMLNode::addAttribute(const String16& ns, const String16& name,
+ const String16& value)
+{
+ if (getType() == TYPE_CDATA) {
+ SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node.");
+ return UNKNOWN_ERROR;
+ }
+ attribute_entry e;
+ e.index = mNextAttributeIndex++;
+ e.ns = ns;
+ e.name = name;
+ e.string = value;
+ mAttributes.add(e);
+ mAttributeOrder.add(e.index, mAttributes.size()-1);
+ return NO_ERROR;
+}
+
+void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
+{
+ attribute_entry& e = mAttributes.editItemAt(attrIdx);
+ if (e.nameResId) {
+ mAttributeOrder.removeItem(e.nameResId);
+ } else {
+ mAttributeOrder.removeItem(e.index);
+ }
+ NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n",
+ String8(getElementName()).string(),
+ String8(mAttributes.itemAt(attrIdx).name).string(),
+ String8(mAttributes.itemAt(attrIdx).string).string(),
+ resId));
+ mAttributes.editItemAt(attrIdx).nameResId = resId;
+ mAttributeOrder.add(resId, attrIdx);
+}
+
+status_t XMLNode::appendChars(const String16& chars)
+{
+ if (getType() != TYPE_CDATA) {
+ SourcePos(mFilename, getStartLineNumber()).error("Adding characters to element node.");
+ return UNKNOWN_ERROR;
+ }
+ mChars.append(chars);
+ return NO_ERROR;
+}
+
+status_t XMLNode::appendComment(const String16& comment)
+{
+ if (mComment.size() > 0) {
+ mComment.append(String16("\n"));
+ }
+ mComment.append(comment);
+ return NO_ERROR;
+}
+
+void XMLNode::setStartLineNumber(int32_t line)
+{
+ mStartLineNumber = line;
+}
+
+void XMLNode::setEndLineNumber(int32_t line)
+{
+ mEndLineNumber = line;
+}
+
+void XMLNode::removeWhitespace(bool stripAll, const char** cDataTags)
+{
+ //printf("Removing whitespace in %s\n", String8(mElementName).string());
+ size_t N = mChildren.size();
+ if (cDataTags) {
+ String8 tag(mElementName);
+ const char** p = cDataTags;
+ while (*p) {
+ if (tag == *p) {
+ stripAll = false;
+ break;
+ }
+ }
+ }
+ for (size_t i=0; i<N; i++) {
+ sp<XMLNode> node = mChildren.itemAt(i);
+ if (node->getType() == TYPE_CDATA) {
+ // This is a CDATA node...
+ const char16_t* p = node->mChars.string();
+ while (*p != 0 && *p < 128 && isspace(*p)) {
+ p++;
+ }
+ //printf("Space ends at %d in \"%s\"\n",
+ // (int)(p-node->mChars.string()),
+ // String8(node->mChars).string());
+ if (*p == 0) {
+ if (stripAll) {
+ // Remove this node!
+ mChildren.removeAt(i);
+ N--;
+ i--;
+ } else {
+ node->mChars = String16(" ");
+ }
+ } else {
+ // Compact leading/trailing whitespace.
+ const char16_t* e = node->mChars.string()+node->mChars.size()-1;
+ while (e > p && *e < 128 && isspace(*e)) {
+ e--;
+ }
+ if (p > node->mChars.string()) {
+ p--;
+ }
+ if (e < (node->mChars.string()+node->mChars.size()-1)) {
+ e++;
+ }
+ if (p > node->mChars.string() ||
+ e < (node->mChars.string()+node->mChars.size()-1)) {
+ String16 tmp(p, e-p+1);
+ node->mChars = tmp;
+ }
+ }
+ } else {
+ node->removeWhitespace(stripAll, cDataTags);
+ }
+ }
+}
+
+status_t XMLNode::parseValues(const sp<AaptAssets>& assets,
+ ResourceTable* table)
+{
+ bool hasErrors = false;
+
+ if (getType() == TYPE_ELEMENT) {
+ const size_t N = mAttributes.size();
+ String16 defPackage(assets->getPackage());
+ for (size_t i=0; i<N; i++) {
+ attribute_entry& e = mAttributes.editItemAt(i);
+ AccessorCookie ac(SourcePos(mFilename, getStartLineNumber()), String8(e.name),
+ String8(e.string));
+ table->setCurrentXmlPos(SourcePos(mFilename, getStartLineNumber()));
+ if (!assets->getIncludedResources()
+ .stringToValue(&e.value, &e.string,
+ e.string.string(), e.string.size(), true, true,
+ e.nameResId, NULL, &defPackage, table, &ac)) {
+ hasErrors = true;
+ }
+ NOISY(printf("Attr %s: type=0x%x, str=%s\n",
+ String8(e.name).string(), e.value.dataType,
+ String8(e.string).string()));
+ }
+ }
+ const size_t N = mChildren.size();
+ for (size_t i=0; i<N; i++) {
+ status_t err = mChildren.itemAt(i)->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
+ const ResourceTable* table)
+{
+ bool hasErrors = false;
+
+ if (getType() == TYPE_ELEMENT) {
+ String16 attr("attr");
+ const char* errorMsg;
+ const size_t N = mAttributes.size();
+ for (size_t i=0; i<N; i++) {
+ const attribute_entry& e = mAttributes.itemAt(i);
+ if (e.ns.size() <= 0) continue;
+ bool nsIsPublic;
+ String16 pkg(getNamespaceResourcePackage(e.ns, &nsIsPublic));
+ NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
+ String8(getElementName()).string(),
+ String8(e.name).string(),
+ String8(e.string).string(),
+ String8(e.ns).string(),
+ (nsIsPublic) ? "public" : "private",
+ String8(pkg).string()));
+ if (pkg.size() <= 0) continue;
+ uint32_t res = table != NULL
+ ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic)
+ : assets->getIncludedResources().
+ identifierForName(e.name.string(), e.name.size(),
+ attr.string(), attr.size(),
+ pkg.string(), pkg.size());
+ if (res != 0) {
+ NOISY(printf("XML attribute name %s: resid=0x%08x\n",
+ String8(e.name).string(), res));
+ setAttributeResID(i, res);
+ } else {
+ SourcePos(mFilename, getStartLineNumber()).error(
+ "No resource identifier found for attribute '%s' in package '%s'\n",
+ String8(e.name).string(), String8(pkg).string());
+ hasErrors = true;
+ }
+ }
+ }
+ const size_t N = mChildren.size();
+ for (size_t i=0; i<N; i++) {
+ status_t err = mChildren.itemAt(i)->assignResourceIds(assets, table);
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t XMLNode::flatten(const sp<AaptFile>& dest,
+ bool stripComments, bool stripRawValues) const
+{
+ StringPool strings;
+ Vector<uint32_t> resids;
+
+ // First collect just the strings for attribute names that have a
+ // resource ID assigned to them. This ensures that the resource ID
+ // array is compact, and makes it easier to deal with attribute names
+ // in different namespaces (and thus with different resource IDs).
+ collect_resid_strings(&strings, &resids);
+
+ // Next collect all remainibng strings.
+ collect_strings(&strings, &resids, stripComments, stripRawValues);
+
+#if 0 // No longer compiles
+ NOISY(printf("Found strings:\n");
+ const size_t N = strings.size();
+ for (size_t i=0; i<N; i++) {
+ printf("%s\n", String8(strings.entryAt(i).string).string());
+ }
+ );
+#endif
+
+ sp<AaptFile> stringPool = strings.createStringBlock();
+ NOISY(aout << "String pool:"
+ << HexDump(stringPool->getData(), stringPool->getSize()) << endl);
+
+ ResXMLTree_header header;
+ memset(&header, 0, sizeof(header));
+ header.header.type = htods(RES_XML_TYPE);
+ header.header.headerSize = htods(sizeof(header));
+
+ const size_t basePos = dest->getSize();
+ dest->writeData(&header, sizeof(header));
+ dest->writeData(stringPool->getData(), stringPool->getSize());
+
+ // If we have resource IDs, write them.
+ if (resids.size() > 0) {
+ const size_t resIdsPos = dest->getSize();
+ const size_t resIdsSize =
+ sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
+ ResChunk_header* idsHeader = (ResChunk_header*)
+ (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
+ idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
+ idsHeader->headerSize = htods(sizeof(*idsHeader));
+ idsHeader->size = htodl(resIdsSize);
+ uint32_t* ids = (uint32_t*)(idsHeader+1);
+ for (size_t i=0; i<resids.size(); i++) {
+ *ids++ = htodl(resids[i]);
+ }
+ }
+
+ flatten_node(strings, dest, stripComments, stripRawValues);
+
+ void* data = dest->editData();
+ ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
+ size_t size = dest->getSize()-basePos;
+ hd->header.size = htodl(dest->getSize()-basePos);
+
+ NOISY(aout << "XML resource:"
+ << HexDump(dest->getData(), dest->getSize()) << endl);
+
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** total xml size: %d / %d%% strings (in %s)\n",
+ dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
+ dest->getPath().string());
+ #endif
+
+ return NO_ERROR;
+}
+
+void XMLNode::print(int indent)
+{
+ String8 prefix;
+ int i;
+ for (i=0; i<indent; i++) {
+ prefix.append(" ");
+ }
+ if (getType() == TYPE_ELEMENT) {
+ String8 elemNs(getNamespaceUri());
+ if (elemNs.size() > 0) {
+ elemNs.append(":");
+ }
+ printf("%s E: %s%s", prefix.string(),
+ elemNs.string(), String8(getElementName()).string());
+ int N = mAttributes.size();
+ for (i=0; i<N; i++) {
+ ssize_t idx = mAttributeOrder.valueAt(i);
+ if (i == 0) {
+ printf(" / ");
+ } else {
+ printf(", ");
+ }
+ const attribute_entry& attr = mAttributes.itemAt(idx);
+ String8 attrNs(attr.ns);
+ if (attrNs.size() > 0) {
+ attrNs.append(":");
+ }
+ if (attr.nameResId) {
+ printf("%s%s(0x%08x)", attrNs.string(),
+ String8(attr.name).string(), attr.nameResId);
+ } else {
+ printf("%s%s", attrNs.string(), String8(attr.name).string());
+ }
+ printf("=%s", String8(attr.string).string());
+ }
+ printf("\n");
+ } else if (getType() == TYPE_NAMESPACE) {
+ printf("%s N: %s=%s\n", prefix.string(),
+ getNamespacePrefix().size() > 0
+ ? String8(getNamespacePrefix()).string() : "<DEF>",
+ String8(getNamespaceUri()).string());
+ } else {
+ printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string());
+ }
+ int N = mChildren.size();
+ for (i=0; i<N; i++) {
+ mChildren.itemAt(i)->print(indent+1);
+ }
+}
+
+static void splitName(const char* name, String16* outNs, String16* outName)
+{
+ const char* p = name;
+ while (*p != 0 && *p != 1) {
+ p++;
+ }
+ if (*p == 0) {
+ *outNs = String16();
+ *outName = String16(name);
+ } else {
+ *outNs = String16(name, (p-name));
+ *outName = String16(p+1);
+ }
+}
+
+void XMLCALL
+XMLNode::startNamespace(void *userData, const char *prefix, const char *uri)
+{
+ NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix, uri));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = XMLNode::newNamespace(st->filename,
+ String16(prefix != NULL ? prefix : ""), String16(uri));
+ node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+ if (st->stack.size() > 0) {
+ st->stack.itemAt(st->stack.size()-1)->addChild(node);
+ } else {
+ st->root = node;
+ }
+ st->stack.push(node);
+}
+
+void XMLCALL
+XMLNode::startElement(void *userData, const char *name, const char **atts)
+{
+ NOISY_PARSE(printf("Start Element: %s\n", name));
+ ParseState* st = (ParseState*)userData;
+ String16 ns16, name16;
+ splitName(name, &ns16, &name16);
+ sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
+ node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+ if (st->pendingComment.size() > 0) {
+ node->appendComment(st->pendingComment);
+ st->pendingComment = String16();
+ }
+ if (st->stack.size() > 0) {
+ st->stack.itemAt(st->stack.size()-1)->addChild(node);
+ } else {
+ st->root = node;
+ }
+ st->stack.push(node);
+
+ for (int i = 0; atts[i]; i += 2) {
+ splitName(atts[i], &ns16, &name16);
+ node->addAttribute(ns16, name16, String16(atts[i+1]));
+ }
+}
+
+void XMLCALL
+XMLNode::characterData(void *userData, const XML_Char *s, int len)
+{
+ NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s, len).string()));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = NULL;
+ if (st->stack.size() == 0) {
+ return;
+ }
+ sp<XMLNode> parent = st->stack.itemAt(st->stack.size()-1);
+ if (parent != NULL && parent->getChildren().size() > 0) {
+ node = parent->getChildren()[parent->getChildren().size()-1];
+ if (node->getType() != TYPE_CDATA) {
+ // Last node is not CDATA, need to make a new node.
+ node = NULL;
+ }
+ }
+
+ if (node == NULL) {
+ node = XMLNode::newCData(st->filename);
+ node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+ parent->addChild(node);
+ }
+
+ node->appendChars(String16(s, len));
+}
+
+void XMLCALL
+XMLNode::endElement(void *userData, const char *name)
+{
+ NOISY_PARSE(printf("End Element: %s\n", name));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
+ node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
+ if (st->pendingComment.size() > 0) {
+ node->appendComment(st->pendingComment);
+ st->pendingComment = String16();
+ }
+ String16 ns16, name16;
+ splitName(name, &ns16, &name16);
+ LOG_ALWAYS_FATAL_IF(node->getElementNamespace() != ns16
+ || node->getElementName() != name16,
+ "Bad end element %s", name);
+ st->stack.pop();
+}
+
+void XMLCALL
+XMLNode::endNamespace(void *userData, const char *prefix)
+{
+ const char* nonNullPrefix = prefix != NULL ? prefix : "";
+ NOISY_PARSE(printf("End Namespace: %s\n", prefix));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
+ node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
+ LOG_ALWAYS_FATAL_IF(node->getNamespacePrefix() != String16(nonNullPrefix),
+ "Bad end namespace %s", prefix);
+ st->stack.pop();
+}
+
+void XMLCALL
+XMLNode::commentData(void *userData, const char *comment)
+{
+ NOISY_PARSE(printf("Comment: %s\n", comment));
+ ParseState* st = (ParseState*)userData;
+ if (st->pendingComment.size() > 0) {
+ st->pendingComment.append(String16("\n"));
+ }
+ st->pendingComment.append(String16(comment));
+}
+
+status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
+ bool stripComments, bool stripRawValues) const
+{
+ collect_attr_strings(dest, outResIds, true);
+
+ int i;
+ if (mNamespacePrefix.size() > 0) {
+ dest->add(mNamespacePrefix, true);
+ }
+ if (mNamespaceUri.size() > 0) {
+ dest->add(mNamespaceUri, true);
+ }
+ if (mElementName.size() > 0) {
+ dest->add(mElementName, true);
+ }
+
+ if (!stripComments && mComment.size() > 0) {
+ dest->add(mComment, true);
+ }
+
+ const int NA = mAttributes.size();
+
+ for (i=0; i<NA; i++) {
+ const attribute_entry& ae = mAttributes.itemAt(i);
+ if (ae.ns.size() > 0) {
+ dest->add(ae.ns, true);
+ }
+ if (!stripRawValues || ae.needStringValue()) {
+ dest->add(ae.string, true);
+ }
+ /*
+ if (ae.value.dataType == Res_value::TYPE_NULL
+ || ae.value.dataType == Res_value::TYPE_STRING) {
+ dest->add(ae.string, true);
+ }
+ */
+ }
+
+ if (mElementName.size() == 0) {
+ // If not an element, include the CDATA, even if it is empty.
+ dest->add(mChars, true);
+ }
+
+ const int NC = mChildren.size();
+
+ for (i=0; i<NC; i++) {
+ mChildren.itemAt(i)->collect_strings(dest, outResIds,
+ stripComments, stripRawValues);
+ }
+
+ return NO_ERROR;
+}
+
+status_t XMLNode::collect_attr_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds, bool allAttrs) const {
+ const int NA = mAttributes.size();
+
+ for (int i=0; i<NA; i++) {
+ const attribute_entry& attr = mAttributes.itemAt(i);
+ uint32_t id = attr.nameResId;
+ if (id || allAttrs) {
+ // See if we have already assigned this resource ID to a pooled
+ // string...
+ const Vector<size_t>* indices = outPool->offsetsForString(attr.name);
+ ssize_t idx = -1;
+ if (indices != NULL) {
+ const int NJ = indices->size();
+ const size_t NR = outResIds->size();
+ for (int j=0; j<NJ; j++) {
+ size_t strIdx = indices->itemAt(j);
+ if (strIdx >= NR) {
+ if (id == 0) {
+ // We don't need to assign a resource ID for this one.
+ idx = strIdx;
+ break;
+ }
+ // Just ignore strings that are out of range of
+ // the currently assigned resource IDs... we add
+ // strings as we assign the first ID.
+ } else if (outResIds->itemAt(strIdx) == id) {
+ idx = strIdx;
+ break;
+ }
+ }
+ }
+ if (idx < 0) {
+ idx = outPool->add(attr.name);
+ NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n",
+ String8(attr.name).string(), id, idx));
+ if (id != 0) {
+ while ((ssize_t)outResIds->size() <= idx) {
+ outResIds->add(0);
+ }
+ outResIds->replaceAt(id, idx);
+ }
+ }
+ attr.namePoolIdx = idx;
+ NOISY(printf("String %s offset=0x%08x\n",
+ String8(attr.name).string(), idx));
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t XMLNode::collect_resid_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds) const
+{
+ collect_attr_strings(outPool, outResIds, false);
+
+ const int NC = mChildren.size();
+
+ for (int i=0; i<NC; i++) {
+ mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds);
+ }
+
+ return NO_ERROR;
+}
+
+status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
+ bool stripComments, bool stripRawValues) const
+{
+ ResXMLTree_node node;
+ ResXMLTree_cdataExt cdataExt;
+ ResXMLTree_namespaceExt namespaceExt;
+ ResXMLTree_attrExt attrExt;
+ const void* extData = NULL;
+ size_t extSize = 0;
+ ResXMLTree_attribute attr;
+
+ const size_t NA = mAttributes.size();
+ const size_t NC = mChildren.size();
+ size_t i;
+
+ LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!");
+
+ const String16 id16("id");
+ const String16 class16("class");
+ const String16 style16("style");
+
+ const type type = getType();
+
+ memset(&node, 0, sizeof(node));
+ memset(&attr, 0, sizeof(attr));
+ node.header.headerSize = htods(sizeof(node));
+ node.lineNumber = htodl(getStartLineNumber());
+ if (!stripComments) {
+ node.comment.index = htodl(
+ mComment.size() > 0 ? strings.offsetForString(mComment) : -1);
+ //if (mComment.size() > 0) {
+ // printf("Flattening comment: %s\n", String8(mComment).string());
+ //}
+ } else {
+ node.comment.index = htodl((uint32_t)-1);
+ }
+ if (type == TYPE_ELEMENT) {
+ node.header.type = htods(RES_XML_START_ELEMENT_TYPE);
+ extData = &attrExt;
+ extSize = sizeof(attrExt);
+ memset(&attrExt, 0, sizeof(attrExt));
+ if (mNamespaceUri.size() > 0) {
+ attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri));
+ } else {
+ attrExt.ns.index = htodl((uint32_t)-1);
+ }
+ attrExt.name.index = htodl(strings.offsetForString(mElementName));
+ attrExt.attributeStart = htods(sizeof(attrExt));
+ attrExt.attributeSize = htods(sizeof(attr));
+ attrExt.attributeCount = htods(NA);
+ attrExt.idIndex = htods(0);
+ attrExt.classIndex = htods(0);
+ attrExt.styleIndex = htods(0);
+ for (i=0; i<NA; i++) {
+ ssize_t idx = mAttributeOrder.valueAt(i);
+ const attribute_entry& ae = mAttributes.itemAt(idx);
+ if (ae.ns.size() == 0) {
+ if (ae.name == id16) {
+ attrExt.idIndex = htods(i+1);
+ } else if (ae.name == class16) {
+ attrExt.classIndex = htods(i+1);
+ } else if (ae.name == style16) {
+ attrExt.styleIndex = htods(i+1);
+ }
+ }
+ }
+ } else if (type == TYPE_NAMESPACE) {
+ node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
+ extData = &namespaceExt;
+ extSize = sizeof(namespaceExt);
+ memset(&namespaceExt, 0, sizeof(namespaceExt));
+ if (mNamespacePrefix.size() > 0) {
+ namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
+ } else {
+ namespaceExt.prefix.index = htodl((uint32_t)-1);
+ }
+ namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
+ namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
+ LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
+ } else if (type == TYPE_CDATA) {
+ node.header.type = htods(RES_XML_CDATA_TYPE);
+ extData = &cdataExt;
+ extSize = sizeof(cdataExt);
+ memset(&cdataExt, 0, sizeof(cdataExt));
+ cdataExt.data.index = htodl(strings.offsetForString(mChars));
+ cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
+ cdataExt.typedData.res0 = 0;
+ cdataExt.typedData.dataType = mCharsValue.dataType;
+ cdataExt.typedData.data = htodl(mCharsValue.data);
+ LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
+ }
+
+ node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
+
+ dest->writeData(&node, sizeof(node));
+ if (extSize > 0) {
+ dest->writeData(extData, extSize);
+ }
+
+ for (i=0; i<NA; i++) {
+ ssize_t idx = mAttributeOrder.valueAt(i);
+ const attribute_entry& ae = mAttributes.itemAt(idx);
+ if (ae.ns.size() > 0) {
+ attr.ns.index = htodl(strings.offsetForString(ae.ns));
+ } else {
+ attr.ns.index = htodl((uint32_t)-1);
+ }
+ attr.name.index = htodl(ae.namePoolIdx);
+
+ if (!stripRawValues || ae.needStringValue()) {
+ attr.rawValue.index = htodl(strings.offsetForString(ae.string));
+ } else {
+ attr.rawValue.index = htodl((uint32_t)-1);
+ }
+ attr.typedValue.size = htods(sizeof(attr.typedValue));
+ if (ae.value.dataType == Res_value::TYPE_NULL
+ || ae.value.dataType == Res_value::TYPE_STRING) {
+ attr.typedValue.res0 = 0;
+ attr.typedValue.dataType = Res_value::TYPE_STRING;
+ attr.typedValue.data = htodl(strings.offsetForString(ae.string));
+ } else {
+ attr.typedValue.res0 = 0;
+ attr.typedValue.dataType = ae.value.dataType;
+ attr.typedValue.data = htodl(ae.value.data);
+ }
+ dest->writeData(&attr, sizeof(attr));
+ }
+
+ for (i=0; i<NC; i++) {
+ status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
+ stripComments, stripRawValues);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ if (type == TYPE_ELEMENT) {
+ ResXMLTree_endElementExt endElementExt;
+ memset(&endElementExt, 0, sizeof(endElementExt));
+ node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
+ node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
+ node.lineNumber = htodl(getEndLineNumber());
+ node.comment.index = htodl((uint32_t)-1);
+ endElementExt.ns.index = attrExt.ns.index;
+ endElementExt.name.index = attrExt.name.index;
+ dest->writeData(&node, sizeof(node));
+ dest->writeData(&endElementExt, sizeof(endElementExt));
+ } else if (type == TYPE_NAMESPACE) {
+ node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
+ node.lineNumber = htodl(getEndLineNumber());
+ node.comment.index = htodl((uint32_t)-1);
+ node.header.size = htodl(sizeof(node)+extSize);
+ dest->writeData(&node, sizeof(node));
+ dest->writeData(extData, extSize);
+ }
+
+ return NO_ERROR;
+}
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
new file mode 100644
index 0000000..86548a2
--- /dev/null
+++ b/tools/aapt/XMLNode.h
@@ -0,0 +1,184 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef XML_NODE_H
+#define XML_NODE_H
+
+#include "StringPool.h"
+#include "ResourceTable.h"
+
+class XMLNode;
+
+extern const char* const RESOURCES_ROOT_NAMESPACE;
+extern const char* const RESOURCES_ANDROID_NAMESPACE;
+
+bool isWhitespace(const char16_t* str);
+
+String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic = NULL);
+
+status_t parseStyledString(Bundle* bundle,
+ const char* fileName,
+ ResXMLTree* inXml,
+ const String16& endTag,
+ String16* outString,
+ Vector<StringPool::entry_style_span>* outSpans,
+ bool isPseudolocalizable);
+
+void printXMLBlock(ResXMLTree* block);
+
+status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
+ bool stripAll=true, bool keepComments=false,
+ const char** cDataTags=NULL);
+
+class XMLNode : public RefBase
+{
+public:
+ static sp<XMLNode> parse(const sp<AaptFile>& file);
+
+ static inline
+ sp<XMLNode> newNamespace(const String8& filename, const String16& prefix, const String16& uri) {
+ return new XMLNode(filename, prefix, uri, true);
+ }
+
+ static inline
+ sp<XMLNode> newElement(const String8& filename, const String16& ns, const String16& name) {
+ return new XMLNode(filename, ns, name, false);
+ }
+
+ static inline
+ sp<XMLNode> newCData(const String8& filename) {
+ return new XMLNode(filename);
+ }
+
+ enum type {
+ TYPE_NAMESPACE,
+ TYPE_ELEMENT,
+ TYPE_CDATA
+ };
+
+ type getType() const;
+
+ const String16& getNamespacePrefix() const;
+ const String16& getNamespaceUri() const;
+
+ const String16& getElementNamespace() const;
+ const String16& getElementName() const;
+ const Vector<sp<XMLNode> >& getChildren() const;
+
+ struct attribute_entry {
+ attribute_entry() : index(~(uint32_t)0), nameResId(0)
+ {
+ value.dataType = Res_value::TYPE_NULL;
+ }
+
+ bool needStringValue() const {
+ return nameResId == 0
+ || value.dataType == Res_value::TYPE_NULL
+ || value.dataType == Res_value::TYPE_STRING;
+ }
+
+ String16 ns;
+ String16 name;
+ String16 string;
+ Res_value value;
+ uint32_t index;
+ uint32_t nameResId;
+ mutable uint32_t namePoolIdx;
+ };
+
+ const Vector<attribute_entry>& getAttributes() const;
+
+ const String16& getCData() const;
+
+ const String16& getComment() const;
+
+ int32_t getStartLineNumber() const;
+ int32_t getEndLineNumber() const;
+
+ status_t addChild(const sp<XMLNode>& child);
+
+ status_t addAttribute(const String16& ns, const String16& name,
+ const String16& value);
+
+ void setAttributeResID(size_t attrIdx, uint32_t resId);
+
+ status_t appendChars(const String16& chars);
+
+ status_t appendComment(const String16& comment);
+
+ void setStartLineNumber(int32_t line);
+ void setEndLineNumber(int32_t line);
+
+ void removeWhitespace(bool stripAll=true, const char** cDataTags=NULL);
+
+ status_t parseValues(const sp<AaptAssets>& assets, ResourceTable* table);
+
+ status_t assignResourceIds(const sp<AaptAssets>& assets,
+ const ResourceTable* table = NULL);
+
+ status_t flatten(const sp<AaptFile>& dest, bool stripComments,
+ bool stripRawValues) const;
+
+ void print(int indent=0);
+
+private:
+ struct ParseState
+ {
+ String8 filename;
+ XML_Parser parser;
+ sp<XMLNode> root;
+ Vector<sp<XMLNode> > stack;
+ String16 pendingComment;
+ };
+
+ static void XMLCALL
+ startNamespace(void *userData, const char *prefix, const char *uri);
+ static void XMLCALL
+ startElement(void *userData, const char *name, const char **atts);
+ static void XMLCALL
+ characterData(void *userData, const XML_Char *s, int len);
+ static void XMLCALL
+ endElement(void *userData, const char *name);
+ static void XMLCALL
+ endNamespace(void *userData, const char *prefix);
+
+ static void XMLCALL
+ commentData(void *userData, const char *comment);
+
+ // Creating an element node.
+ XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace);
+
+ // Creating a CDATA node.
+ XMLNode(const String8& filename);
+
+ status_t collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
+ bool stripComments, bool stripRawValues) const;
+
+ status_t collect_attr_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds, bool allAttrs) const;
+
+ status_t collect_resid_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds) const;
+
+ status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
+ bool stripComments, bool stripRawValues) const;
+
+ String16 mNamespacePrefix;
+ String16 mNamespaceUri;
+ String16 mElementName;
+ Vector<sp<XMLNode> > mChildren;
+ Vector<attribute_entry> mAttributes;
+ KeyedVector<uint32_t, uint32_t> mAttributeOrder;
+ uint32_t mNextAttributeIndex;
+ String16 mChars;
+ Res_value mCharsValue;
+ String16 mComment;
+ String8 mFilename;
+ int32_t mStartLineNumber;
+ int32_t mEndLineNumber;
+};
+
+#endif
diff --git a/tools/aapt/printapk.cpp b/tools/aapt/printapk.cpp
new file mode 100644
index 0000000..4cf73d8
--- /dev/null
+++ b/tools/aapt/printapk.cpp
@@ -0,0 +1,127 @@
+#include <utils/ResourceTypes.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <zipfile/zipfile.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+using namespace android;
+
+static int
+usage()
+{
+ fprintf(stderr,
+ "usage: apk APKFILE\n"
+ "\n"
+ "APKFILE an android packge file produced by aapt.\n"
+ );
+ return 1;
+}
+
+
+int
+main(int argc, char** argv)
+{
+ const char* filename;
+ int fd;
+ ssize_t amt;
+ off_t size;
+ void* buf;
+ zipfile_t zip;
+ zipentry_t entry;
+ void* cookie;
+ void* resfile;
+ int bufsize;
+ int err;
+
+ if (argc != 2) {
+ return usage();
+ }
+
+ filename = argv[1];
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "apk: couldn't open file for read: %s\n", filename);
+ return 1;
+ }
+
+ size = lseek(fd, 0, SEEK_END);
+ amt = lseek(fd, 0, SEEK_SET);
+
+ if (size < 0 || amt < 0) {
+ fprintf(stderr, "apk: error determining file size: %s\n", filename);
+ return 1;
+ }
+
+ buf = malloc(size);
+ if (buf == NULL) {
+ fprintf(stderr, "apk: file too big: %s\n", filename);
+ return 1;
+ }
+
+ amt = read(fd, buf, size);
+ if (amt != size) {
+ fprintf(stderr, "apk: error reading file: %s\n", filename);
+ return 1;
+ }
+
+ close(fd);
+
+ zip = init_zipfile(buf, size);
+ if (zip == NULL) {
+ fprintf(stderr, "apk: file doesn't seem to be a zip file: %s\n",
+ filename);
+ return 1;
+ }
+
+ printf("files:\n");
+ cookie = NULL;
+ while ((entry = iterate_zipfile(zip, &cookie))) {
+ char* name = get_zipentry_name(entry);
+ printf(" %s\n", name);
+ free(name);
+ }
+
+ entry = lookup_zipentry(zip, "resources.arsc");
+ if (entry != NULL) {
+ size = get_zipentry_size(entry);
+ bufsize = size + (size / 1000) + 1;
+ resfile = malloc(bufsize);
+
+ err = decompress_zipentry(entry, resfile, bufsize);
+ if (err != 0) {
+ fprintf(stderr, "apk: error decompressing resources.arsc");
+ return 1;
+ }
+
+ ResTable res(resfile, size, resfile);
+ res.print();
+#if 0
+ size_t tableCount = res.getTableCount();
+ printf("Tables: %d\n", (int)tableCount);
+ for (size_t tableIndex=0; tableIndex<tableCount; tableIndex++) {
+ const ResStringPool* strings = res.getTableStringBlock(tableIndex);
+ size_t stringCount = strings->size();
+ for (size_t stringIndex=0; stringIndex<stringCount; stringIndex++) {
+ size_t len;
+ const char16_t* ch = strings->stringAt(stringIndex, &len);
+ String8 s(String16(ch, len));
+ printf(" [%3d] %s\n", (int)stringIndex, s.string());
+ }
+ }
+
+ size_t basePackageCount = res.getBasePackageCount();
+ printf("Base Packages: %d\n", (int)basePackageCount);
+ for (size_t bpIndex=0; bpIndex<basePackageCount; bpIndex++) {
+ const char16_t* ch = res.getBasePackageName(bpIndex);
+ String8 s = String8(String16(ch));
+ printf(" [%3d] %s\n", (int)bpIndex, s.string());
+ }
+#endif
+ }
+
+
+ return 0;
+}
diff --git a/tools/aapt/tests/plurals/AndroidManifest.xml b/tools/aapt/tests/plurals/AndroidManifest.xml
new file mode 100644
index 0000000..c721dee
--- /dev/null
+++ b/tools/aapt/tests/plurals/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.aapt.test.plurals">
+
+</manifest>
diff --git a/tools/aapt/tests/plurals/res/values/strings.xml b/tools/aapt/tests/plurals/res/values/strings.xml
new file mode 100644
index 0000000..1c1fc19
--- /dev/null
+++ b/tools/aapt/tests/plurals/res/values/strings.xml
@@ -0,0 +1,7 @@
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="ok">OK</string>
+ <plurals name="a_plural">
+ <item quantity="one">A dog</item>
+ <item quantity="other">Some dogs</item>
+ </plurals>
+</resources>
diff --git a/tools/aapt/tests/plurals/run.sh b/tools/aapt/tests/plurals/run.sh
new file mode 100755
index 0000000..4d39e10
--- /dev/null
+++ b/tools/aapt/tests/plurals/run.sh
@@ -0,0 +1,16 @@
+TEST_DIR=tools/aapt/tests/plurals
+TEST_OUT_DIR=out/plurals_test
+
+rm -rf $TEST_OUT_DIR
+mkdir -p $TEST_OUT_DIR
+mkdir -p $TEST_OUT_DIR/java
+
+#gdb --args \
+aapt package -v -x -m -z -J $TEST_OUT_DIR/java -M $TEST_DIR/AndroidManifest.xml \
+ -I out/target/common/obj/APPS/framework-res_intermediates/package-export.apk \
+ -P $TEST_OUT_DIR/public_resources.xml \
+ -S $TEST_DIR/res
+
+echo
+echo "==================== FILES CREATED ==================== "
+find $TEST_OUT_DIR -type f